diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index aa777be8..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve. -title: '' -labels: ["bug", "process/needs triage"] -assignees: '' - ---- - -**Describe the bug** -Describe the issue you are experiencing here. -Tell us what you were trying to do and what happened. - -**Expected behavior** -Describe clearly and concisely what you expected to happen. - -**Actual behavior** -Describe clearly and concisely what actually happened. - -**To Reproduce** -Link to a small reproducer or attach an archive containing the reproducer to the issue. -Alternatively, provide clear and concise steps to reproduce the behavior. - -## Environment - -**Timefold Solver Version or Git ref**: - -**Python version used:** - -**Output of `uname -a` or `ver`:** - -## Additional information - -Provide any and all other information which might be relevant to the issue. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index ffed6958..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Question - url: https://stackoverflow.com/questions/ask?tags=timefold - about: Ask a question about how to use Timefold Solver. - - name: Discussions - url: https://github.com/TimefoldAI/timefold-solver/discussions/new?category=general - about: Open Community discussions related to Timefold Solver. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index faa1a6da..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project. -title: 'Feat: ' -labels: ["enhancement", "process/needs triage"] -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a13bd381..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: 2 -updates: -- package-ecosystem: maven - directory: "/" - schedule: - interval: weekly - time: '05:00' - open-pull-requests-limit: 10 - target-branch: "main" - commit-message: - prefix: "deps: " - -- package-ecosystem: pip - directory: "/" - schedule: - interval: weekly - time: '05:00' - open-pull-requests-limit: 10 - target-branch: "main" - commit-message: - prefix: "deps: " - -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: weekly - time: '05:00' # Otherwise it picks a random time. diff --git a/.github/scripts/change_versions.sh b/.github/scripts/change_versions.sh deleted file mode 100755 index 1bd274c6..00000000 --- a/.github/scripts/change_versions.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Expects the following environment variables to be set: -# $NEW_VERSION (Example: "1.2.0") -# $NEW_VERSION_PYTHON (Example: "1.2.0a0") - -# This will fail the Maven build if the version is not available. -# Thankfully, this is not the case (yet) in this project. -# If/when it happens, this needs to be replaced by a manually provided version, -# as scanning the text of the POM would be unreliable. -echo " New version: $NEW_VERSION" -echo " New Python Version: $NEW_VERSION_PYTHON" -mvn clean install -Dquickly -mvn versions:update-parent "-DparentVersion=$NEW_VERSION" -DskipResolution=true -DallowSnapshots=true -DgenerateBackupPoms=false -mvn versions:update-child-modules -DallowSnapshots=true -DgenerateBackupPoms=false -sed -i "s/^timefold_solver_python_version.*=.*/timefold_solver_python_version = '$NEW_VERSION_PYTHON'/" setup.py -git commit -am "build: switch to version $NEW_VERSION_PYTHON" \ No newline at end of file diff --git a/.github/workflows/downstream_python_enterprise.yml b/.github/workflows/downstream_python_enterprise.yml deleted file mode 100644 index 417bd544..00000000 --- a/.github/workflows/downstream_python_enterprise.yml +++ /dev/null @@ -1,161 +0,0 @@ -# Tests PRs on multiple operating systems and Python/Java versions -name: Downstream - Timefold Solver Enterprise for Python - -on: - # Enables the workflow to run on PRs from forks; - # token sharing is safe here, because enterprise is a private repo and therefore fully under our control. - pull_request_target: - branches: [ main, '*.x' ] - types: - - opened - - reopened - - synchronize - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -defaults: - run: - shell: bash - -jobs: - build: - concurrency: - group: downstream-enterprise-python-${{ github.event_name }}-${{ github.head_ref }} - cancel-in-progress: true - timeout-minutes: 120 - runs-on: ubuntu-latest - - steps: - - name: Check out repository code - uses: actions/checkout@v4 - with: - path: './timefold-solver-python' - - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists - id: checkout-solver - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-solver - ref: ${{ github.head_ref }} - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver - ref: main - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver - env: - BLESSED_REPO: "timefold-solver" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver - run: .github/scripts/prevent_stale_fork.sh - - # Clone timefold-solver-enterprise - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists - id: checkout-solver-enterprise - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: ${{ github.head_ref }} - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver-enterprise.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: main - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver-enterprise - env: - BLESSED_REPO: "timefold-solver-enterprise" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver-enterprise - run: ../timefold-solver/.github/scripts/prevent_stale_fork.sh - - # Clone timefold-solver-python-enterprise - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver-enterprise-python (PR) # Checkout the PR branch first, if it exists - id: checkout-solver-enterprise-python - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-enterprise-python - ref: ${{ github.head_ref }} - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise-python - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver-python-enterprise-python (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver-enterprise-python.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-enterprise-python - ref: main - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise-python - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver-enterprise-python - env: - BLESSED_REPO: "timefold-solver-enterprise-python" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver-enterprise-python - run: ../timefold-solver/.github/scripts/prevent_stale_fork.sh - - # Build and test - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' - cache: 'maven' - # Need to install all Python versions in the same run for tox - - name: Python 3.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - name: Install tox - run: - python -m pip install --upgrade pip - pip install tox build - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - run: mvn -B -Dquickly clean install - - name: Quickly Build timefold-solver-enterprise - working-directory: ./timefold-solver-enterprise - shell: bash - run: mvn -B -Dquickly clean install - - name: Build with Maven to install parent poms for python build - working-directory: ./timefold-solver-python - run: mvn -B --fail-at-end clean install - - name: Build timefold solver python - working-directory: ./timefold-solver-python - run: python -m build - - name: Run tox on timefold solver enterprise python test suite - working-directory: ./timefold-solver-enterprise-python - env: - PIP_FIND_LINKS: ${{ github.workspace }}/timefold-solver-python/dist - run: tox diff --git a/.github/workflows/downstream_python_quickstarts.yml b/.github/workflows/downstream_python_quickstarts.yml deleted file mode 100644 index af22fe6f..00000000 --- a/.github/workflows/downstream_python_quickstarts.yml +++ /dev/null @@ -1,121 +0,0 @@ -# Tests PRs on multiple operating systems and Python/Java versions -name: Downstream - Timefold Solver for Python Quickstarts - -on: - pull_request: - types: [opened, synchronize, reopened, labeled] - branches: - - main - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - - '.ci/**' - -defaults: - run: - shell: bash - -jobs: - test-build: - concurrency: - group: pull_request_python_quickstarts-${{ github.event_name }}-${{ github.head_ref }}-${{ matrix.os }}-${{ matrix.java-version }}-${{ matrix.python-version }} - cancel-in-progress: true - strategy: - matrix: - os: [ ubuntu-latest ] - java-version: [ 17 ] # Only the first supported LTS; already too many jobs here. - # TODO: Add Python 3.10 once employee scheduling and school timetabling support it - python-version: ['3.11', '3.12'] - fail-fast: false - runs-on: ${{ matrix.os }} - - steps: - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists - id: checkout-solver - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-solver - ref: ${{ github.head_ref }} - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver - ref: main - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver - env: - BLESSED_REPO: "timefold-solver" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver - run: .github/scripts/prevent_stale_fork.sh - - - name: Check out repository code - uses: actions/checkout@v4 - with: - path: './timefold-solver-python' - - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-quickstarts (PR) # Checkout the PR branch first, if it exists - id: checkout-quickstarts - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-quickstarts - ref: ${{ github.head_ref }} - path: ./timefold-quickstarts - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-quickstarts (development) # Checkout the development branch if the PR branch does not exist - if: steps.checkout-solver-quickstarts.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-quickstarts - ref: development - path: ./timefold-quickstarts - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-quickstarts - env: - BLESSED_REPO: "timefold-quickstarts" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'development' }} - shell: bash - working-directory: ./timefold-quickstarts - run: ../timefold-solver/.github/scripts/prevent_stale_fork.sh - - # Build and test - - name: "Setup Java and Maven" - uses: actions/setup-java@v4 - with: - java-version: ${{matrix.java-version}} - distribution: 'temurin' - cache: 'maven' - - name: Python Setup - uses: actions/setup-python@v5 - with: - python-version: ${{matrix.python-version}} - cache: 'pip' - cache-dependency-path: | - **/setup.py - - name: Install build - run: - python -m pip install --upgrade pip - pip install build - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - run: mvn -B -Dquickly -DskipTests clean install - - name: Build timefold-solver-python - working-directory: ./timefold-solver-python - run: python -m build - - name: Build and test timefold-quickstarts - working-directory: ./timefold-quickstarts - env: - TIMEFOLD_SOLVER_PYTHON_DIST: "${{ github.workspace }}/timefold-solver-python/dist" - run: .github/scripts/run_python_tests.sh diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml deleted file mode 100644 index dfebc757..00000000 --- a/.github/workflows/pull_request.yml +++ /dev/null @@ -1,95 +0,0 @@ -# Tests PRs on multiple operating systems and Python/Java versions -name: Test Build - -on: - pull_request: - types: [opened, synchronize, reopened, labeled] - branches: - - main - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - - '.ci/**' - -defaults: - run: - shell: bash - -jobs: - test-build: - strategy: - matrix: - os: [ ubuntu-latest ] - java-version: [ 17, 21, 22 ] - fail-fast: false - runs-on: ${{ matrix.os }} - - steps: - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists - id: checkout-solver - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-solver - ref: ${{ github.head_ref }} - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver - ref: main - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver - env: - BLESSED_REPO: "timefold-solver" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver - run: .github/scripts/prevent_stale_fork.sh - - - name: Check out repository code - uses: actions/checkout@v4 - with: - path: './timefold-solver-python' - - # Build and test - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: ${{ matrix.java-version }} - distribution: 'temurin' - cache: 'maven' - # Need to install all Python versions in the same run for tox - - name: Python 3.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - name: Install tox - run: - python -m pip install --upgrade pip - pip install tox pytest - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - run: mvn -B -Dquickly clean install - - name: Build with Maven to install parent poms for python build - working-directory: ./timefold-solver-python - run: mvn -B --fail-at-end clean install - - name: Run tox on timefold solver python test suite - working-directory: ./timefold-solver-python - run: python -m tox - - name: Run tox on jpyinterpreter test suite - working-directory: ./timefold-solver-python/jpyinterpreter - run: python -m tox diff --git a/.github/workflows/release-changelog-template.md b/.github/workflows/release-changelog-template.md deleted file mode 100644 index 9ab8260c..00000000 --- a/.github/workflows/release-changelog-template.md +++ /dev/null @@ -1,28 +0,0 @@ -**!!! REMOVE THIS !!!** - -**!!! SUMMARIZE THE RELEASE HERE !!!** - -**!!! REMOVE THIS !!!** - -# Changelog - -{{changelogChanges}} -{{changelogContributors}} - -_Timefold Solver Community Edition_ is an open source project, and you are more than welcome to contribute as well! -For more, see [Contributing](https://github.com/TimefoldAI/timefold-solver/blob/main/CONTRIBUTING.adoc). - -Should your business need to scale to truly massive data sets or require enterprise-grade support, -check out [_Timefold Solver Enterprise Edition_](https://timefold.ai/pricing). - -# How to use Timefold Solver in Python - -To see Timefold Solver in action, check out [the quickstarts](https://github.com/TimefoldAI/timefold-quickstarts). - -Add `timefold=={{projectVersion}}` to your `requirements.txt` or `pip install timefold=={{projectVersion}}` file to get started. - -# Additional notes - -The changelog and the list of contributors above are automatically generated. -It excludes contributions to certain areas of the repository, such as CI and build automation. -This is done for the sake of brevity and to make the user-facing changes stand out more. diff --git a/.github/workflows/release-pr-body.md b/.github/workflows/release-pr-body.md deleted file mode 100644 index fa0844c1..00000000 --- a/.github/workflows/release-pr-body.md +++ /dev/null @@ -1,13 +0,0 @@ -At this point, the release of _Timefold Solver Community Edition_ for Python is ready to be published. -Artifacts have been uploaded to PyPI. -Release branch has been created. - -To finish the release of _Timefold Solver Community Edition_ for Python, -please follow the steps below in the given order: - -1. [ ] [Undraft the release](https://github.com/TimefoldAI/timefold-solver-python/releases) on Github. -2. [ ] Merge this PR. -3. [ ] Delete the branch that this PR is based on. (Typically a button appears on this page once the PR is merged.) - -Note: If this is a dry run, -none of the above applies and this PR should not be merged. \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 7e9b700b..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,143 +0,0 @@ -name: Release -on: - workflow_dispatch: - inputs: - version: - description: 'Community Edition version (e.g. 1.0.0)' - required: true - pythonVersionSuffix: - description: 'What suffix to append to the Python version (ex: b0 for beta release)' - required: true - default: b0 - sourceBranch: - description: 'Branch to cut the release from' - default: main - required: true - releaseBranch: - description: 'Release branch to create (e.g. 1.0.x for version 1.0.0; once created, branch protection rules apply)' - default: dry_run - required: true - dryRun: - description: 'Do a dry run? (true or false)' - default: true - required: true -jobs: - build: - env: - MAVEN_ARGS: "--no-transfer-progress --batch-mode" - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/timefold - permissions: - contents: write # IMPORTANT: required for action to create release branch - pull-requests: write # IMPORTANT: so release PR can be created - id-token: write # IMPORTANT: mandatory for trusted publishing - steps: - - name: Print inputs to the release workflow - run: echo "${{ toJSON(github.event.inputs) }}" - - name: Checkout the relevant timefold-solver tag - uses: actions/checkout@v4 - with: - repository: "TimefoldAI/timefold-solver" - path: "./timefold-solver" - fetch-depth: 0 - ref: v${{ github.event.inputs.version }} - - - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Set up Maven - uses: stCarolas/setup-maven@v5 - with: - maven-version: 3.9.3 - - - name: Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - - name: Install Pip and build - run: - python -m pip install --upgrade pip - pip install build - - # Needed so mvn versions:set in the Python repo can update - # the version in its pom.xml - - name: Build the upstream release tag as 999-SNAPSHOT - working-directory: "./timefold-solver" - run: | - mvn versions:set -DnewVersion=999-SNAPSHOT - mvn -Dquickly install - cd .. - rm -rf timefold-solver - - - name: Checkout timefold-solver-python - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.sourceBranch }} - - - name: Create release branch and switch to it - run: | - git config user.name "Timefold Release Bot" - git config user.email "release@timefold.ai" - git checkout -b ${{ github.event.inputs.releaseBranch }} - - # We skip tests in dry run, to make the process faster. - # Technically, this goes against the main reason for doing a dry run; to eliminate potential problems. - # But unless something catastrophic happened, PR checks on source branch already ensured that all tests pass. - # We also do not use versions:set, because we'd have to have the SNAPSHOT version built from somewhere, - # and at this point in the release, there is no upstream branch anywhere that would have this version anymore. - - name: Set release version and build release - run: | - export NEW_VERSION=${{ github.event.inputs.version }} - export NEW_VERSION_PYTHON=${{ github.event.inputs.version }}${{ github.event.inputs.pythonVersionSuffix }} - .github/scripts/change_versions.sh - python -m build - - # JReleaser requires the release branch to exist, so we need to push it before releasing. - # Once this is pushed, branch protection rules apply. - # So if any of the subsequent steps should fail, the release branch is there to stay; cannot be deleted. - # To minimize that chance, do a dry run first, with a branch named in a way that the protection rules don't apply. - - name: Push release branch to Git - run: | - git push origin ${{ github.event.inputs.releaseBranch }} - - - name: Run JReleaser - uses: jreleaser/release-action@v2 - env: - JRELEASER_DRY_RUN: ${{ github.event.inputs.dryRun }} - JRELEASER_PROJECT_VERSION: ${{ github.event.inputs.version }}-beta - JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} - JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} - JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} - JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} - - - name: JReleaser release output - uses: actions/upload-artifact@v4 - if: always() - with: - name: jreleaser-release - path: | - out/jreleaser/trace.log - out/jreleaser/output.properties - - - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - if: ${{ github.event.inputs.dryRun == 'false' }} - - # Pull Request will be created with the changes and a summary of next steps. - - name: Put back the 999-SNAPSHOT version on the release branch - run: | - git checkout -B ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot - export NEW_VERSION="999-SNAPSHOT" - export NEW_VERSION_PYTHON="999-dev0" - .github/scripts/change_versions.sh - git push origin ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot - gh pr create --reviewer triceo,Christopher-Chianelli --base ${{ github.event.inputs.releaseBranch }} --head ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot --title "build: move back to 999-SNAPSHOT" --body-file .github/workflows/release-pr-body.md - env: - GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 827ad885..00000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,114 +0,0 @@ -# Runs the SonarCloud analysis of the Timefold Solver Python main branch after a PR is merged. -name: SonarCloud Analysis - -on: - push: - branches: - - main - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - - '.ci/**' - pull_request_target: # This workflow will be triggered by the opening, reopening, or updating of a PR, and the first run will not require approval. - types: - - opened - - reopened - - synchronize - -defaults: - run: - shell: bash - -jobs: - sonarcloud-analysis: - runs-on: ubuntu-latest - - steps: - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists - id: checkout-solver - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-solver - ref: ${{ github.head_ref }} - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver - ref: main - path: ./timefold-solver - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Prevent stale fork of timefold-solver - env: - BLESSED_REPO: "timefold-solver" - BLESSED_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }} - shell: bash - working-directory: ./timefold-solver - run: .github/scripts/prevent_stale_fork.sh - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version - path: './timefold-solver-python' - - # Build and test - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v4 - with: - path: ~/.m2 - 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.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - name: Install tox - run: - python -m pip install --upgrade pip - pip install tox coverage pytest pytest-cov - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - run: mvn -B -Dquickly clean install - - name: Build with Maven to measure code coverage - working-directory: ./timefold-solver-python - run: mvn -B --fail-at-end clean install -Prun-code-coverage - - name: Get JaCoCo Agent - working-directory: ./timefold-solver-python - run: mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=org.jacoco:org.jacoco.agent:0.8.11:jar:runtime -Ddest=target/jacocoagent.jar - - name: Run tox to measure timefold solver python code coverage from Python tests - working-directory: ./timefold-solver-python - run: python -m tox -- --cov=timefold --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=./target/jacocoagent.jar - - name: Run tox to measure jpyinterpreter code coverage from Python tests - working-directory: ./timefold-solver-python/jpyinterpreter - run: python -m tox -- --cov=jpyinterpreter --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=../target/jacocoagent.jar --jacoco-output=../target/jacoco.exec - - name: Run SonarCloud analysis - working-directory: ./timefold-solver-python - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B -Psonarcloud-analysis validate org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.organization=timefold -Dsonar.projectKey=TimefoldAI_timefold-solver-python -Dsonar.host.url=https://sonarcloud.io -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} -Dsonar.scm.revision=${{ github.event.pull_request.head.sha }} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d8b2495a..00000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.1/apache-maven-3.9.1-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 9dc78b3f..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,12 +0,0 @@ -graft .mvn -include mvnw mvnw.cmd pom.xml create-stubs.py - -include timefold-solver-python-core/pom.xml -graft timefold-solver-python-core/src/main/java -graft timefold-solver-python-core/src/main/resources -graft timefold-solver-python-core/src/test/java - -include jpyinterpreter/pom.xml -graft jpyinterpreter/src/main/java -graft jpyinterpreter/src/main/resources -graft jpyinterpreter/src/test/java diff --git a/README.adoc b/README.adoc new file mode 100644 index 00000000..dc9ce5f6 --- /dev/null +++ b/README.adoc @@ -0,0 +1,4 @@ += PROJECT MOVED + +This project has been merged into the https://github.com/TimefoldAI/timefold-solver[Timefold Solver repo]. +All future development will be done there. diff --git a/README.md b/README.md deleted file mode 100644 index a984b9a6..00000000 --- a/README.md +++ /dev/null @@ -1,186 +0,0 @@ -![Timefold Logo](https://raw.githubusercontent.com/TimefoldAI/timefold-solver/main/docs/src/modules/ROOT/images/shared/timefold-logo.png) - -# Timefold Solver for Python - -[![PyPI](https://img.shields.io/pypi/v/timefold?style=for-the-badge& "PyPI")](https://pypi.org/project/timefold/) -[![License](https://img.shields.io/github/license/TimefoldAI/timefold-solver-python?style=for-the-badge&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0) -[![JVM support](https://img.shields.io/badge/Java-17+-brightgreen.svg?style=for-the-badge)](https://sdkman.io) -[![Python support](https://img.shields.io/badge/Python-3.10+-brightgreen.svg?style=for-the-badge)](https://www.python.org/downloads) -[![Commit Activity](https://img.shields.io/github/commit-activity/m/TimefoldAI/timefold-solver-python?label=commits&style=for-the-badge)](https://github.com/TimefoldAI/timefold-solver-python/pulse) - -[![Stackoverflow](https://img.shields.io/badge/stackoverflow-ask_question-orange.svg?logo=stackoverflow&style=for-the-badge)](https://stackoverflow.com/questions/tagged/timefold) -[![GitHub Discussions](https://img.shields.io/github/discussions/TimefoldAI/timefold-solver?style=for-the-badge&logo=github)](https://github.com/TimefoldAI/timefold-solver/discussions) -[![GitHub Issues](https://img.shields.io/github/issues/TimefoldAI/timefold-solver-python?style=for-the-badge&logo=github)](https://github.com/TimefoldAI/timefold-solver-python/issues) - -[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=TimefoldAI_timefold-solver-python&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=TimefoldAI_timefold-solver-python) -[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=TimefoldAI_timefold-solver-python&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=TimefoldAI_timefold-solver-python) -[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=TimefoldAI_timefold-solver-python&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=TimefoldAI_timefold-solver-python) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=TimefoldAI_timefold-solver-python&metric=coverage)](https://sonarcloud.io/summary/new_code?id=TimefoldAI_timefold-solver-python) - -Timefold Solver is *an AI constraint solver for Python* to optimize -the Vehicle Routing Problem, Employee Rostering, Maintenance Scheduling, Task Assignment, School Timetabling, -Cloud Optimization, Conference Scheduling, Job Shop Scheduling, Bin Packing and many more planning problems. - -Using Timefold Solver in Python is significantly slower than using Timefold Solver in [Java](https://github.com/TimefoldAI/timefold-solver) or Kotlin. - -## Get started with Timefold Solver for Python - -* [Read a Getting Started guide](https://timefold.ai/docs) -* [Clone the Quickstarts repository](https://github.com/TimefoldAI/timefold-quickstarts) - -## Requirements - -- [Install Python 3.10 or later.](https://www.python.org) -- [Install JDK 17 or later](https://adoptium.net) with the environment variable `JAVA_HOME` configured to the JDK installation directory. - For example, with [Sdkman](https://sdkman.io/): - ```shell - $ sdk install java - ``` - -## Build from source - -1. [Build the main branch of Timefold Solver for Java from source](https://github.com/TimefoldAI/timefold-solver?tab=readme-ov-file#build-from-source) -2. Install the repo - ```shell - $ pip install git+https://github.com/TimefoldAI/timefold-solver-python.git - ``` - -## Source code overview - -### Domain - -In Timefold Solver, the domain has three parts: - -- Problem Facts, which do not change. -- Planning Entities, which have one or more planning variables. -- Planning Solution, which define the facts and entities of the problem. - -#### Problem Facts - -Problem facts can be any Python class, which are used to describe unchanging facts in your problem: - -```python -from dataclasses import dataclass -from datetime import time - -@dataclass -class Timeslot: - id: int - day_of_week: str - start_time: time - end_time: time -``` - -#### Planning Entities - -To declare Planning Entities, use the `@planning_entity` decorator along with annotations: - -```python -from dataclasses import dataclass, field -from typing import Annotated -from timefold.solver.domain import planning_entity, PlanningId, PlanningVariable - -@planning_entity -@dataclass -class Lesson: - id: Annotated[int, PlanningId] - subject: str - teacher: str - student_group: str - timeslot: Annotated[Timeslot, PlanningVariable] = field(default=None) - room: Annotated[Room, PlanningVariable] = field(default=None) -``` - -- The `PlanningVariable` annotation is used to mark what fields the solver is allowed to change. - -- The `PlanningId` annotation is used to uniquely identify an entity object of a particular class. The same Planning Id can be used on entities of different classes, but the ids of all entities in the same class must be different. - -#### Planning Solution - -To declare the Planning Solution, use the `@planning_solution` decorator: - -```python -from dataclasses import dataclass, field -from typing import Annotated -from timefold.solver.domain import (planning_solution, ProblemFactCollectionProperty, ValueRangeProvider, - PlanningEntityCollectionProperty, PlanningScore) -from timefold.solver.score import HardSoftScore - -@planning_solution -@dataclass -class TimeTable: - timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider] - rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider] - lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty] - score: Annotated[HardSoftScore, PlanningScore] = field(default=None) -``` - -- The `ValueRangeProvider` annotation is used to denote a field that contains possible planning values for a `PlanningVariable`. - -- The`ProblemFactCollection` annotation is used to denote a field that contains problem facts. This allows these facts to be queried in your constraints. - -- The `PlanningEntityCollection` annotation is used to denote a field that contains planning entities. The planning variables of these entities will be modified during solving. - -- The `PlanningScore` annotation is used to denote the field that holds the score of the current solution. The solver will set this field during solving. - -### Constraints - -You define your constraints by using the ConstraintFactory: - -```python -from domain import Lesson -from timefold.solver.score import (Joiners, HardSoftScore, ConstraintFactory, - Constraint, constraint_provider) - -@constraint_provider -def define_constraints(constraint_factory: ConstraintFactory) -> list[Constraint]: - return [ - # Hard constraints - room_conflict(constraint_factory), - # Other constraints here... - ] - -def room_conflict(constraint_factory: ConstraintFactory) -> Constraint: - # A room can accommodate at most one lesson at the same time. - return ( - constraint_factory.for_each_unique_pair(Lesson, - # ... in the same timeslot ... - Joiners.equal(lambda lesson: lesson.timeslot), - # ... in the same room ... - Joiners.equal(lambda lesson: lesson.room)) - .penalize(HardSoftScore.ONE_HARD) - .as_constraint("Room conflict") - ) -``` -for more details on Constraint Streams, -see https://timefold.ai/docs/timefold-solver/latest/constraints-and-score/score-calculation. - -### Solve - -```python -from timefold.solver import SolverFactory -from timefold.solver.config import SolverConfig, TerminationConfig, ScoreDirectorFactoryConfig, Duration -from constraints import define_constraints -from domain import TimeTable, Lesson, generate_problem - -solver_config = SolverConfig( - solution_class=TimeTable, - entity_class_list=[Lesson], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=define_constraints - ), - termination_config=TerminationConfig( - spent_limit=Duration(seconds=30) - ) -) - -solver = SolverFactory.create(solver_config).build_solver() -solution = solver.solve(generate_problem()) -``` - -`solution` will be a `TimeTable` instance with planning -variables set to the final best solution found. - -## More information - -For a full API spec, visit [the Timefold Documentation](https://timefold.ai/docs/timefold-solver/latest). diff --git a/create-stubs.py b/create-stubs.py deleted file mode 100644 index 888d7d22..00000000 --- a/create-stubs.py +++ /dev/null @@ -1,20 +0,0 @@ -import pathlib -import jpype -import stubgenj - -jars = list(map(str, pathlib.Path('target/dependency').glob('**/*.jar'))) - -jpype.startJVM(classpath=jars, convertStrings=True) - -import jpype.imports # noqa -import ai.timefold.solver.core.api # noqa -import ai.timefold.solver.core.config # noqa -import ai.timefold.jpyinterpreter # noqa -import java.lang # noqa -import java.time # noqa -import java.util # noqa - -stubgenj.generateJavaStubs([java.lang, java.time, java.util, ai.timefold.solver.core.api, - ai.timefold.solver.core.config, ai.timefold.jpyinterpreter], - useStubsSuffix=True) - diff --git a/jpyinterpreter/.gitignore b/jpyinterpreter/.gitignore deleted file mode 100644 index cc3c0ab1..00000000 --- a/jpyinterpreter/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/dist -/*.egg-info -/target -/*-stubs - -# Eclipse, Netbeans and IntelliJ files -/.* -!.gitignore -!.dockerignore -!.mvn -/nbproject -/*.ipr -/*.iws -/*.iml diff --git a/jpyinterpreter/.mvn/wrapper/maven-wrapper.properties b/jpyinterpreter/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d8b2495a..00000000 --- a/jpyinterpreter/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.1/apache-maven-3.9.1-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/jpyinterpreter/MANIFEST.in b/jpyinterpreter/MANIFEST.in deleted file mode 100644 index 5b1a72ba..00000000 --- a/jpyinterpreter/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -graft .mvn -graft src/main/java -graft src/main/resources -include mvnw mvnw.cmd pom.xml \ No newline at end of file diff --git a/jpyinterpreter/README.adoc b/jpyinterpreter/README.adoc deleted file mode 100644 index 14635231..00000000 --- a/jpyinterpreter/README.adoc +++ /dev/null @@ -1,66 +0,0 @@ -= Python to Java Translator - -This module contains the code needed to translate Python's -bytecode into equivalent Java bytecode. This allows Java -functions to use Python functions without incurring a large -overhead cost on each function call for changing context from -Java to Python (and then Python to Java). This is mostly relevant -for small functions, such as those that simply return an attribute -or perform a small calculation. For small functions, the -performance increase is around 600%. As functions get larger, -the overhead cost matters less, causing CPython to outperform -the Java version. - -== Building - -Install the python build module, then run the following command: - -```bash -python -m build -``` - -== Running - -. Install the built `javapython` package into a virtual -environment: -+ -```bash -python -m venv venv -. venv/bin/activate -pip install dist/jpyinterpreter-*-py3-none-any.whl -``` - -. Initialize the translator: -+ -```python -import jpype.imports -import jpyinterpreter - -jpyinterpreter.init(path=['my-java-jar.jar']) -``` -+ -This will start the JVM used by the Java translator and load jars -specified by the `path` parameter. - -. Translate a Python function to Java: -+ -```python -from java.util.function import Function - -def my_function(arg): - return arg + 1 - -translated_function = javapython.translate_python_bytecode_to_java_bytecode(my_function, Function) -``` -+ -The first parameter is the Python function you want to translate, -the second parameter is the Java class the function implements. -+ -. Use the translated function like a normal Python function: -+ -```python -from org.acme import MyClass # A class defined in a Java jar - -translated_function(10) # 11 -MyClass.performALongOperation(translated_function) -``` \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/.gitignore b/jpyinterpreter/developer-docs/.gitignore deleted file mode 100644 index 45f84210..00000000 --- a/jpyinterpreter/developer-docs/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -/node_modules -/build -/dist -/*.egg-info -/target -/*-stubs - -# Eclipse, Netbeans and IntelliJ files -/.* -!.gitignore -!.dockerignore -!.mvn -/nbproject -/*.ipr -/*.iws -/*.iml diff --git a/jpyinterpreter/developer-docs/src/antora-template.yml b/jpyinterpreter/developer-docs/src/antora-template.yml deleted file mode 100644 index 92f12126..00000000 --- a/jpyinterpreter/developer-docs/src/antora-template.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This file is a template for antora.yml, which is created during a release by: -# 1. running maven that substitutes the properties into this file -# 2. running update_antora_yml.sh that copies the result of the previous step to src/antora.yml -# -# The goal is to have the antora.yml containing correct attributes available in the release branch before -# the optapy-website is refreshed. -name: docs -title: JPyInterpreter Developer Guide ${version.jpyinterpreter} -version: latest -asciidoc: - attributes: - jpyinterpreter-version: ${version.jpyinterpreter} - java-version: ${maven.compiler.release} -nav: - - modules/ROOT/nav.adoc \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/antora.yml b/jpyinterpreter/developer-docs/src/antora.yml deleted file mode 100644 index 008895c1..00000000 --- a/jpyinterpreter/developer-docs/src/antora.yml +++ /dev/null @@ -1,7 +0,0 @@ -# This file is a placeholder that is replaced by antora-template.yml during a release. -# Please note that some AsciiDoc attributes might be missing if you build the docs from this branch. -name: docs -title: JPyInterpreter Developer Guide -version: latest -nav: - - modules/ROOT/nav.adoc \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.png b/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.png deleted file mode 100644 index 71b234d5..00000000 Binary files a/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.png and /dev/null differ diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.svg b/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.svg deleted file mode 100644 index 0f8293cb..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/images/jpyinterpreter-architecture/jpyinterpreter-architecture.svg +++ /dev/null @@ -1,685 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jvm_setup.py - - - - - python_to_java_bytecode_translator.py - - - Python - - - PythonBytecodeToJavaBytecodeTranslator - On Startup - To compile function - - PythonGeneratorTranslator - - FlowGraph - - PythonBytecodeToJavaBytecodeTranslator - - Opcode - - PythonBytecodeToJavaBytecodeTranslator - - Opcode - - Implementor - - - - - - - - - - - - Converts the Python function into a PythonCompiledFunction and pass it to... - - Calculates Initial StackMetadata of function and send it to... - Sends generator functions to... - Break generator functions into parts and send each part to... - Calculates Initial StackMetadata of function and send it to... - Computes the next StackMetadata for the prior StackMetadata using... - Returns the StackMetadata of each Opcode to... - Provides StackMetadata and FunctionMetadata to each instance... - Sends the StackMetadata, FunctionMetadata, and other arguments to a... - Generate the Java bytecode to implement the operation and return to... - - Java - - - diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/nav.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/nav.adoc deleted file mode 100644 index 411bd914..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/nav.adoc +++ /dev/null @@ -1,6 +0,0 @@ -* xref:jpyinterpreter-introduction/jpyinterpreter-introduction.adoc[leveloffset=+1] -* xref:stack-machines/stack-machines.adoc[leveloffset=+1] -* xref:python-function-structure/python-function-structure.adoc[leveloffset=+1] -* xref:opcodes/opcodes.adoc[leveloffset=+1] -* xref:java-bytecode/java-bytecode.adoc[leveloffset=+1] -* xref:jpyinterpreter-architecture/jpyinterpreter-architecture.adoc[leveloffset=+1] \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/.index.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/.index.adoc deleted file mode 100644 index 541b06c0..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/.index.adoc +++ /dev/null @@ -1,18 +0,0 @@ -= JPyInterpreter Developer Guide -:doctype: book -:imagesdir: . -:toc: left -:toclevels: 3 -:sectnums: -:sectanchors: -:sectlinks: -:sectnumlevels: 5 -:icons: font -:docinfo: - -include::jpyinterpreter-introduction/jpyinterpreter-introduction.adoc[leveloffset=+1] -include::stack-machines/stack-machines.adoc[leveloffset=+1] -include::python-function-structure/python-function-structure.adoc[leveloffset=+1] -include::opcodes/opcodes.adoc[leveloffset=+1] -include::java-bytecode/java-bytecode.adoc[leveloffset=+1] -include::jpyinterpreter-architecture/jpyinterpreter-architecture.adoc[leveloffset=+1] \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/java-bytecode/java-bytecode.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/java-bytecode/java-bytecode.adoc deleted file mode 100644 index d3111c92..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/java-bytecode/java-bytecode.adoc +++ /dev/null @@ -1,137 +0,0 @@ -[[javaBytecode]] -= Java Bytecode - -== Java Virtual Machine Basic Properties - -The Java Virtual Machine is a stack based virtual machine. -It has the following properties: - -- Parameters and local variables are stored in slots, with parameters being first. -This also includes the `this` parameter, which is always in the first slot for instance methods. -Compilers can also introduce synthetic local variables that do not correspond to any variable in the source. - -- On exception, the current stack is cleared and the thrown exception is pushed to the stack. - -- Each instruction must have a consistent prior stack size. -This means if an instruction is the target of multiple instructions, each instruction that have it as a target must have the same post stack size. -For instance, this is invalid: -+ -``` -LDC 1 -ILOAD 1 -if_icmpeq [if-equal] -ILOAD 2 -[if-equal] -RETURN -``` -+ -Because RETURN have an inconsistent prior stack size: if the `if_icmpeq` branch is taken, the expected stack size is 0; otherwise, the expected stack size is 1 (because `ILOAD 2` push an `int` to the stack). -The bytecode below is valid: -+ -``` -LDC 1 -ILOAD 1 -if_icmpeq [if-equal] -ILOAD 2 -POP -[if-equal] -RETURN -``` -+ -since the expected stack size prior to RETURN is 0 for both branches of `if_icmpeq`. - -- The stack is typed, and will cause verification errors if we were to try to call a method with invalid types on the stack. -For instance, this is invalid: -+ -``` -ILOAD 1 -INVOKESTATIC [MyClass.method(float) -> float] -``` -+ -since we are calling a method that expects a `float` with an `int`. -This is also invalid: -+ -``` -ALOAD 1 [Object] -INVOKEVIRTUAL [MyClass.method() -> void] -``` -+ -Since we are trying to call a `MyClass` method on `Object` (which may or may not be a `MyClass` instance). -To fix it, we need to cast it first: -+ -``` -ALOAD 1 [Object] -CHECKCAST [MyClass] -INVOKEVIRTUAL [MyClass.method() -> void] -``` - -== Java Virtual Machine instruction listing - -A full explanation of the Java Virtual Machine's instruction set can be viewed at the https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html[Oracle JVM Documentation] and a summary can be read on the https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions[List of Java bytecode instruction wikipedia article]. - -== Generating Java bytecode instructions - -Java bytecode instructions are generated with the https://asm.ow2.io/[ASM library]. -Functions are created by creating a `JavaPythonClassWriter` (which is a `ClassWriter` that overrides `getCommonSuperClass` to prevent `TypeNotPresent` errors) instance, -getting the MethodVisitor for its https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8[functional interface method], -adapting the method visitor with `MethodVisitorAdapters.adapt` (which sorts try blocks for us and gives us better error messages), -and then generating the Java bytecode using that method visitor. -It looks like this in the code: - -```java -ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); -classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), - new String[] { methodDescriptor.getDeclaringClassInternalName() }); - -// ... Create fields and constructor ... - -MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, - methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), - null, - null); - -MethodVisitorAdapters.adapt(methodVisitor, methodDescriptor); - -// ... Visit parameters ... - -methodVisitor.visitCode(); - -// ... Create bytecode ... - -methodVisitor.visitMaxs(0, 0); -methodVisitor.visitEnd(); -``` - -To generate a particular opcode instruction, identify what kind of instruction it is: - -- Instructions that either conditionally or unconditionally jump to a label are created with `visitJumpInsn`, which take the label to either conditionally or unconditionally jump to. - -- Instructions that load parameters or local variables are created with `visitVarInsn`, which take an `int` to identify which slot to load the parameter/local variable from. -We can use the `LocalVariableHelper` on `StackMetadata` to obtain the slot number of parameters and local variables. - -- Instructions that operate on the stack are created with `visitInsn` (for instance, popping a value or adding the top two `int` on the stack). No parameter (beside the opcode to generate) is needed for these opcodes. - -- Instructions that call methods are created with `visitMethodInsn`, which takes. - -** The internal name of the declaring class. -This can be retrieved with `Type.getInternalName`. - -** The method name. - -** The method descriptor string that describe the parameter and return types of the method. -This can be received with `Type.getMethodDescriptor(Type returnType, Type... parameterTypes)` (the `Type` object of a class can be received with `Type.getType`). - -** A boolean that is true if the method is defined on an interface, false otherwise. - -- Instructions that read or set fields are created with `visitFieldInsn`, which takes - -** The internal name of the class this field belongs to. This can be retrieved with `Type.getInternalName`. - -** The field's name. - -** The field's type descriptor string. This can be received with `Type.getDescriptor`. - -- Instructions that operate on types (i.e. `CHECKCAST`, `INSTANCEOF` and `NEW`) are generated with `visitTypeInsn`. It takes the internal name of the class to cast/instanceof/new as an argument (this can be retrieved with `Type.getInternalName`). - -- The instruction that load constants onto the stack can be generated with `visitLDC`; it takes the constant to load (which *must* be a primitive type, Type object, or a String) as its only argument. diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-architecture/jpyinterpreter-architecture.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-architecture/jpyinterpreter-architecture.adoc deleted file mode 100644 index 6ac731c7..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-architecture/jpyinterpreter-architecture.adoc +++ /dev/null @@ -1,125 +0,0 @@ -[[jpyinterpreterArchitecture]] -= JPyInterpreter Architecture - -The architecture of JPyInterpreter is composed of several components spread across Python and Java. -The Python components are: - -- `jvm_setup.py`, which sets up the JVM and the hooks JPyInterpreter uses to communicate with CPython (to look up packages, call native methods, and converting CPython object to Java object and vice-versa). - -- `python_to_java_bytecode_translator.py`, which acts as the Python's frontend to JPyInterpreter. -Users supply a CPython function to translate (and an optional Java functional interface it implements) to `translate_python_bytecode_to_java_bytecode`, which firsts converts that function to a `PythonCompiledFunction`, and then passes it to `PythonBytecodeToJavaBytecodeTranslator` to translate the function. -It also provides the `translate_python_class_to_java_class` function, which is given a user supplied CPython class, converts it to a `PythonCompiledClass`, and passes it to `PythonClassTranslator` to translate the class. - -The Java components are: - -- `PythonBytecodeToJavaBytecodeTranslator`, which acts as the entrypoint for function translation. -It is responsible for: - -** Setting up the `JavaPythonClassWriter` and `MethodVisitor` used for bytecode generation. - -** Creating and setting fields on the generated class objects (See <<_pythonbytecodetojavabytecodetranslator>> for details). - -** Do the leg work of moving/translating Java parameters (ex: `int`) into `PythonLikeObject`. - -** Setup cells variables. - -** Delegating to `PythonGeneratorTranslator` when it detects the function being translated is a generator. - -** Using `FlowGraph` to calculate the `StackMetadata` of each instruction. - -** Calling the `implement` method on every opcode in the `Opcode` list with the `StackMetadata` for the opcode and the `FunctionMetadata` for the overall function. - -- `PythonGeneratorTranslator` is like `PythonBytecodeToJavaBytecodeTranslator` but for generators. -It breaks a single generator function into multiple `advance` functions, and generates each `advance` function bytecode independently. - -- `FlowGraph`, which calculates the `StackMetadata` that corresponds to each `Opcode`. -It is responsible for unifying the `StackMetadata` from all jump sources for each `Opcode` that is a jump target. -For instance, if two sources with the same target have post `StackMetadata` of `... int` and `... bool` respectively, `FlowGraph` will unify that to `... int` (since `bool` is a subclass of `int`, for better or worse). - -- `StackMetadata` stores metadata about the stack and local variables. -Each `Opcode` get its own `StackMetadata` instance. -It is mostly used to perform optimizations; for instance, if we detect the top two items on the stack are `int` and `int` for the `BINARY_ADD` instruction, we can change the (normally complex due to Python semantics) `BINARY_ADD` bytecode into a single method call. - -- `FunctionMetadata` stores metadata about the function (for instance, the `MethodVisitor` to use to generate bytecode). Each `Opcode` gets the same `FunctionMetadata` instance. - -- `Opcode` are the interface between CPython opcodes and the `Implementors`. -Each describe a particular operation, and usually (but not always) correspond to a CPython opcode. -Some CPython opcodes map to the same `Opcode` implementation. - -- `Implementors` are responsible for generating the Java bytecode corresponding to CPython bytecode. -They can be found in the `implementors` package. - -The overall process of compiling a function looks like this: - -image::jpyinterpreter-architecture/jpyinterpreter-architecture.png[A diagram showing how JPyInterpreter classes interact] - - -== Types - -The builtin types for JPyInterpreter can be found in the `types` package. -They all implement `PythonLikeObject`, the interface the bytecode uses to represent arbitrary objects. -If type flow analysis determines a more specific type can be used (via `StackMetadata`), the more specific type is used directly instead. -`PythonLikeObject` have several methods: - -- `\\__getAttributeOrNull`: returns the attribute with the given name if it exists, otherwise returns null. -This is NOT `__getattribute\__` (which is implemented by `$method$\__getattribute\__` instead). -This is more akin to `self.\__dict\__[attribute]`. -The default `$method$\__getattribute__` uses it to get the attribute (with additional magic to handle descriptors, see https://docs.python.org/3.11/howto/descriptor.html#invocation-from-an-instance[the Python descriptor tutorial] for more detail). - -- `__getAttributeOrError`: returns the attribute with the given name if it exists, otherwise raises `AttributeError`. -Used in bytecode generation to lookup methods on types. - -- `\\__setAttribute`: sets the attribute with the given name to the given value. -This is NOT `__setattr\__` (which is implemented by `$method$\__setattr\__` instead). -This is more akin to `self.\__dict\__[attribute] = value`. -The default `$method$\__setattr__` uses it to set the attribute. - -- `\\__deleteAttribute`: deletes the attribute with the given name. -This is NOT `__delattr\__` (which is implemented by `$method$\__delattr\__` instead). -This is more akin to `del self.\__dict\__[attribute]`. -The default `$method$\__delattr__` uses it to delete the attribute. - -- `__getType`: returns the type of the object. -Used to implement `type(object)`. - -- `__getGenericType`: returns the generic type of the object (ex: `list[int]`). -Used for typeflow analysis. - -- `$method$`: the builtin methods on every object in Python. -The `$method$` naming is to allow custom classes to override them (custom classes prefix method names with `$method$` to not clash with Java method names). - -== PythonBytecodeToJavaBytecodeTranslator - -The entrypoint for function translation, and the glue code for the many subsystems of the translator. -It is responsible for setting up the `JavaPythonClassWriter`, `MethodVisitor` and configuring the class' fields. -The fields it configures are: - -- `co_consts`: Static; a `List` that stores constants used in the bytecode. - -- `co_names`: Static; a `List` that stores names used in the bytecode. - -- `co_varnames`: Static; a `List` that stores variable names used in the bytecode. - -- `\\__globals__`: Static; a `Map` used to read and store globals. - -- `\\__spec_getter__`: Static; a `BiFunction>` that maps default arguments (which are per function) to an `ArgumentSpec` that can be used to set parameters. - -- `\\__defaults__`: Instance; a `PythonLikeTuple` that stores default positional arguments. - -- `\\__kwdefaults__`: Instance; a `PythonLikeDict` that stores default keyword arguments. - -- `\\__annotations__`: Instance; a `PythonLikeDict` that stores type annotations on the function. - -- `\\__closure__`: Instance; a `PythonLikeTuple` that stores the function's closure (i.e. the free variable cells). - -- `\\__qualname__`: Instance; a `PythonString` that stores the qualified name of the function. - -- `\\__spec__`: Instance; an `ArgumentSpec` that can be used to receive parameter (and correctly handle default arguments). - -- `\\__interpreter__`: Instance; the `PythonInterpreter` this function runs in (used to perform imports and lookup unknown globals). - -If a Python function cannot be translated for any reason (ex: native code), the following fields are also added: - -- `\\__code__`: Static; an opaque pointer to the function's CPython code object (used in the construct to make the wrapped CPython function). - -- `\\__function__`: Instance; a `PythonObjectWrapper` that wraps the CPython function (used to call the CPython function). \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-introduction/jpyinterpreter-introduction.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-introduction/jpyinterpreter-introduction.adoc deleted file mode 100644 index 5c26314f..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/jpyinterpreter-introduction/jpyinterpreter-introduction.adoc +++ /dev/null @@ -1,38 +0,0 @@ -[[jpyinterpreterIntroduction]] -= JPyInterpreter Introduction -// Redirect to this page from .../docs/jpyinterpreter/latest. -:page-aliases: ../index.adoc -:doctype: book -:sectnums: -:icons: font - -[[whatIsJpyinterpreter]] -== What is JPyInterpreter - -JPyInterpreter is a Python interpreter that runs in -the JVM that can be embedded in CPython to remove overhead -of calling Python code frequently from Java. This is mostly -relevant for small functions, where the overhead decreases -performance by as much as 100 times. - -It accomplishes this by creating Java class files from the -https://docs.python.org/3/glossary.html#term-bytecode[CPython bytecode], accessible https://docs.python.org/3/library/dis.html[via the dis module]. Both CPython Virtual Machine and the JVM are -https://en.wikipedia.org/wiki/Stack_machine[stack machines], meaning each opcode reads it parameters -and writes its results to a stack. As such, JPyInterpreter can also be thought of as a translator of CPython bytecode to Java bytecode. - -[[jpyinterpreterStructure]] -== JPyInterpreter's Structure - -JPyInterpreter's structure can be broken into several -components: - -- The Python frontend, which is given Python function and classes and returns their corresponding Java versions. - The Python frontend has minimal code, and is mostly responsible for passing the Python bytecode to Java and converting Python constants into Java constants. - -- The Java opcode parser, which is given Python bytecode instructions and needs to convert them to intermediary opcodes. - -- A typeflow analyzer, which is given the Python function signature and the intermediary opcodes, and returns the stack metadata associated with each opcode. The stack metadata is useful for optimizing opcodes, such as `GET_ATTR` (which typically need to go through a complicated dict lookup process, but if it is a known field, it can be implemented by a single `getfield` java bytecode instruction) - -- Implementors, which write the java bytecode instructions -to implement a given intermediary opcode with the given stack metadata and function metadata. - diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/opcodes/opcodes.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/opcodes/opcodes.adoc deleted file mode 100644 index 1ab75f85..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/opcodes/opcodes.adoc +++ /dev/null @@ -1,3843 +0,0 @@ -[[cpythonOpcodes]] -= CPython Opcode Instructions - -CPython compiles function into bytecode. -The opcodes for this bytecode are listed here and can also be found in `OpcodeIdentifier`. -CPython bytecode structure in not stable, and opcode behaviour may change between releases. - -== General Format - -Each opcode will be listed in the following format: - -``` -Opcode Name - -Python Versions - -Stack Prior: ... [expected stack state] -Stack After: ... [new stack state] - -Description of Opcode - -Example sources that generate the opcode -``` - -For instance, the `POP_TOP` opcode, which pop a single item off the top of the stack, would look like this: - -``` -POP_TOP - -Python Versions: ALL - -Stack Prior: ... item -Stack After: ... - -Pops a single item off the stack. -Used to discard return value of functions and to manipulate the stack. - -Example Sources: -my_fun() # POP_TOP is generated after the function call - # since the result of my_fun is unused. -``` - - -== Instruction Addressing - -Prior to 3.10, instructions are addressed by byte offsets. -So the first instruction would have address 0, the second instruction would have address 2, and the third instruction would have address 4. -After 3.10, instructions are addressed by instruction offsets. -So the first instruction would have address 0, the second instruction would have address 1, and the third instruction would have address 2. - -Absolute jumps go to the instruction with the address given by the argument. -So an absolute jump with argument 2 will go to the instruction at address 2 regardless of the jump's address. - -Relative jumps go to the instruction that are argument addresses away from it. -So a relative jump with argument 2 will go to the instruction at address 3 if the jump is at address 1, or 4 if the jump is at address 2. - - -[#_cell_variable_index] -== Cell Variable Index - -In Python 3.10 and below, the argument for `LOAD_CLOSURE`, `LOAD_CLASSDEREF`, `LOAD_DEREF`, `STORE_DEREF` and `DELETE_DEREF` correspond to the index of the variable name in the combined list `co_cellvars + co_freevars`. -For instance, if `co_cellvars = ['a', 'b']` and `co_freevars = ['c', 'd']`, the argument to read/modify/delete the variable corresponding to each name would be: - -- 'a': 0 -- 'b': 1 -- 'c': 2 -- 'd': 3 - -In Python 3.11, the indices for cell variables that do not correspond to parameters are offset by `co_varnames`. If a cell variable corresponds to a parameter, it uses the parameter index in `co_varnames` as its cell index. For instance, if `co_varnames = ['a', 'x', 'y']`, `co_cellvars = ['a', 'b']` and `co_freevars = ['c', 'd']`, the argument to read/modify/delete the variable corresponding to each name would be: - -- 'a': 0 -- 'b': 3 -- 'c': 4 -- 'd': 5 - -== Opcode Listing - -=== NOP - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Does absolutely nothing. -Exists purely to simplify bytecode generation. - -Example Sources: - -```python -pass # `pass` represent a NOP in Python -``` - -=== RESUME - -Python Versions: 3.11+ - -``` -Stack Prior: ... -Stack After: ... -``` - -A NOP opcode that CPython uses to perform internal tracing, debugging and optimization checks. -Generally put into places where the CPython interpreter enters a function (the start of every function and after yield statements). - -Example Sources: - -```python -def function(): - # A RESUME opcode is placed at the start of every - # function. - return None -``` - -```python -yield 10 # A RESUME opcode is placed after - # each YIELD_VALUE opcode -``` - -=== CACHE - -Python Versions: 3.11+ - -A NOP opcode that CPython uses to record runtime conditions for the https://peps.python.org/pep-0659/[specializing adaptive interpreter]. -For instance, if CPython detects a BINARY_OP opcode is usually performed on two `int`, -the CPython interpreter writes that information into the CACHE opcodes that precede the BINARY_OP to specialize the BINARY_OP into `INT_BINARY_ADD`. -JPyIntepreter does specialization at compile time using information from type annotation and typeflow analysis. - -Example Sources: - -```python -# Several CACHE instruction precede the -# BINARY_OP -x + y -``` - -=== POP_TOP - -Python Versions: ALL - -``` -Stack Prior: ... item -Stack After: ... -``` - -Pops a single item off the stack. -Used to discard return value of functions and to manipulate the stack. - -Example Sources: - -```python -my_fun() # POP_TOP is generated after the function call - # since the result of my_fun is unused. -``` - -=== ROT_TWO - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... second, first -Stack After: ... first, second -``` - -Swaps the top two elements of the stack. -Used for stack manipulation. - -Example Sources: - -```python -# Swap in Python 3.10 and below can be implemented as -# LOAD[y], LOAD[x], ROT_TWO, STORE[x], STORE[y] -x, y = y, x -``` - -=== ROT_THREE - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... third, second, first -Stack After: ... first, third, second -``` - -Move the top of stack down by two, raising the two items immediately below it up by one. -Used for stack manipulation. - -Example Sources: - -```python -# FLIP_3 in Python 3.10 and below can be implemented as -# LOAD[z], LOAD[y], LOAD[x], (stack is z, y, x) -# ROT_THREE, (stack is x, z, y) -# ROT_TWO, (stack is x, y, z) -# STORE[x], STORE[y], STORE[z] -x, y, z = z, y, x -``` - -=== ROT_FOUR - -Python Versions: >= 3.8 and \<= 3.10 - -``` -Stack Prior: ... fourth, third, second, first -Stack After: ... first, fourth, third, second -``` - -Move the top of stack down by three, raising the three items immediately below it up by one. -Used for stack manipulation. - -Example Sources: - -```python -# FLIP_3 in Python 3.10 and below can be implemented as -# LOAD[w], LOAD[z], LOAD[y], LOAD[x], (stack is z, y, x, w) -# ROT_FOUR, (stack is w, z, y, x) -# ROT_THREE, (stack is w, x, z, y) -# ROT_TWO (stack is w, x, y, z) -# STORE[w], STORE[x], STORE[y], STORE[z] -w, x, y, z = z, y, x, w -``` - -=== DUP_TOP - -Python Versions: >= 3.2, \<= 3.10 - -``` -Stack Prior: ... top -Stack After: ... top, top -``` - -Duplicates the top of stack. -Used to preserve a stack value that is used in multiple operations. - -Example Sources: - -```python -# Python 3.10 and below -a == b == c -``` - -=== DUP_TOP_TWO - -Python Versions: >= 3.2, \<= 3.10 - -``` -Stack Prior: ... a, b -Stack After: ... a, b, a, b -``` - -Duplicates the top two elements of the stack. - -Example Sources: - -```python -a[b] += a[c] -``` - -=== COPY(i) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item_i, ..., item_2, item_1 -Stack After: ... item_i, ..., item_2, item_1, item_i -``` - -Copies the item at the given 1-based index in the stack to the top of stack. -The item is not removed from its original position. - -Example Sources: - -```python -# Python 3.11 and above -a == b == c -``` - -=== SWAP(i) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item_i, ..., item_2, item_1 -Stack After: ... item_1, ..., item_2, item_i -``` - -Swaps the item at the given 1-based index in the stack with the top of stack. - -Example Sources: - -```python -# Python 3.11 and above -a == b == c -``` - -=== UNARY_POSITIVE - -Python Versions: ALL - -``` -Stack Prior: ... operand -Stack After: ... result -``` - -Implements `+x`. -Pops the operand off the stack, call its `\\__pos__` method, and push the result. - -Example Sources: - -```python -+x -``` - -=== UNARY_NEGATIVE - -Python Versions: ALL - -``` -Stack Prior: ... operand -Stack After: ... result -``` - -Implements `-x`. -Pops the operand off the stack, call its `\\__neg__` method, and push the result. - -Example Sources: - -```python --x -``` - -=== UNARY_INVERT - -Python Versions: ALL - -``` -Stack Prior: ... operand -Stack After: ... result -``` - -Implements `~x`. -Pops the operand off the stack, call its `\\__invert__` method, and push the result. - -Example Sources: - -```python -~x -``` - -=== GET_ITER - -Python Versions: ALL - -``` -Stack Prior: ... iterable -Stack After: ... iterator -``` - -Pops the iterable off the stack, call its `\\__iter__` method, and push the result. Used to implement for loops. - -Example Sources: - -```python -# The for loop performs GET_ITER on iterable to get the iterator, -# which is then used by FOR_ITER to loops through iterable items. -for item in iterable: - ... -``` - -=== GET_YIELD_FROM_ITER - -Python Versions: >= 3.5 - -``` -Stack Prior: ... iterable -Stack After: ... iterator -``` - -If the iterable is a generator iterator or a coroutine, leave it on the stack. -Otherwise, call its `\\__iter__` method. -Used in `yield from` expressions. - -Example Sources: - -```python -# GET_YIELD_FROM_ITER is used to get the -# iterator for [1, 2, 3]. -yield from [1, 2, 3] -``` - -=== BINARY_OP(op) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... left_operand, right_operand -Stack After: ... result -``` - -Perform the binary operator indicated by the opcode's argument on the top two items on the stack (popping them), and push the result to the top of stack. -An binary op can either be inplace or not in place. -Inplace opcodes try the binary op's inplace method first, -then fall back to the standard binary op handling. -The argument to binary operator mapping can be found in https://github.com/python/cpython/blob/0faa0ba240e815614e5a2900e48007acac41b214/Python/ceval.c#L299[CPython source code]. -In general, first are the non-inplace opcodes in alphabetical order, followed by the inplace opcodes in alphabetical order. - -The left operand is always the first element below the top of stack, and the right operand is always the top of stack. The binary operation is performed as followed: - -* Get the method for the binary operation from the left operand's type. - -* If the method is present: - -** Call the resolved method with the left and right operands. -** If the result is not `NotImplemented`, then it the result of this `BINARY_OP` and go to the next bytecode instruction. -** If the result is `NotImplemented`, treat it as if the left operand's type did not have the method. -** If the left operand a builtin type and the method raises a `TypeError`, treat it as if the left operand's type did not have the method. - -* If the method is not present: - -** Get the method for the reflected version of the binary operation from the right operand's type. - -** If the reflected method is present: - -*** Call the reflected resolved method with the right and left operands. -*** If the result is not `NotImplemented`, then it the result of this `BINARY_OP` and go to the next bytecode instruction. -*** If the result is `NotImplemented`, treat it as if the right operand's type did not have the reflected method. - -** If the reflected method is not present, raise a `TypeError` with the message `f"unsupported operand type(s) for {symbol(BINARY_OP)}: '{type(left_operand)}' and '{type(right_operand)}'"` - -In Python, it would look like this: - -```python -def binary_op(BINARY_OP, left_operand, right_operand): - UNSUPPORTED_OP_MSG = f"unsupported operand type(s) for {symbol(BINARY_OP)}: '{type(left_operand)}' and '{type(right_operand)}'" - def reflected_binary_op(): - right_method = getattr(type(right_operand), reflected(BINARY_OP), None) - if right_method is not None: - reflected_out = right_method(right_operand, left_operand) - if reflected_out is NotImplemented: - raise TypeError(UNSUPPORTED_OP_MSG) - else: - return reflected_out - - if is_in_place(BINARY_OP): - inplace_method = getattr(type(left_operand), BINARY_OP, None) - if inplace_method is not None: - out = inplace_method(left_operand, right_operand) - if out is not NotImplemented: - return out - BINARY_OP = get_standard_binary_op(BINARY_OP) - left_method = getattr(type(left_operand), BINARY_OP, None) - if left_method is not None: - try: - out = left_method(left_operand, right_operand) - if out is not NotImplemented: - return out - except TypeError: - if type(left_operand) in BUILTIN_TYPES: - return reflected_binary_op() - raise - return reflected_binary_op() - -``` - -Example Sources: - -```python -x + 1 -``` - -```python -x -= y -``` - -=== BINARY_POWER - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... base, exponent -Stack After: ... result -``` - -<> corresponding to `\\__pow__` and `\\__rpow__`. Implements `**`. - -Example Sources: - -```python -base ** expotent -``` - -=== BINARY_MULTIPLY - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_factor, right_factor -Stack After: ... product -``` - -<> corresponding to `\\__mul__` and `\\__rmul__`. Implements `*`. - -Example Sources: - -```python -left_factor * right_factor -``` - -=== BINARY_MATRIX_MULTIPLY - -Python Versions: >= 3.5 and \<= 3.10 - -``` -Stack Prior: ... left_factor, right_factor -Stack After: ... product -``` - -<> corresponding to `\\__matmul__` and `\\__rmatmul__`. Implements `@`. - -Example Sources: - -```python -left_factor @ right_factor -``` - -=== BINARY_FLOOR_DIVIDE - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... quotient -``` - -<> corresponding to `\\__floordiv__` and `\\__rfloordiv__`. Implements `//`. - -Example Sources: - -```python -dividend // divisor -``` - -=== BINARY_TRUE_DIVIDE - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... quotient -``` - -<> corresponding to `\\__truediv__` and `\\__rtruediv__`. Implements `/`. - -Example Sources: - -```python -dividend / divisor -``` - -=== BINARY_MODULO - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... modulus -``` - -<> corresponding to `\\__mod__` and `\\__rmod__`. Implements `%`. - -Example Sources: - -```python -dividend % divisor -``` - -=== BINARY_ADD - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... augend, addend -Stack After: ... sum -``` - -<> corresponding to `\\__add__` and `\\__radd__`. Implements `+`. - -Example Sources: - -```python -augend + addend -``` - -=== BINARY_SUBTRACT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... minuend, subtrahend -Stack After: ... difference -``` - -<> corresponding to `\\__sub__` and `\\__rsub__`. Implements `-`. - -Example Sources: - -```python -minuend - subtrahend -``` - - -=== BINARY_LSHIFT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... to_shift, shift -Stack After: ... result -``` - -<> corresponding to `\\__lshift__` and `\\__rlshift__`. Implements `<<`. - -Example Sources: - -```python -to_shift << shift -``` - -=== BINARY_RSHIFT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... to_shift, shift -Stack After: ... result -``` - -<> corresponding to `\\__rshift__` and `\\__rrshift__`. Implements `>>`. - -Example Sources: - -```python -to_shift >> shift -``` - -=== BINARY_AND - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_conjunct, right_conjunct -Stack After: ... conjunction -``` - -<> corresponding to `\\__and__` and `\\__rand__`. Implements `&`. - -Example Sources: - -```python -left_conjunct & right_conjunct -``` - - -=== BINARY_OR - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_disjunct, right_disjunct -Stack After: ... disjunction -``` - -<> corresponding to `\\__or__` and `\\__ror__`. Implements `|`. - -Example Sources: - -```python -left_disjunct | right_disjunct -``` - - -=== BINARY_XOR - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_disjunct, right_disjunct -Stack After: ... disjunction -``` - -<> corresponding to `\\__xor__` and `\\__rxor__`. Implements `^`. - -Example Sources: - -```python -left_disjunct ^ right_disjunct -``` - - -=== INPLACE_POWER - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... base, exponent -Stack After: ... result -``` - -<> corresponding to `\\__ipow__`, `\\__pow__` and `\\__rpow__`. Implements `**=`. - -Example Sources: - -```python -base **= expotent -``` - -=== INPLACE_MULTIPLY - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_factor, right_factor -Stack After: ... product -``` - -<> corresponding to `\\__imul__`, `\\__mul__` and `\\__rmul__`. Implements `*`. - -Example Sources: - -```python -left_factor *= right_factor -``` - -=== INPLACE_MATRIX_MULTIPLY - -Python Versions: >= 3.5 and \<= 3.10 - -``` -Stack Prior: ... left_factor, right_factor -Stack After: ... product -``` - -<> corresponding to `\\__imatmul__`, `\\__matmul__` and `\\__rmatmul__`. Implements `@=`. - -Example Sources: - -```python -left_factor @= right_factor -``` - -=== INPLACE_FLOOR_DIVIDE - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... quotient -``` - -<> corresponding to `\\__ifloordiv__`, `\\__floordiv__` and `\\__rfloordiv__`. Implements `//=`. - -Example Sources: - -```python -dividend //= divisor -``` - -=== INPLACE_TRUE_DIVIDE - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... quotient -``` - -<> corresponding to `\\__itruediv__`, `\\__truediv__` and `\\__rtruediv__`. Implements `/=`. - -Example Sources: - -```python -dividend /= divisor -``` - -=== INPLACE_MODULO - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... dividend, divisor -Stack After: ... modulus -``` - -<> corresponding to `\\__imod__`, `\\__mod__` and `\\__rmod__`. Implements `%=`. - -Example Sources: - -```python -dividend %= divisor -``` - -=== INPLACE_ADD - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... augend, addend -Stack After: ... sum -``` - -<> corresponding to `\\__iadd__`, `\\__add__` and `\\__radd__`. Implements `+=`. - -Example Sources: - -```python -augend += addend -``` - -=== INPLACE_SUBTRACT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... minuend, subtrahend -Stack After: ... difference -``` - -<> corresponding to `\\__isub__`, `\\__sub__` and `\\__rsub__`. Implements `-=`. - -Example Sources: - -```python -minuend -= subtrahend -``` - - -=== INPLACE_LSHIFT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... to_shift, shift -Stack After: ... result -``` - -<> corresponding to `\\__ilshift__`, `\\__lshift__` and `\\__rlshift__`. Implements `<\<=`. - -Example Sources: - -```python -to_shift <<= shift -``` - -=== INPLACE_RSHIFT - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... to_shift, shift -Stack After: ... result -``` - -<> corresponding to `\\__irshift__` , `\\__rshift__` and `\\__rrshift__`. Implements `>>=`. - -Example Sources: - -```python -to_shift >>= shift -``` - -=== INPLACE_AND - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_conjunct, right_conjunct -Stack After: ... conjunction -``` - -<> corresponding to `\\__iand__` , `\\__and__` and `\\__rand__`. Implements `&=`. - -Example Sources: - -```python -left_conjunct &= right_conjunct -``` - - -=== INPLACE_OR - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_disjunct, right_disjunct -Stack After: ... disjunction -``` - -<> corresponding to `\\__ior__`, `\\__or__` and `\\__ror__`. Implements `|=`. - -Example Sources: - -```python -left_disjunct |= right_disjunct -``` - - -=== INPLACE_XOR - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... left_disjunct, right_disjunct -Stack After: ... disjunction -``` - -<> corresponding to `\\__ixor__`, `\\__xor__` and `\\__rxor__`. Implements `^=`. - -Example Sources: - -```python -left_disjunct ^= right_disjunct -``` - - -=== BINARY_SUBSCR - -Python Versions: ALL - -``` -Stack Prior: ... collection, key -Stack After: ... item -``` - -Implements `collection[key]`. Acts like a <>, but does not have a reflective method. Pop the two top items off the stack, calls the `\\__getitem__` method on `type(collection)` with the arguments `collection` and `key`, and push the method result to the top of the stack. - -Example Sources: - -```python -collection[key] -``` - - -=== STORE_SUBSCR - -Python Versions: ALL - -``` -Stack Prior: ... value, collection, key -Stack After: ... -``` - -Implements `collection[key] = value`. Pop the three top items off the stack, calls the `\\__setitem__` method on `type(collection)` with the arguments `collection`, `key` and `value`. Does not push the result onto the stack. - -Example Sources: - -```python -collection[key] = value -``` - - -=== DELETE_SUBSCR - -Python Versions: ALL - -``` -Stack Prior: ... collection, key -Stack After: ... -``` - -Implements `del collection[key]`. Pop the two top items off the stack, calls the `\\__delitem__` method on `type(collection)` with the arguments `collection` and `key`. Does not push the result onto the stack. - -Example Sources: - -```python -del collection[key] -``` - - -=== GET_AWAITABLE(where) - -Python Versions: >= 3.5 - -``` -Stack Prior: ... awaitable -Stack After: ... awaitable_iterator -``` - -If awaitable is a coroutine or a generator coroutine, leave it as is on the stack. Otherwise, replace `awaitable` with the result of calling its `\\__await__` method. - -Before Python 3.11, this opcode did not have an argument. - -After Python 3.11, this opcode has an argument, which indicates where the instruction occurs if non-zero: - -- `1` if after a call to `\\__aenter__` -- `2` if after a call to `\\__aexit__` - -Example Sources: - -```python -await awaitable -``` - - -=== GET_AITER - -Python Versions: >= 3.5 - -``` -Stack Prior: ... async_iterable -Stack After: ... async_iterator -``` - -Pops the async iterable off the stack, call its `\\__aiter__` method, and push the result. Used to implement async for loops. - -Example Sources: - -```python -# The for loop performs GET_AITER on async_iterable to get the async iterator. -# GET_ANEXT is then called on the async iterator. -# The object returned by GET_ANEXT is sent None, -# and the object returned from the send operation is the next item in the async for loop. -async for item in iterable: - print(item) -``` -The bytecode in 3.11 looks like this: -``` - 6 LOAD_FAST 0 (iterable) - 8 GET_AITER -================= begin try block ===================== ->> 10 GET_ANEXT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+ - 12 LOAD_CONST 0 (None) ^ ->> 14 SEND 3 (to 22) ------+>>+ ^ - 16 YIELD_VALUE ^ v ^ - 18 RESUME 3 ^ v ^ - 20 JUMP_BACKWARD_NO_INTERRUPT 4 (to 14)-+ v ^ -================== end try block ===================v=^>v ->> 22 STORE_FAST 1 (item) <<<<<<<<<<+ ^ v - 24 LOAD_GLOBAL 1 (NULL + print) ^ v - 36 LOAD_FAST 1 (item) ^ v - 38 PRECALL 1 ^ v - 42 CALL 1 ^ v - 52 POP_TOP ^ v - 54 JUMP_BACKWARD 23 (to 10)>>>>>>>>>>>>+ v -================== begin finally ======================<+ ->> 56 END_ASYNC_FOR -================== end finally ======================== -``` - - -=== GET_ANEXT - -Python Versions: >= 3.5 - -``` -Stack Prior: ... awaitable_iterator -Stack After: ... awaitable_item -``` - -Pops `awaitable_iterator` off the stack. -Calls `\\__anext__` on `awaitable_iterator`. -If the result is a coroutine or a generator coroutine, push it as is on the stack. -Otherwise, call the result's `\\__await__` method and push that to the stack. -Used to implement async for loops. - - -Example Sources: - -See <> for an example. - - -=== END_ASYNC_FOR - -Python Versions: >= 3.5, \<= 3.10 - -``` -Stack Prior: ... async_iterable, tb1, ex1, ex_type1, tb2, ex2, ex_type2 -Stack After: ... -``` - -Python Versions: >= 3.11 - -``` -Stack Prior: ... async_iterable, exception -Stack After: ... -``` - -Terminates an async for loop. - -Behavior prior to 3.11: - -If `ex_type2` is `StopAsyncIteration`, pop 7 values from the stack, restoring exception state from `tb1`, `ex1` and `ex_type1`. -Otherwise, re-raise `ex2`. - -Behavior after 3.11: - -If `exception` is an instance of `StopAsyncIteration`, pop both `async_iterable` and `exception` off the stack. -Otherwise, re-raise `exception`. - -Example Sources: - -See <> for an example. - - -=== BEFORE_ASYNC_WITH - -Python Versions: >= 3.5 - -``` -Stack Prior: ... async_context_manager -Stack After: ... async_exit_function, async_enter_result -``` - -Pops `async_context_manager` off the stack and resolve its `\\__aexit__` and `\\__aenter__` methods. -The `\\__aexit__` function is pushed to the stack, -followed by the object return by calling `\\__aenter__`. -Used to implement asynchronous context managers - -Example Sources: - -```python -async with async_context_manager as async_context: - pass -``` - - -=== SETUP_ASYNC_WITH - -Python Versions: >= 3.5, \<= 3.10 - -``` -Stack Prior: ... -Stack After: ... -``` - -Create the `try...finally` block used for asynchronous context managers. - -Example Sources: - -```python -async with async_context_manager as async_context: - pass -``` - - -=== PRINT_EXPR - -Python Versions: ALL - -``` -Stack Prior: ... to_print -Stack After: ... -``` - -Pops off the top of the stack, calls its `\\__repr__` method, and prints the result. -Used by the CPython interactive interpreter to print an entered expression. -It is never emitted as an opcode in a function. - -Example Sources: - -It cannot be emitted in a function, and thus has no example sources. -It is emitted as the final opcode when the CPython interactive interpreter compiles an expression outside a block. - -=== SET_ADD(i) - -Python Versions: ALL - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -Pops off the top of the stack and call `s_i.add(s_0)`. -`s_i` remains on the stack so it can be reused. -Used to implement set comprehensions. - -Example Sources: - -```python -{point for point in point_list} -``` - - -=== LIST_APPEND(i) - -Python Versions: ALL - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -Pops off the top of the stack and call `s_i.append(s_0)`. -`s_i` remains on the stack so it can be reused. -Used to implement list comprehensions. - -Example Sources: - -```python -[point for point in point_list] -``` - - -=== MAP_ADD(i) - -Python Versions: ALL - -``` -Stack Prior: ... s_i, ..., s_1, s_0, value -Stack After: ... s_i, ..., s_1 -``` - -Pops off the top two items on the stack and call `s_i.\\__setitem__(s_0, value)`. -`s_i` remains on the stack so it can be reused. -Used to implement dict comprehensions. - -Example Sources: - -```python -{key: value for key, value in item_list} -``` - -=== RETURN_VALUE - -Python Versions: ALL - -``` -Stack Prior: ... return_value -Stack After: N/A -``` - -Returns the item at the top of the stack. - -Example Sources: - -```python -def function(): - pass # A `return None` is placed at the end of the function -``` - -```python -return # This is actually `return None` in disguise -``` - -```python -return value -``` - -=== RETURN_GENERATOR - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Creates a new generator from the current frame. -Used to implement generators. - -Generators basically act as two separate functions: - -* One outer function that just set locals and return a generator object that wraps the inner function. -* One inner function that yield values. - -In this way, it is similar to returning a new anonymous class in a Java function, where we need to pass the function locals to the anonymous class. - -However, unlike Java, the outer function and the inner function share the same bytecode. So a generator will start with the `RETURN_GENERATOR` opcode (which returns the generator object), followed by the bytecode for the generator. - -JPyInterpreter treats this opcode as a no-op, since we use separate classes for the outer function and the inner functions. - -Example Sources: - -```python -def function(): - # A RETURN_GENERATOR will be placed at the start - # of the generator - yield -``` - - -=== GEN_START(kind) - -Python Versions: == 3.10 - -``` -Stack Prior: ... top -Stack After: ... -``` - -Pops off the top of stack. -This is the first opcode for generators in 3.10. -The stack is not empty, since calling a generator is the same as sending the generator `None`, which push `None` to the top of the stack. -The `kind` argument indicates what kind of generator it is: - -- 0 is a normal generator. -- 1 is a coroutine. -- 2 is an async generator. - -Example Sources: - -```python -def function(): - # A GEN_START(0) will be placed at the start - # of the generator - yield -``` - - -=== SEND(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... subgenerator, sent_value -Stack if subgenerator is not exhausted: ... subgenerator, yielded_value -Stack if subgenerator is exhausted: ... subgenerator -``` - -Pops off the top of stack, and sends it to the sub-generator of this generator. -If the sub-generator is not exhausted, the yielded value is pushed to the top of the stack. -Otherwise, jump forward by `target_delta`, leaving `subgenerator` on the stack. -Used to implement `yield from` and `await` statements. - -Example Sources: - -```python -# yield from subgenerator is implemented as the following loop -# (with None initially at the top of the stack) -# -# SEND (sends the top of stack to the subgenerator) -# YIELD_VALUE (returns the yielded value to the caller) -# JUMP_BACKWARDS (to SEND) -# -# Before the loop, GET_YIELD_FROM_ITER is used to get the generator -# that will act as the subgenerator -yield from subgenerator -``` - -```python -# await is a yield from in disguise, -# and is implemented by the same loop -# -# Before the loop, GET_AWAITABLE is used to get the awaitable -# that will act as the subgenerator -await awaitable -``` - - -=== YIELD_VALUE - -Python Versions: ALL - -``` -Stack Prior: ... yielded_value -Stack After: ... sent_value -``` - -Pops off the top of the stack and yields it to the calling function. -The function is then paused, and is resumed by either a `.send(sent_value)` or `.throw(raised_exception)` call. -If `.send(sent_value)` is used, that value is pushed to the top of the stack. -Otherwise, the exception passed to `.throw(raised_exception)` is raised at this opcode position. -Calling `next` on a generator acts as `generator.send(None)`. - -Example Sources: - -```python -# the sent value is ignored here -yield 10 -``` - -```python -# the sent value is stored in sent_value here -sent_value = yield 10 -``` - - -=== YIELD_FROM - -Python Versions: >= 3.3, \<= 3.10 - -``` -Stack Prior: ... subgenerator, sent_value -Stack After: ... final_yielded_value -``` - -Pops off the top of the stack, and set this generator subgenerator to the element immediately below it. -Control is then passed to the subgenerator. -If the subgenerator is not a generator but an iterable, it is treated as the following pseudo-generator: - -```python -def iterable_generator(iterable): - for item in iterable: - sent_value = yield item - if sent_value is not None: - raise AttributeError(f"'{type(iter(iterable))}' object has no attribute 'send'") - -``` - -When the subgenerator is not exhausted, `send` and `throw` calls are proxied to it. -When the subgenerator is exhausted, its final yielded value is pushed to the top of the stack and this generator resumes. - -In Python 3.11 and above, the YIELD_FROM opcode is replaced by a SEND + YIELD_VALUE while loop, as documented in the <>. - -Example Sources: - -```python -yield from subgenerator -``` - -=== SETUP_ANNOTATIONS - -Python Versions: >= 3.6 - -``` -Stack Prior: ... -Stack After: ... -``` - -Checks if the variable `\\__annotations__` is defined. -If `\\__annotations__` is not defined, it is initialized to an empty `dict`. -Otherwise, `\\__annotations__` keep it current value. -It is emitted for a class and module bodies which contain static variable annotations. -This opcode is not emitted for function bodies. - -Example Sources: - -```python -class MyClass: - # SETUP_ANNOTATIONS is emitted here, - # for a later __annotations__['x'] = int - # call - x: int -``` - - -=== IMPORT_STAR - -Python Versions: ALL - -``` -Stack Prior: ... modulevalue -Stack After: ... -``` - -Loads all symbols not starting with '_' directly from the module located at the top of the stack to the local namespace. -The module is popped after loading all names, whose values are copied to the module's local variables. -This opcode implements `from module import *`. -It is illegal to use `from module import *` in a function. - -Example Sources: - -```python -from module import * -``` - - -=== POP_BLOCK - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... -Stack After: ... -``` - -Pops the block that store exception handler information off the stack. -Since the JVM store the exception table separate from the bytecode, this is a no-op for JPyInterpreter. -In CPython 3.11, the CPython interpreter also stores the exception table separate from the bytecode, removing the need for this opcode. - -Example Sources: - -```python -try: - something() - # A POP_BLOCK opcode is placed - # at the end of a try block. -except: - pass -``` - - -=== POP_EXCEPT - -Python Versions: ALL - -Python 3.10 and below -``` -Stack Prior: ... traceback, exception, exception_type -Stack After: ... -``` - -Python 3.11 and above -``` -Stack Prior: ... exception -Stack After: ... -``` - -Pop off exception data off the stack, which is used to restore the exception state. -Before Python 3.11, this pop off three values (traceback, exception and type(exception)). -After Python 3.11, this pop off one value (exception). -This is placed at the beginning of every except block - -Example Sources: - -```python -try: - something() -except: - # A POP_EXCEPT opcode is placed - # at the beginning of an except block. - pass -``` - - -=== PUSH_EXC_INFO - -Python Versions: >= 3.11 - -``` -Stack Prior: ... top -Stack After: ... exception, top -``` - -Inserts the currently active exception behind the item currently at the top of stack. -Used to allow the current exception to be stored if an except block uses it. - -Example Sources: - -```python -try: - pass - -# A PUSH_EXC_INFO is emitted at the start of the -# try block exception handler, which goes through -# a series of conditional jumps to determine which -# except block to enter. -# The except block then decide if they should store -# the current exception, or pop it off the stack. -except ValueError as e: - pass -except Exception: - pass -``` - - -=== CHECK_EXC_MATCH - -Python Versions: >= 3.11 - -``` -Stack Prior: ... exception, exception_type -Stack After: ... exception, test_result -``` - -Test if `exception` is an instance of `exception_type`. -If so, it pushes `True` to the top of stack; otherwise, it pushes `False`. -`exception_type` is popped off the stack; `exception` remains on the stack. -Used to implement `except` blocks that catch particular types of exceptions. - -Example Sources: - -```python -try: - pass -except ValueError: # CHECK_EXC_MATCH is used here with - # exception and ValueError - # on the stack. - pass -``` - - -=== RERAISE(set_f_lasti) - -Python Versions: >= 3.9 - -If `set_f_lasti` is not set -``` -Stack Prior: ... exception_or_type -Stack After: N/A -``` - -If `set_f_lasti` is set -``` -Stack Prior: ... index, exception_or_type -Stack After: N/A -``` - -The item at the top of the stack is either an exception or type. -If it is an exception, throw it. -If it is a type, construct and throw a new instance of that type. -Used to implement a bare `raise` in an except block. - -Note: CPython uses the `index` below the exception/type to set the last index if the bytecode argument not 0. -JPyInterpreter can ignore the argument, since the JVM keep track of frames for us. - -Example Sources: - -```python -try: - pass -except: - raise # this emits RERAISE -``` - - -=== WITH_EXCEPT_START - -Python Versions: >= 3.9 - -Before 3.11 -``` -Stack Prior: ... exit_function, instruction, stack_size, label, traceback, exception, exception_type -Stack After: N/A -``` - -After 3.11 -``` -Stack Prior: ... exit_function, traceback, exception, exception_type -Stack After: N/A -``` - -Calls `exit_function` with arguments `exception_type, exception, traceback`, and push the returned value to the top of the stack. -The returned value should be a boolean. -If the returned value is Truthy, the context manager handled the exception and execution continue. -If the returned value is Falsy, the exception is propagated. -If no exception occurred, exception_type, exception, traceback are all None. - - -Example Sources: - -```python -with context: - pass -``` - - -=== LOAD_ASSERTION_ERROR - -Python Versions: >= 3.9 - -``` -Stack Prior: ... -Stack After: ... AssertionError -``` - -Pushes the type `AssertionError` onto the stack. -Used in `assert` statements. - -Example Sources: - -```python -assert False -``` - - -=== LOAD_BUILD_CLASS - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... __build_class__ -``` - -Pushes the function `builtins.\\__build_class__` onto the stack. -Used to construct classes. -The function signature for `\\__build_class__` is: - -```python -def __build_class__(class_body: function, - class_name: str, - *bases: List[Type], - metaclass: Type, - **metaclass_parameters: Dict[str, Any]) \ - -> Type -``` - -Example Sources: - -```python -class C: - pass - -# this translates roughly to: -# __build_class__(, 'C') -``` - -```python -class C(A, B): - pass - -# this translates roughly to: -# __build_class__(, 'C', A, B) -``` - -```python -class C(A, metaclass=M, metaclass_arg=1): - pass - -# this translates roughly to: -# __build_class__(, 'C', A, metaclass=M, metaclass_arg=1) -``` - - -=== SETUP_WITH(except_delta) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... context_manager -Stack After: ... exit_function, start_function_result -Stack On Exception: ... instruction, stack_size, label, traceback, exception, exception_type -``` - -Pops off the top of the stack, and push its `\\__exit__` function and the result of calling its `\\__enter__` function to the top of the stack. -`except_delta` points to the exception handler for the which block. -If an exception occurs, the follow items will be pushed to the stack: - -. The bytecode instruction index that was executing when the exception happened. - -. The size of the stack before the with block. - -. The exception handler label. - -. The traceback for the exception. - -. The actual exception. - -. The type of the exception. - -Example Sources: - -```python -with context_manager: - pass -``` - - -=== STORE_NAME(namei) - -Python Versions: ALL - -``` -Stack Prior: ... value -Stack After: ... -``` - -Sets either the global or local variable with the name `co_names[namei]` to the item currently at the top of stack. -The top of stack is then popped. -This opcode is only emitted for module bodies and classes. - -Example Sources: - -```python -class C: - x = 10 # this emits a STORE_NAME('x') opcode - # with 10 on the top of the stack -``` - - -=== DELETE_NAME(namei) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Deletes either the global or local variable with the name `co_names[namei]`. -This opcode is only emitted for module bodies and classes. - -Example Sources: - -```python -class C: - x = 10 - del x # this emits a DELETE_NAME('x') opcode -``` - -=== UNPACK_SEQUENCE(count) - -Python Versions: ALL - -``` -Stack Prior: ... iterable -Stack After: ... item_{count - 1}, ..., item_1, item_0 -``` - -Top of stack is an iterable. -In reverse order, push its items to the stack (making the new item at the top of stack the first item in the iterable). -The iterable is then popped off the stack. -If the iterable does not have exactly `count` items, a `ValueError` is raised. -The items are received using an iterator loop: - -```python -iterator = iter(iterable) -items = [] -while True: - try: - items.append(next(iterator)) - except StopIteration: - break - -``` - -Example Sources: - -```python -# This is compiled as: -# UNPACK_SEQUENCE(2) -# STORE_FAST('x') (1) -# STORE_FAST('y') (2) -x, y = (1, 2) -``` - - -=== UNPACK_EX((high byte) end_index | (low byte) start_index) - -Python Versions: ALL - -``` -Stack Prior: ... iterable -Stack After: ... after_0, after_1, ..., after_{end_index - 1}, extra, before_{start_index - 1}, ..., before_1, before_0 -``` - -Top of stack is an iterable. -Collect its item into a list. -Push each item in the slice [-end_index:] to the stack in forward order (empty if end_index == 0). -Push a list containing the slice [start_index:end_index] to the stack as a single item. -Push each item in the slice [:start_index] in reverse order (empty if start_index == 0). -The iterable is then popped off the stack. -If the iterable does not have at least `start_index + end_index` items, a `ValueError` is raised. -The items are received using an iterator loop: - -```python -iterator = iter(iterable) -items = [] -while True: - try: - items.append(next(iterator)) - except StopIteration: - break - -before = items[:start_index] -after = items[-end_index:] -extra = items[start_index:end_index] -``` - -Example Sources: - -```python -# This is compiled as: -# UNPACK_EX((2 << 8) | 1) (= 513) -# STORE_FAST('b_0') (1) -# STORE_FAST('extra') ([2, 3]) -# STORE_FAST('a_0') (4) -# STORE_FAST('a_1') (5) -b_0, *extra, a_0, a_1 = (1, 2, 3, 4, 5) -``` - - -=== STORE_ATTR(namei) - -Python Versions: ALL - -``` -Stack Prior: ... value, object -Stack After: ... -``` - -Sets the attribute with the name `co_names[namei]` on the object at the top of the stack to the value immediately below it. -Both the object and the value are popped. -The value is set by calling the `\\__setattr__(self, name: str, value: Any)` method on the type of object with `object`, `co_names[namei]` and `value` as the arguments. - -Example Sources: - -```python -# equivalent to -# type(my_object).__setattr__(my_object, 'attribute', value) -my_object.attribute = value -``` - - -=== DELETE_ATTR(namei) - -Python Versions: ALL - -``` -Stack Prior: ... object -Stack After: ... -``` - -Deletes the attribute with the name `co_names[namei]` on the object at the top of the stack. -The object is then popped off the stack. -The value is deleted by calling the `\\__delattr__(self, name: str)` method on the type of object with `object` and `co_names[namei]` as the arguments. - -Example Sources: - -```python -# equivalent to -# type(my_object).__delattr__(my_object, 'attribute') -del my_object.attribute -``` - - -=== STORE_GLOBAL(namei) - -Python Versions: ALL - -``` -Stack Prior: ... value -Stack After: ... -``` - -Sets the global variable with the name `co_names[namei]` to the value currently at the top of the stack. -The value is then popped from the stack. -Each module has a unique global namespace used to store global variables. -Functions in the same module use the same namespace. - -Example Sources: - -```python -# required, otherwise it be a STORE_FAST -global variable -variable = 5 -``` - - -=== DELETE_GLOBAL(namei) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Deletes the global variable with the name `co_names[namei]`. -Each module has a unique global namespace used to store global variables. -Functions in the same module use the same namespace. - -Example Sources: - -```python -# required, otherwise it be a DELETE_FAST -global variable -del variable -``` - - -=== LOAD_CONST(consti) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... constant -``` - -Loads the constant `co_constants[consti]`. - -Example Sources: - -```python -'A string constant' -``` - -```python -1 # an int constant -``` - -```python -(1, 2, 3) # a tuple constant -``` - - -=== LOAD_NAME(namei) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... value -``` - -Pushes either the global or local variable with the name `co_names[namei]` to the top of stack. -This opcode is only emitted for module bodies and classes. - -Example Sources: - -```python -class C: - x # this emits a LOAD_NAME('x') opcode -``` - - -=== BUILD_TUPLE(count) - -Python Versions: ALL - -``` -Stack Prior: ... item_0, item_1, ..., item_{count - 1} -Stack After: ... item_tuple -``` - -Construct a tuple from the top `count` items on the stack. -The items are placed in the reverse order that they are encountered from the top of stack (making the top of stack the last element). -The top `count` items are then popped from the stack, and the newly constructed tuple is pushed to the stack. - -Example Sources: - -```python -x = 1 -y = 2 -z = (x, y) # This creates a BUILD_TUPLE opcode: -# LOAD_FAST('x') -# LOAD_FAST('y') -# BUILD_TUPLE(2) -``` - - -=== BUILD_LIST(count) - -Python Versions: ALL - -``` -Stack Prior: ... item_0, item_1, ..., item_{count - 1} -Stack After: ... item_tuple -``` - -Construct a list from the top `count` items on the stack. -The items are placed in the reverse order that they are encountered from the top of stack (making the top of stack the last element). -The top `count` items are then popped from the stack, and the newly constructed list is pushed to the stack. - -Example Sources: - -```python -x = 1 -y = 2 -z = [x, y] # This creates a BUILD_LIST opcode: -# LOAD_FAST('x') -# LOAD_FAST('y') -# BUILD_LIST(2) -``` - - -=== BUILD_SET(count) - -Python Versions: ALL - -``` -Stack Prior: ... item_0, item_1, ..., item_{count - 1} -Stack After: ... item_tuple -``` - -Construct a set from the top `count` items on the stack. -The items are placed in the reverse order that they are encountered from the top of stack (making the top of stack the last element). -The top `count` items are then popped from the stack, and the newly constructed set is pushed to the stack. -The items lower in the stack are prioritized over items higher in stack (i.e. if `item_0 == item_1`, then `item_0` be added to the set, not `item_1`). - -Example Sources: - -```python -x = 1 -y = 2 -z = {x, y} # This creates a BUILD_SET opcode: -# LOAD_FAST('x') -# LOAD_FAST('y') -# BUILD_SET(2) -``` - - -=== BUILD_MAP(count) - -Python Versions: ALL - -``` -Stack Prior: ... , key_0, value_0, key_1, value_1, ..., key_{count - 1}, value_{count - 1} -Stack After: ... item_map -``` - -Construct a dict from the top `2 * count` items on the stack. -The items are put in the reverse order that they are encountered from the top of stack (making the top two items on the stack the last key-value pair). -The top `2 * count` items are then popped from the stack, and the newly constructed dict is pushed to the stack. -The items higher in the stack are prioritized over items higher in stack (i.e. if `key_0 == key_1`, then `key_1 = value_1` be put in the dict, not `key_0 = value_0`). - -Example Sources: - -```python -key_0 = 1 -value_0 = 2 -key_1 = 3 -value_1 = 4 -z = { - key_0: value_0, - key_1: value_1 -} -# This creates a BUILD_MAP opcode: -# LOAD_FAST('key_0') -# LOAD_FAST('value_0') -# LOAD_FAST('key_1') -# LOAD_FAST('value_1') -# BUILD_MAP(2) -``` - - -=== BUILD_CONST_KEY_MAP(count) - -Python Versions: >= 3.6 - -``` -Stack Prior: ... , value_0, value_1, ..., value_{count - 1}, key_tuple -Stack After: ... item_map -``` - -Construct a dict from the top `count + 1` items on the stack. -The item at the top of the stack is a tuple of constants of length count, which stores the dict's keys. -There are `count` items below it representing each key's corresponding value. -The key-value pairs are put in the reverse order that they are encountered from the top of stack (making `key_tuple[-1], value_{count - 1}` the last key-value pair to be added to the dict). -The top `count + 1` items are then popped from the stack, and the newly constructed dict is pushed to the stack. -The items higher in the stack are prioritized over items higher in stack (i.e. if `tuple[0] == tuple[1]`, then `tuple[1] = value_1` be put in the dict, not `tuple[0] = value_0`). - -Example Sources: - -```python -value_0 = 1 -value_1 = 2 -z = { - 'a': value_0, - 'b': value_1 -} -# This creates a BUILD_CONST_KEY_MAP opcode: -# LOAD_FAST('value_0') -# LOAD_FAST('value_1') -# LOAD_CONSTANT (('a', 'b')) -# BUILD_CONST_KEY_MAP(2) -``` - - -=== BUILD_STRING(count) - -Python Versions: >= 3.6 - -``` -Stack Prior: ... string_0, string_1, ..., string_{count - 1} -Stack After: ... result -``` - -Concatenate the top `count` items on the stack into a single string. -Each of the top `count` items on the stack must be a string. -The strings are concatenated from the lowest item up -(i.e. `string_0 + string_1 + ... + string_{count - 1}`). -Used to implement f-strings. - -Example Sources: - -```python -a = 'before' -b = 'after' -f'{a} {b}' -# Bytecode: -# LOAD_FAST(a) -# FORMAT_VALUE(str) -# LOAD_CONST(' ') -# LOAD_FAST(b) -# FORMAT_VALUE(str) -# BUILD_STRING(3) -``` - - -=== LIST_TO_TUPLE - -Python Versions: >= 3.9 - -``` -Stack Prior: ... list -Stack After: ... tuple -``` - -The top of the stack is a list. -Pop off the top of the stack, and replace it with a tuple with the same values in the same order. -Used to unpack a list into a tuple. - -Example Sources: - -```python -(*[1, 2, 3],) -# -# BUILD_LIST(0) # Construct an empty list -# to store the final result -# -# BUILD_LIST(0) # Construct an empty list -# # to store the immediate [1, 2, 3] -# # since lists cannot be constants -# -# LOAD_CONST((1, 2, 3)) # Load the constant (1, 2, 3) -# -# LIST_EXTEND(1) # Convert the constant (1, 2, 3) -# to [1, 2, 3] -# -# LIST_EXTEND(1) # Unpacks [1, 2, 3] into the -# # final result -# -# LIST_TO_TUPLE # Convert the final result into a tuple -``` - - -=== LIST_EXTEND(i) - -Python Versions: >= 3.9 - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -The top of the stack is an iterable and `s_i` is a list. -Pop off the top of the stack, and add its contents to `s_i`. -`s_i` remains on the stack so it can be reused. -Used to implement list unpacking. - -Example Sources: - -See <>. - - -=== SET_UPDATE(i) - -Python Versions: >= 3.9 - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -The top of the stack is an iterable and `s_i` is a set. -Pop off the top of the stack, and add its contents to `s_i`. -The added content will not replace items already in the `set`. -`s_i` remains on the stack so it can be reused. -Used to implement set unpacking. - - -Example Sources: - -```python -{*(1, 2, 3)} -# BUILD_SET(0) # Create an empty set to -# # store the result -# -# LOAD_CONST((1, 2, 3)) # Load the constant (1, 2, 3) -# -# SET_UPDATE(1) # Unpacks (1, 2, 3) into the result -``` - - -=== DICT_UPDATE(i) - -Python Versions: >= 3.9 - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -The top of the stack is an mapping and `s_i` is a dict. -Pop off the top of the stack, and add its contents to `s_i`. -The added content will replace the value assigned to keys already in the `dict`. -`s_i` remains on the stack so it can be reused. -Used to implement dict unpacking. - - -Example Sources: - -```python -{ - 'a': 1, - **b -} -# LOAD_CONSTANT('a') -# LOAD_CONSTANT(1) -# -# BUILD_MAP(1) # Create a dict with items -# # ('a', 1) -# -# LOAD_FAST(n) # Load b -# -# DICT_UPDATE(1) # Unpacks b into the result dict -``` - - -=== DICT_MERGE(i) - -Python Versions: >= 3.9 - -``` -Stack Prior: ... s_i, ..., s_1, s_0 -Stack After: ... s_i, ..., s_1 -``` - -The top of the stack is an mapping and `s_i` is a dict. -Pop off the top of the stack, and add its contents to `s_i`. -If the mapping at the top of the stack share any keys with `s_i`, a `TypeError` is raised. -`s_i` remains on the stack so it can be reused. -Used to implement dict unpacking in function calls. - - -Example Sources: - -```python -my_function(a=1, **b) -# LOAD_CONSTANT('a') -# LOAD_CONSTANT(1) -# -# BUILD_MAP(1) # Create a dict with items -# # ('a', 1) -# -# LOAD_FAST(n) # Load b -# -# DICT_MERGE(1) # Unpacks b into the result dict, - # raise an exception if b has a value - # for the key 'a' -``` - - -=== LOAD_ATTR(namei) - -Python Versions: ALL - -``` -Stack Prior: ... object -Stack After: ... attribute -``` - -Loads the attribute with the name `co_names[namei]` on the object at the top of the stack; the top of stack is popped. -The value is retrieved by calling the `\\__getattribute__(self, name: str)` method on the type of object with `object` and `co_names[namei]` as the arguments. - -Example Sources: - -```python -# equivalent to -# type(my_object).__getattribute__(my_object, 'attribute') -my_object.attribute -``` - - -=== COMPARE_OP(op) - -Python Versions: ALL - -``` -Stack Prior: ... left_comparable, right_comparable -Stack After: ... comparison_result -``` - -A <> that correspond to the comparison operation indicated by the `op` argument. -The comparison operation that `op` refers to is `cmp_op[op]` (where `cmp_op` is https://github.com/python/cpython/blob/174c4bfd0fee4622657a604af7a2e7d20a3f0dbc/Lib/opcode.py#L24[defined here]). In particular: - -- 0 corresponds to `\\__lt__` (normal) and `\\__gt__` (reflected) -- 1 corresponds to `\\__le__` (normal) and `\\__ge__` (reflected) -- 2 corresponds to `\\__eq__` (normal) and `\\__eq__` (reflected) -- 3 corresponds to `\\__ne__` (normal) and `\\__ne__` (reflected) -- 4 corresponds to `\\__gt__` (normal) and `\\__lt__` (reflected) -- 5 corresponds to `\\__ge__` (normal) and `\\__le__` (reflected) - -The top two items on the top of the stack are popped, the comparison operation is performed, and the result (not necessary a boolean) is pushed to the top of the stack. - -Example Sources: - -```python -left_comparable < right_comparable -``` - -```python -left_comparable == right_comparable -``` - - -=== IS_OP(invert) - -Python Versions: ALL - -``` -Stack Prior: ... left, right -Stack After: ... is_same -``` - -Pop off the top two items on the stack. -Push `True` if the two items refer to the same reference, `False` otherwise. -If `invert == 1`, then the result is negated. - - -Example Sources: - -```python -left = [] -right = [] -left is right # False -``` - -```python -left = [] -right = left -left is right # True -``` - -```python -left is not right -``` - -=== CONTAINS_OP(invert) - -Python Versions: ALL - -``` -Stack Prior: ... query, container -Stack After: ... is_contained -``` - -Pop the two top items off the stack. -The top item is the `container`, and the item immediately below it is the `query`. -If `container` has a `\\__contain__(self, object)` method, it is called, and its result is converted to a boolean value (i.e. `None` is converted to `True`). -Otherwise, an iterator is obtained by calling the `\\__iter__` method on `container`. -If the iterator returns an object equal to `query`, `True` is pushed to the stack. -If the iterator get exhausted before that, `False` is pushed to the stack. -If the iterator is infinite and does not contain `query`, an infinite loop occurs. -If `invert == 1`, then the result is negated. - -Example Sources: - -```python -1 in (1, 2, 3) -``` - -```python -1 not in (1, 2, 3) -``` - - -=== IMPORT_NAME(namei) - -Python Versions: ALL - -``` -Stack Prior: ... level, from_list -Stack After: ... module -``` - -Calls the https://docs.python.org/3/library/functions.html#import__[\_import_] builtin function with the arguments `co_names[namei]`, `globals()`, `locals()`, `from_list` and `level`. -The top two elements of the stack are popped, and the imported module is pushed. -`from_list` can either be `None` or a list of strings containing names to import from the module. -`level` indicates if the import is absolute or relative. -If `level` is `0`, then it is an absolute import (the default). -Otherwise, `level` indicates how many parents directories need to be navigated to perform the relative import (for instance, `1` is same directory as the current module, `2` is parent directory of the current module, `3` is the parent of the parent directory). -The namespace is not modified; that is done by a subsequent <> instruction(s). - -Example Sources: - -```python -import module -``` - -```python -from module import a, b, c -``` - - - -=== IMPORT_FROM(namei) - -Python Versions: ALL - -``` -Stack Prior: ... module -Stack After: ... module, attribute -``` - -Loads the attribute with the name `co_names[namei]` from the module that is on the top of the stack. -The top of stack is not popped, and the loaded attribute is pushed to the top of the stack. -The namespace is not modified; that is done by a subsequent <> instruction(s). - -Example Sources: - -```python -from module import a, b, c -``` - - -=== JUMP_FORWARD(target_delta) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Performs a forced relative jump forward by `target_delta` addresses (see <> for details). -Used to implement skipping unentered blocks in `if...elif...else` blocks and skipping exception handlers in `try...except...finally` blocks. - -Example Sources: - -```python -if cond: - x = 1 - # a JUMP_FORWARD is put here - # to skip the else -else: - x = 2 -return x * 2 -``` - - -=== JUMP_BACKWARD(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Performs a forced relative jump backwards by `target_delta` addresses (see <> for details). -CPython checks for interrupts during this instruction. -Used to implement `for` and `while True` loops. - -Example Sources: - -```python -for item in iterable: - pass - # a JUMP_BACKWARD is put here - # to jump back to the start of - # the FOR_ITER instruction - # (which ends the loop if the - # iterator is exhausted) -``` - -```python -while True: - pass - # a JUMP_BACKWARD is put here - # to jump back to the start of - # the while block -``` - - -=== JUMP_BACKWARD_NO_INTERRUPT(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Performs a forced relative jump backwards by `target_delta` addresses (see <> for details). -CPython does not checks for interrupts during this instruction. -Used to implement `yield from` statements. - -Example Sources: - -```python -yield from generator -# A JUMP_BACKWARD_NO_INTERRUPT is used to -# jump back to the SEND opcode (which will -# break out of the loop when the generator -# is exhausted). -``` - - -=== POP_JUMP_IF_TRUE(target) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is truthy, jump to `target`, which represents an absolute address (see <> for details). -Used to implement going to the next block when there a negated condition in an `if...elif...else` chain or start of a `while` loop. - -Example Sources: - -```python -if not cond: # A POP_JUMP_IF_TRUE - # is put here to jump - # to else if cond is truthy - print('case 1') -else: - print('case 2') -``` - -```python -while not cond: # A POP_JUMP_IF_TRUE - # is put here to skip - # the loop if cond is - # truthy - pass -``` - - -=== POP_JUMP_FORWARD_IF_TRUE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is truthy, jump forward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement going to the next block when there a negated condition in an `if...elif...else` chain. - -Example Sources: - -```python -if not cond: # A POP_JUMP_FORWARD_IF_TRUE - # is put here to jump to else - # if cond is truthy - print('case 1') -else: - print('case 2') -``` - -```python -while not cond: # A POP_JUMP_FORWARD_IF_TRUE - # is put here to skip - # the loop if cond is - # truthy - pass -``` - - -=== POP_JUMP_BACKWARD_IF_TRUE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is truthy, jump backward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement looping in a `while` loop. - -Example Sources: - -```python -while cond: - pass - # A POP_JUMP_BACKWARD_IF_TRUE is put here - # with a test on cond -``` - - -=== POP_JUMP_IF_FALSE(target) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is falsely, jump to `target`, which represents an absolute addresses (see <> for details). -Used to implement going to the next block when there a positive condition in an `if...elif...else` chain. - -Example Sources: - -```python -if cond: # A POP_JUMP_IF_FALSE is - # put here to jump to else - # else if cond is falsely - print('case 1') -else: - print('case 2') -``` - -```python -while cond: # A POP_JUMP_IF_FALSE - # is put here to skip - # the loop if cond is - # falsely - pass -``` - -=== POP_JUMP_FORWARD_IF_FALSE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is falsely, jump forward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement going to the next block when there a positive condition in an `if...elif...else` chain. - -Example Sources: - -```python -if cond: # A POP_JUMP_IF_FALSE is - # put here to jump to else - # else if cond is falsely - print('case 1') -else: - print('case 2') -``` - -```python -while cond: # A POP_JUMP_IF_FALSE - # is put here to skip - # the loop if cond is - # falsely - pass -``` - - -=== POP_JUMP_BACKWARD_IF_FALSE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... condition -Stack After: ... -``` - -If `condition` is falsely, jump backward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement looping in a negated `while` loop. - -Example Sources: - -```python -while not cond: - pass - # A POP_JUMP_BACKWARD_IF_FALSE is put here - # with a test on cond -``` - - -=== POP_JUMP_FORWARD_IF_NOT_NONE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item -Stack After: ... -``` - -If `item` is not None, jump forward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement going to the next block when there a `is None` condition in an `if...elif...else` chain. - -Example Sources: - -```python -if item is None: # POP_JUMP_FORWARD_IF_NOT_NONE - # is put here to jump to else - # if item is not None - print('case 1') -else: - print('case 2') -``` - -```python -# POP_JUMP_FORWARD_IF_NOT_NONE -# is put here to skip -# the loop if cond is -# truthy -while item is None: - - pass -``` - - -=== POP_JUMP_BACKWARD_IF_NOT_NONE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item -Stack After: ... -``` - -If `condition` is falsely, jump backward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement looping in a `while item is not None` loop. - -Example Sources: - -```python -while item is not None: - pass - # A POP_JUMP_BACKWARD_IF_NOT_NONE - # is put here with a test on item -``` - - -=== POP_JUMP_FORWARD_IF_NONE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item -Stack After: ... -``` - -If `item` is None, jump forward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement going to the next block when there a `is not None` condition in an `if...elif...else` chain. - -Example Sources: - -```python -if item is not None: # POP_JUMP_FORWARD_IF_NONE - # is put here to jump to else - # if item is not None - print('case 1') -else: - print('case 2') -``` - -```python -# POP_JUMP_FORWARD_IF_NONE -# is put here to skip -# the loop if cond is -# truthy -while item is not None: - - pass -``` - - -=== POP_JUMP_BACKWARD_IF_NONE(target_delta) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... item -Stack After: ... -``` - -If `condition` is falsely, jump backward by `target_delta`, which represents a relative addresses (see <> for details). -Used to implement looping in a `while item is None` loop. - -Example Sources: - -```python -while item is None: - pass - # A POP_JUMP_BACKWARD_IF_NONE - # is put here with a test on item -``` - - -=== JUMP_IF_NOT_EXC_MATCH(target) - -Python Versions: >= 3.9, \<= 3.10 - -``` -Stack Prior: ... exception_type, test_type -Stack After: ... -``` - -If `exception_type` is not a subclass of `test_type`, jump to `target`, which represents an absolute address (see <> for details). -Used to determine which except block to enter. - -Example Sources: - -```python -try: - pass -except ValueError as e: - # JUMP_IF_NOT_EXC_MATCH is used here - # with type(e), ValueError on the stack - pass -``` - - -=== JUMP_IF_TRUE_OR_POP(target) - -Python Versions: ALL - -``` -Stack Prior: ... item -Stack After if truthy: ... item -Stack After if falsely: ... -``` - -If `item` is truthy, jump to `target` and keep `item` on the stack. -Otherwise, pop `item` from the stack. - -IMPORTANT: In Python 3.10 and below, `target` is an absolute address. In Python 3.11 and above, `target` is a relative address (see <> for details). - -Used to implement `or`. - -Example Sources: - -```python -# if a is truthy, b is not evaluated at all -# since JUMP_IF_TRUE_OR_POP jumps past it -# as such, after this statement, -# the stack is either: -# a, if a is truthy -# b, if a is falsely -a or b -``` - - -=== JUMP_IF_FALSE_OR_POP(target) - -Python Versions: ALL - -``` -Stack Prior: ... item -Stack After if truthy: ... item -Stack After if falsely: ... -``` - -If `item` is falsely, jump to `target` and keep `item` on the stack. -Otherwise, pop `item` from the stack. - -IMPORTANT: In Python 3.10 and below, `target` is an absolute address. In Python 3.11 and above, `target` is a relative address (see <> for details). - -Used to implement `and`. - -Example Sources: - -```python -# if a is falsely, b is not evaluated at all -# since JUMP_IF_FALSE_OR_POP jumps past it -# as such, after this statement, -# the stack is either: -# a, if a is falsely -# b, if a is truthy -a and b -``` - - -=== JUMP_ABSOLUTE(target) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Jump to `target`, which represents an absolute address (see <> for details). -Used to implement looping in `for` and `while` loops. - -Example Sources: - -```python -for item in iterable: - pass - # a JUMP_ABSOLUTE to FOR_ITER is placed here -``` - -```python -while True: - pass - # a JUMP_ABSOLUTE to place here -``` - - -=== FOR_ITER(target_delta) - -Python Versions: ALL - -``` -Stack Prior: ... iterator -Stack After if not exhausted: ... iterator item -Stack After if exhausted: ... -``` - -If the iterator at the top of the stack is exhausted, jump forward by `target_delta`, which represents a relative addresses (see <> for details) and pop iterator off the stack. -Otherwise, keep iterator on the stack, and push its next item (obtained by calling `iterator.\\__next__()`) to the top of the stack. - -In Python code, it looks like this: - -```python -while True: - try: - item = next(iterator) - except StopIteration: - break - # ... The for block -``` - -Used to implement `for` loops. - -Example Sources: - -```python -for item in iterable: - pass -``` - - -=== LOAD_GLOBAL(namei) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After (1): ... global -Stack After (2): ... NULL, global -``` - -Prior 3.11: - -Push the global variable with the name `co_names[namei]` to the top of the stack. - -After 3.11: - -Push the global variable with the name `co_names[namei >> 1]` to the top of the stack. If `namei & 1` is set, push `NULL` before the global variable. - -Used to read global variables. - -Example Sources: - -```python -global variable -# NULL will not be pushed here after 3.11 -variable -``` - -```python -global function -# NULL will be pushed here after 3.11 -function(1,2,3) -``` - - -=== SETUP_FINALLY(target_delta) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... -Stack After: ... -Stack On Exception: ... instruction, stack_size, label, traceback, exception, exception_type -``` - -Creates a try block whose handler is at the given `target_delta` relative address (see <> for details). -The try block starts at this instruction, and ends at the start of its handler. -When an exception occurs, the stack prior to the `SETUP_FINALLY` is restored, and the following is pushed to the stack: - -- The instruction index that created the block (i.e. this `SETUP_FINALLY` address) -- The stack depth at the time the block was created -- The exception handler start address -- The traceback for the exception -- The exception itself -- The type of the exception - -Used to implement try blocks. - -Example Sources: - -```python -try: - # A SETUP_FINALLY is emitted here, which - # points to the except block - pass -except: - pass -``` - - -=== LOAD_FAST(var_num) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... local -``` - -Push the local variable with the name `co_varnames[var_num]` to the top of the stack. -Used to read local variables. - -Example Sources: - -```python -variable -``` - - -=== STORE_FAST(var_num) - -Python Versions: ALL - -``` -Stack Prior: ... value -Stack After: ... -``` - -Pops off the top item on the stack and sets the local variable with the name `co_varnames[var_num]` to it. -Used to set local variables. - -Example Sources: - -```python -variable = value -``` - - -=== DELETE_FAST(var_num) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Deletes the local variable with the name `co_varnames[var_num]`. -Used to implement `del variable`. - -Example Sources: - -```python -del variable -``` - - -=== MAKE_CELL(i) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Creates a new cell in slot `i`. If that slot is not empty then that value is stored into the new cell. -Used to initialize cell variables. -This is a NOP for JPyInterpreter, which initializes cell variables at function definition. - -Example Sources: - -```python -def outer_function(): - # MAKE_CELL will be emitted here for a, - # since it is used in inner_function - a = 10 - def inner_function(): - nonlocal a - print(a) -``` - -```python -def outer_function(a): - # MAKE_CELL will be emitted here for a, - # since it is used in inner_function - def inner_function(): - nonlocal a - print(a) -``` - - -=== COPY_FREE_VARS(n) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Copies the `n` free variables from the closure into the frame. -Removes the need for special code on the caller’s side when calling closures. -This is a NOP for JPyInterpreter, which initializes free variables at function definition. - -Example Sources: - -```python -def outer_function(): - a = 10 - def inner_function(): - # COPY_FREE_VARS(1) is emitted here - nonlocal a - print(a) -``` - -```python -def outer_function(a): - def inner_function(): - # COPY_FREE_VARS(1) is emitted here - nonlocal a - print(a) -``` - - -=== LOAD_CLOSURE(i) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... cell -``` - -Loads the cell (not its value) in slot `i` as described by <<_cell_variable_index>>. -A cell corresponds to either a shared or free variable. -Used to pass shared variables from an outer function to an inner function (where they be free variables). - -Example Sources: - -```python -def outer(): - x = 10 - # LOAD_CLOSURE(x) is generated here - # so x's cell can be put into a tuple - # that is passed to inner's MAKE_FUNCTION - # (allowing inner to access it). - def inner(): - nonlocal x - return x -``` - - -=== LOAD_DEREF(i) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... cell_value -``` - -Loads the value contained in the cell at slot `i` as described by <<_cell_variable_index>>. -A cell corresponds to either a shared or free variable. -Used to read shared and free variables. - -Example Sources: - -```python -def outer(): - x = 10 - print(x) # LOAD_DEREF(x) is used here - # since x is used in inner - def inner(): - nonlocal x - # ... -``` - -```python -def outer(): - x = 10 - def inner(): - nonlocal x - print(x) # LOAD_DEREF(x) is used here - # since x is a free variable -``` - - -=== LOAD_CLASSDEREF(i) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... cell_value -``` - -If locals has a variable corresponding to the name of slot `i`, push its value. -Otherwise, push the value contained in the cell at slot `i` as described by <<_cell_variable_index>>. -A cell corresponds to either a shared or free variable. -Used to read shared and free variables in class bodies. - -Example Sources: - -```python -def outer(): - x = 10 - class InnerClass: - my_value = x # LOAD_CLASSDEREF(x) is - # used here since x is a - # free variable in a class - # body. -``` - - -=== STORE_DEREF(i) - -Python Versions: ALL - -``` -Stack Prior: ... cell_value -Stack After: ... -``` - -Pop off the top of the stack and sets the value contained in the cell at slot `i` as described by <<_cell_variable_index>> to the popped value. -A cell corresponds to either a shared or free variable. -Used to set shared and free variables. - -Example Sources: - -```python -def outer(): - x = 10 # STORE_DEREF(x) is used here - # since x is used in inner - print(x) - def inner(): - nonlocal x - # ... -``` - -```python -def outer(): - x = 10 - def inner(): - nonlocal x - x = 20 # STORE_DEREF(x) is used here - # since x is a free variable - inner() - print(x) # 20 -``` - - -=== DELETE_DEREF(i) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -Deletes the value contained in the cell at slot `i` as described by <<_cell_variable_index>>. -The actual cell is NOT deleted, but has no associated -value. -A cell corresponds to either a shared or free variable. -Used to delete shared and free variables. - -Example Sources: - -```python -def outer(): - x = 10 - del x # DELETE_DEREF(x) is used here - # since x is used in inner - def inner(): - nonlocal x - # ... -``` - -```python -def outer(): - x = 10 - def inner(): - nonlocal x - del x # DELETE_DEREF(x) is used here - # since x is a free variable -``` - - -=== RAISE_VARARGS(argc) - -Python Versions: ALL - -``` -Stack Prior (argc = 0): ... -Stack Prior (argc = 1): ... exception -Stack Prior (argc = 2): ... exception cause -Stack After: N/A -``` - -Does one of three things depends on `argc`: - -- If `argc = 0`, reraise the last raised exception. Used to implement a bare `raise` in an except block. - -- If `argc = 1`, the top of stack is either an exception or an exception type. -If it is an exception instance, raise it; otherwise, create a new instance of the exception type. -Used to implement `raise Exception` and `raise Exception()` - -- If `argc = 2`, the top of stack is an exception and the item immediately below it is an exception or an exception type. -If `exception` is an exception instance, set its `\\__cause__` to `cause` and raise it. Otherwise, construct a new instance of `exception`, set its `\\__cause__` to `cause` and raise it. -Used to implement `raise Exception from cause` and `raise Exception() from cause`. - -Example Sources: - -```python -try: - # ... -except: - raise # argc = 0 -``` - -```python -raise Exception # argc = 1 -``` - -```python -raise Exception() # argc = 1 -``` - -```python -raise Exception from cause # argc = 2 -``` - -```python -raise Exception() from cause # argc = 2 -``` - - -=== KW_NAMES(consti) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -Sets the keyword names for the next <> opcode to the tuple of strings stored in `co_consts[consti]`. -Used to implement calling a function with keyword arguments - -Example Sources: - -```python -# Assume co_const[3] = ('a', 'b'), -# then KW_NAMES(3) would be emitted here -my_function(a=1, b=2) -``` - - -=== PRECALL(argc) - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... -``` - -A NOP. -CPython uses it to allow the specialization of function calls. `argc` is the number of arguments as described in <>. -Used when calling a function. - -Example Sources: - -```python -# A PRECALL(3) is put here -my_function(1, 2, a=3) -``` - - -=== PUSH_NULL - -Python Versions: >= 3.11 - -``` -Stack Prior: ... -Stack After: ... NULL -``` - -Pushes `NULL` to the top of the stack. -Used in the call sequence to match the NULL pushed by <> for non-method calls. - -Example Sources: - -```python -# 3 -# A PUSH_NULL is used here -my_function(1, 2, a=3) -``` - - -=== CALL(argc) - -Python Versions: >= 3.11 - -``` -Stack Prior (1): ... NULL, callable, arg_1, arg_2, ..., arg_{argc} -Stack Prior (2): ... method, object, arg_1, arg_2, ..., arg_{argc} -Stack After: ... return_value -``` - -Calls a function from the top `argc + 2` items on the stack. -The first `argc` items on the stack are the arguments to the function. -For the arguments, the keyword names internal variable length is checked, and the top `len(keyword names)` items are keyword arguments, and the bottom `argc - len(keyword names)` items are positional arguments. -The two items below the arguments are either: - -- An unbound method object and an object -- NULL and an arbitrary callable - -If it is NULL and an arbitrary callable, the given positional and keyword arguments are used. -If it is an unbound method object and an object, object is inserted as the first item in the positional argument list. -All the arguments and the two items below the arguments are popped, and the result of the function call is pushed to the top of the stack. -The keyword names are reset to an empty list after this call. -Used to implement function calls that do not unpack an iterable or mapping. - -Example Sources: - -```python -# This is a CALL in NULL, CALLABLE form -my_function(1, 2, a=3) -``` - -```python -# This is a CALL in METHOD, OBJECT form -my_object.my_function(1, 2, a=3) -``` - - -=== CALL_FUNCTION(argc) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... callable, arg_1, arg_2, ..., arg_{argc} -Stack After: ... return_value -``` - -Calls a function from the top `argc + 1` items on the stack. -The first `argc` items on the stack are the positional arguments to the function (there are no keyword arguments). -The item below the arguments is the callable to call. -All the arguments and the item below them are popped, and the result of the function call is pushed to the top of the stack. -Used to implement function calls without keyword arguments. - -Example Sources: - -```python -# This is CALL_FUNCTION(3) -function(1, 2, 3) -``` - - -=== CALL_FUNCTION_KW(argc) - -Python Versions: \<= 3.10 - -``` -Stack Prior: ... callable, arg_1, arg_2, ..., arg_{argc}, kw_names -Stack After: ... return_value -``` - -Calls a function from the top `argc + 2` items on the stack. -The item on the top of the stack is a tuple containing the names of keyword arguments. -The `argc` items below that are the arguments to the function. -For the arguments, the tuple at the top of the stack length is checked, and the top `len(keyword names)` items are keyword arguments, and the bottom `argc - len(keyword names)` items are positional arguments. -The item below the arguments is the callable to call. -The keyword argument name tuple, arguments and the callable are popped and the result of the function call is pushed to the top of the stack. -Used to implement function calls with keyword arguments. - -Example Sources: - -```python -# This is CALL_FUNCTION_KW(3) with -# the tuple ('arg_1',) at the top of the stack -function(1, 2, arg_1=3) -``` - - -=== CALL_FUNCTION_EX(flags) - -Python Versions: ALL - -``` -Stack Prior (1): ... function, iterable -Stack Prior (2): ... function, iterable, mapping -Stack Prior (3): ... NULL, function, iterable -Stack Prior (4): ... NULL, function, iterable, mapping -Stack After: ... return_value -``` - -There are two modes for this function, controlled by its flags: - -- If `flags & 1` is set, this is a function call with both positional and keyword arguments, and the stack contains `callable`, `iterable`, `mapping`. -The items in the iterable and mapping are unpacked, and are used to make the function call. -Used to implement `function(*iterable, **mapping)` - -- If `flags & 1` is unset, this is a function call with only positional arguments, and the stack contains `callable`, `iterable`. -The items in the iterable are unpacked and are used to make the function call. -Used to implement `function(*iterable)` - -If the Python version is at least 3.11, a `NULL` is put beneath callable. -In all cases, the argument containers, callable (and possibly `NULL`) are popped and the returned value is pushed to the top of the stack. - - -Used to implement function calls that unpack arguments. - -Example Sources: - -```python -# CALL_FUNCTION_EX(0) -function(*iterable) -``` - -```python -# CALL_FUNCTION_EX(0) -function(1, 2, 3, *iterable) -``` - -```python -# CALL_FUNCTION_EX(1) -function(*iterable, **mapping) -``` - -```python -# CALL_FUNCTION_EX(1) -function(**mapping) -``` - -```python -# CALL_FUNCTION_EX(1) -function(1, arg=1, *iterable) -``` - - -=== LOAD_METHOD(namei) - -Python Versions: ALL - -``` -Stack Prior: ... object -Stack After (1): ... unbound_method, object -Stack After (2): ... function, NULL -Stack After (3): ... NULL, function -``` - -Load the method with the name `co_names[namei]` from the object at the top of the stack. -If the method exists (and is an instance method, not a class method, static method or a callable), the unbounded method is put beneath the object at the top of the stack. -Otherwise, the object at the top of the stack is popped, attribute lookup is performed, and the result of the lookup is pushed to the stack (with a NULL either before or after the lookup result depending on the Python version). -Used to implement attribute lookups for function calls. - -Example Sources: - -```python -# This emits LOAD_METHOD -my_object.function() - -# This DOES NOT emit LOAD_METHOD -my_onject.function -``` - - -=== CALL_METHOD(argc) - -Python Versions: \<= 3.10 - -``` -Stack Prior (1): ... method, object, arg_1, ..., arg_{argc} -Stack Prior (2): ... callable, NULL, arg_1, ..., arg_{argc} -Stack After: ... return_value -``` - -Calls a method with `argc` positional arguments (and no keyword arguments). -The top `argc` items on the stack are the positional arguments. -The item immediately below the argument is either an object (which will be used as the self parameter) or NULL. -The item below that is either an unbound method or a callable to call. -All the arguments, the object/NULL, and the method/callable are all popped, and the return value is pushed to the top of the stack. -Used to implement method calls. - -Example Sources: - -```python -# This emits CALL_METHOD -my_object.function() -``` - - -=== MAKE_FUNCTION(flags) - -Python Versions: ALL - -``` -Stack Prior: ... [default_positional_args], [default_keyword_args], [annotation_directory_or_tuple], [cell_tuple], function_code, [function_name] -Stack After: ... function -``` - -Creates a function from the code object. -Expected stack varies depending on `flags` and Python version. - -- If the Python version is prior to 3.11, at the top of the stack is the qualified name of the function, with the code object for the function below it. -Otherwise, at the top of the stack is the code object (and the qualified name of the function is received via the code object). - -- If `flags & 8` is set, the next item below is a tuple containing the closure for the created function. -The closure is a tuple that consists of the cells the inner function shares with the outer function. - -- If `flags & 4` is set, the next item below are the annotations for the created function. -Prior to 3.10, this is a `tuple` containing `(key, value)` pairs. -After 3.10, this is a `dict`. - -- If `flags & 2` is set, the next item below are the default values for keyword-only arguments (as a `dict`). - -- Finally, if `flags & 1` is set, the next item below are the default values for allow-positional arguments (as a `tuple`). - -All these items are popped off the stack and are used to create a new function object, which is pushed to the top of the stack. -Used to create inner functions. - -Example Sources: - -```python -def outer(): - fun_list = [] - for i in range(10): - # MAKE_FUNCTION(1) - def inner(value=i): - return value - fun_list.append(inner) - fun_list[0]() # 0 because it - # the default parameter - # for fun_list[0] -``` - -```python -def outer(): - for i in range(10): - # MAKE_FUNCTION(8) - def inner(): - return i - fun_list.append(inner) - fun_list[0]() # 9 because - # it the current value of i -``` - -```python -def outer(): - # MAKE_FUNCTION(4) - # on the stack is either - # (('a', str),) or {'a': str} - # depending on Python version - def inner(a: str): - return a -``` - -```python -def outer(): - # MAKE_FUNCTION(2) - def inner(*, keyword_arg=1): - return keyword_arg -``` - - -=== BUILD_SLICE(argc) - -Python Versions: ALL - -``` -Stack Prior (argc=2): ... start, end -Stack Prior (argc=3): ... start, end, step -Stack After: ... function -``` - -At the top of the stack is either two or three items depending on `argc`: - -- If `argc = 2`, at the top of the stack are two objects to use as the start and end index of a slice. -- If `argc = 3`, at the top of the stack are three objects to use as the start, end and step of a slice. - -Any items on the stack that are not `int` or `None` are converted to `int` by calling their `\\__index__()` method. -The arguments are popped, and the created slice is pushed to the top of the stack. -Used to implement https://docs.python.org/3/library/functions.html#slice[slice indexing]. - -Example Sources: - -```python -# BUILD_SLICE(2) -my_list[:] -my_list[1:] -my_list[:2] -my_list[1:-1] - -# BUILD_SLICE(3) -my_list[::] -my_list[1::] -my_list[:2:] -my_list[::3] -my_list[1:2:] -my_list[:2:3] -my_list[1:2:3] -``` - - -=== EXTENDED_ARG(ext) - -Python Versions: ALL - -``` -Stack Prior: ... -Stack After: ... -``` - -A NOP. -Used to extend an opcode argument range beyond one byte by prefixing up to three EXTENDED_ARG opcodes before it. - -Example Sources: - -```python -# Dynamic list of length 260, -# which is too large to fit into a byte (256) -# So EXTENDED_ARG is used to extend -# BUILD_LIST argument like so: -# EXTENDED_ARG(0x01) BUILD_LIST(0x04) -# = BUILD_LIST(0x0104 = 260) -[ - a[0x00], a[0x01], a[0x02], a[0x03], - a[0x04], a[0x05], a[0x06], a[0x07], - # ... - a[0xFC], a[0xFD], a[0xFE], a[0xFF], - a[0x100], a[0x101], a[0x102], a[0x103] -] -``` - - -=== FORMAT_VALUE(flags) - -Python Versions: ALL - -``` -Stack Prior: ... object, [format_spec] -Stack After: ... formatted_string -``` - -Formats an object with an optional format specifier string. -Acts different depending on `flags`: - -- If `flags & 4` is set, there is a format specifier on the top of the stack, with the object to format below it. -Otherwise, the object to format is at the top of the stack (and `None` will be used as the format specifier). - -- If `(flags & 3) == 0`, the object is formatted as is. - -- If `(flags & 3) == 1`, the object is converted via `str()` before formatting. - -- If `(flags & 3) == 2`, the object is converted via `repr()` before formatting. - -- If `(flags & 3) == 3`, the object is converted via `ascii()` before formatting. - - -Example Sources: - -```python -f'{my_object}' # FORMAT_VALUE(0) -f'{my_object!s}' # FORMAT_VALUE(1) -f'{my_object!r}' # FORMAT_VALUE(2) -f'{my_object!a}' # FORMAT_VALUE(3) -f'{my_object:my_spec}' # FORMAT_VALUE(4) -f'{my_object!s:my_spec}' # FORMAT_VALUE(5) -``` \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/python-function-structure/python-function-structure.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/python-function-structure/python-function-structure.adoc deleted file mode 100644 index 95cc589d..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/python-function-structure/python-function-structure.adoc +++ /dev/null @@ -1,191 +0,0 @@ -[[pythonFunctionStructure]] -= Python Function Structure - -In Python, the details about a function, including -its implementation and argument spec, are accessible -from the https://docs.python.org/3.11/reference/datamodel.html#index-33[function object]. -In `jpyinterpreter`, this information is stored in the class `PythonCompiledFunction`. -It has several attributes. In particular, we are interested in: - -- `\\__globals__`: The globals dict that is used by the function. All classes defined in the same file share the same globals. Classes defined in different files have different globals. All globals variables are stored in this dict. - -- `\\__closure__`: Variables used by the function that were declared in an outer scope. for instance: -+ -```python -def outer(): - x = 10 - def inner(): - return x - return inner -``` -+ -the function `inner.\\__closure__` would be `[Cell(value=10)]`. Cells are used because changes in the outer function affects the inner function. For instance: -+ -```python -def outer(): - x = 10 - def inner(): - return x - x = 20 - return inner() # returns 20 -``` - -- `\\__defaults__`: contains default values for "allow-positional" arguments. For example, for the function: -+ -```python -def my_function(a, pos_only=1, / , allow_pos=2, * , keyword_only=3): - pass -``` -+ -`\\__defaults__` would be `(1, 2)`. Keyword-only default arguments are instead stored in `\\__kwdefaults__`, which for the above function would be `{'keyword_only': 3}`. - -- `\\__qualname__`: the https://docs.python.org/3.11/glossary.html#term-qualified-name[qualified name] of the function. - -- `\\__annotations__`: the type annotations of the function. For example, for the function: -+ -```python -def typed_function(a: int, b: str) -> 'A': - return A(a, b) -``` -+ -`\\__annotations__` would be `{'a': int, 'b': str, 'return': 'A'}`. Values in the `\\__annotations__` dict should be a type or a string. If it is a string, it represents the type that has the same name as that string. - -- `\\__code__`: The code object of the function, containing the function bytecode and argument spec. Code objects are described below. - -== Code Objects - -https://docs.python.org/3.11/reference/datamodel.html#index-55[Code objects] is the byte-compiled Python function. The primary difference between function objects and code objects is that function objects contain context (i.e. globals, defaults, closure) whereas code objects are context-free. Of its attributes, we are interested in: - -- `co_names`: A tuple of names used by the bytecode. For instance, the function -+ -```python -def my_function(): - a = 1 - b = 2 - return max(a, b) -``` -+ -has `('a', 'b', 'max')` in its `co_names` attribute. It is referenced by the bytecode to load and store globals. For example, `LOAD_GLOBAL 2` means "load the global with the name given by the entry in co_names at index 2" (i.e. `max`). - -- `co_varnames`: a tuple of variable names used by the bytecode (including parameters). For instance, the function: -+ -```python -def outer_function(outer_parameter): - def inner_function(inner_parameter): - my_local = outer_parameter + inner_parameter - return my_local * 2 - return inner_function -``` -+ -`inner_function's` `co_varnames` would be `('inner_parameter', 'my_local', 'outer_parameters')`. `co_varnames` always starts with the function's parameter, followed by local variables used in the function, followed by cell variables used in the function. Cell variables are variables that are stored in a cell. There are two types: free variables, which is a cell from an outer function (in this case, `outer_parameter` is a free variable of `inner_function`), and bound variables, which is a cell that is used in an inner function (in this case, `outer_parameter` is a bound variable of `outer_function`). - -- `co_cellvars`: a tuple of strings representing bound variable. - -- `co_freevars`: a tuple of strings representing free variable. - -- `co_constants`: a tuple of constants used in the function. For instance, the function: -+ -```python -def my_function(): - a = 1 - b = 2 - return a + b -``` -+ -`co_constants` would be `(1, 2)`. In CPython, objects -eligible to be constants are: -+ -** `int` objects (ex: `1`, `42`) -** `float` objects (ex: `1.5`, `NaN`) -** `bool` objects (`True` and `False`) -** `str` objects (ex: `'hello world'`) -** `bytes` objects (ex: `b'hello world'`) -** `None` (`None`) -** `Ellipsis` (`...`) -+ -The following objects are not eligible to be used as constants, and are loaded with `LOAD_GLOBAL` instead: -+ -** `class` objects (ex: `int`, `str`, user defined classes) -** `function` objects (ex: `max`, `range`, user defined functions) -** Instances of user defined classes - -- `co_exceptiontable`: a mapping from bytecode instruction ranges to their exception handler. - The exception handler have the following properties: -+ -** `targetInstruction`: the bytecode index to jump to -** `stackDepth`: the stack depth prior to the try block (required for restoring the stack state) -** `pushLastIndex`: A boolean, that if true, indicates the handler should push the bytecode index that raised the exception prior to pushing the exception (otherwise, only the exception is pushed). -+ -This attribute is only present in Python 3.11 and above; previous versions of Python use bytecode instruction to push and pop exception blocks. - For example, the function: -+ -```python -def my_function(): - for x in range(10): - try: - y = other_function_1(x) - try: - other_function_2(x, y) - except: - print('other 2 exception') - except: - print('other 1 exception') -``` -+ -would have (simplified) bytecode looking like this: -+ -``` -0 LOAD_GLOBAL 0 (range) -1 LOAD_CONSTANT 1 (10) -2 CALL 1 -3 GET_ITER -4 FOR_ITER 19 (23 LOAD_CONSTANT) -5 STORE_LOCAL 0 (x) -6 LOAD_GLOBAL 1 (other_function_1) -7 LOAD_LOCAL 0 (x) -8 CALL 1 -9 STORE_LOCAL 1 (y) -10 LOAD_GLOBAL 2 (other_function_2) -11 LOAD_LOCAL 0 (x) -12 LOAD_LOCAL 1 (y) -13 CALL 2 -14 JUMP_BACKWARDS 10 (4 FOR_ITER) -15 POP_TOP -16 LOAD_GLOBAL 3 (print) -17 LOAD_CONSTANT 2 ('other 2 exception') -18 JUMP_BACKWARDS 14 (4 FOR_ITER) -19 POP_TOP -20 LOAD_GLOBAL 3 (print) -21 LOAD_CONSTANT 2 ('other 1 exception') -22 JUMP_BACKWARDS 18 (4 FOR_ITER) -23 LOAD_CONSTANT 0 (None) -24 RETURN -``` -+ -For simplicity, the code that handles exceptions in exception handlers has been excluded. The above bytecode would have the corresponding exception table below: -+ -``` -Exception Table: -[12, 14) -> 15 (stack-depth: 1) -[5, 18) -> 19 (stack-depth: 1) -``` -+ -Stack depth is 1 because the iterator was on the stack prior to the try block. - -- `co_argcount` the number of allow-positional arguments the function takes. For example, for the function below: -+ -```python -def my_function(a, b, /, c, d=10, *, e=20): - pass -``` -+ -`co_argcount` would be `4`, since `a`, `b`, `c`, and `d` can be specified as a positional argument (`a`, `b` are required positional only arguments, -`c` is a positional-or-keyword required argument, -`d` is a positional-or-keyword optional argument, -`e` is a keyword-only optional argument). - -- `co_kwonlyargcount` is the number of keyword-only arguments. -For the example given in `co_argcount`, it would be `1`, since `e` is the only keyword-only argument. - -- `co_posonlyargcount` is the number of positional-only arguments. -For the example given in `co_argcount`, it would be `2`, since `a` and `b` are the only positional-only arguments. \ No newline at end of file diff --git a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/stack-machines/stack-machines.adoc b/jpyinterpreter/developer-docs/src/modules/ROOT/pages/stack-machines/stack-machines.adoc deleted file mode 100644 index 84b80bca..00000000 --- a/jpyinterpreter/developer-docs/src/modules/ROOT/pages/stack-machines/stack-machines.adoc +++ /dev/null @@ -1,570 +0,0 @@ -[[stackMachineIntroduction]] -= Stack Machines Introduction -:doctype: book -:sectnums: -:icons: font - -NOTE: This section uses a fake stack machine language for educational purposes, and is unrelated to both the Java bytecode machine language and the CPython bytecode machine language. - -[[whatIsAStackMachine]] -== What is a Stack Machine? - -A Stack Machine is a type of virtual machine that uses a stack to store intermediary inputs and results. -For instance, this program: - -```python -def my_fun(x: int): - return x + 1 -``` - -when compiled to a stack machine language, may look like this: - -``` -LOAD_LOCAL 0 (x) -LOAD_CONSTANT 0 (1) -ADD -RETURN -``` - -The stack starts initially empty, and each operation modifies it: - -``` -// [] -LOAD_LOCAL 0 (x) // push x to the stack - -// [x] -LOAD_CONSTANT 0 (1) // push 1 to the stack - -// [x, 1] -ADD // pop top two elements (x, 1) and push their sum - -// [(x+1)] -RETURN // returns TOS (x+1) to the caller -``` - -The stack is often not the only means of storage. Usually, there are local variables that can also be read and written to. For instance, this function: - -```python -def test(x): - a = x + x - b = x * x - return a + b -``` - -when compiled to a stack machine language, may look like this: - -``` -// stack: [] locals: {} -LOAD_LOCAL 0 (x) - -// stack: [x] locals: {} -DUP_TOP - -// stack: [x, x] locals: {} -ADD - -// stack: [(x + x)] locals: {} -STORE_LOCAL 1 (a) - -// stack: [], locals: {a: x+x} -LOAD_LOCAL 0 (x) - -// stack: [x], locals: {a: x+x} -DUP_TOP - -// stack: [x, x], locals: {a: x+x} -MULTIPLY - -// stack: [(x*x)], locals: {a: x+x} -STORE_LOCAL 2 (b) - -// stack: [], locals: {a: x+x, b: x*x} -LOAD_LOCAL 1 (a) - -// stack: [x+x], locals: {a: x+x, b: x*x} -LOAD_LOCAL 2 (b) - -// stack: [x+x, x*x], locals: {a: x+x, b: x*x} -ADD - -// stack: [(x+x) + (x*x)] locals: {a: x+x, b: x*x} -RETURN -``` - -[[jumpsInAStackMachine]] -== Jumps in a Stack Machine - -A stack machine opcode could conditionally jump to a different location. For instance: - -```python -def test(x): - a = 0 - if x < 10: - a += x - return a -``` - -when compiled to a stack machine language, may look like this: - -``` -// stack: [] locals: {} -LOAD_CONSTANT 0 (0) - -// stack: [0] locals: {} -STORE_LOCAL 1 (a) - -// stack: [] locals: {a: 0} -LOAD_LOCAL 0 (x) - -// stack: [x] locals: {a: 0} -LOAD_CONSTANT 1 (10) - -// stack: [x, 1] locals: {a: 0} -LESS_THAN - -// stack: [Type] locals: {a: 0} -POP_JUMP_IF_FALSE skip_if >---------------| - | -// stack: [] locals: {a: 0} | -LOAD_LOCAL 1 (a) | - | -// stack: [a] locals: {a: 0} | -LOAD_LOCAL 0 (x) | - | -// stack: [a, x] locals: {a: 0} | -ADD | - | -// stack: [(a+x)] locals: {a: 0} | -STORE_LOCAL 1 (a) | - | -[label skip_if] | -// stack: [] locals: {a: 0 or (0 + x)} | -LOAD_LOCAL 1 (a) <------------------------| - -// stack: [a] locals: {a: 0 or (0 + x)} -RETURN -``` - -In particular, the instruction after `POP_JUMP_IF_FALSE` will be the instruction after `skip_if` if TOS is False. If TOS is True, it will be the instruction after `POP_JUMP_IF_FALSE` instead. We call the possible next instruction(s) for a given instruction that instruction's target(s). - -This means the instruction after the label `skip_if` has -two possible predecessors: - -- `POP_JUMP_IF_FALSE` if TOS was False -- `STORE_LOCAL (a + x)` if TOS was True - -In most stack based languages, stack state must be consistent. -This means, among other things, the number of elements in the stack for a given opcode must be the same for all possible predecessors. For example, the following stack machine program is invalid: - -``` -// stack: [] -LOAD_LOCAL 0 (x) - -// stack: [x] -LOAD_CONSTANT 0 (10) - -// stack: [x, 10] -LESS_THAN - -// stack: [Type] -POP_JUMP_IF_TRUE skip_if >-----------------------------------------| - | -// stack: [] | -LOAD_CONSTANT 0 (10) | - | -[label skip_if] | -// Cannot compute stack; stack size mismatch [10] (1) vs [] (0) | -LOAD_CONSTANT 0 (10)<----------------------------------------------| - -// ??? -RETURN -``` - -The `if` block pushed an extra element to the stack, but did not pop it, causing an inconsistent stack size after the if -(either 1, if the branch was taken, or 0, if it was not). - -== Differences between Java and the CPython Virtual Machines - -The Java and CPython virtual machines have a number of differences: - -- In Java, the compiler is allowed to introduce "extra" - local variables not declared in the source program. This - is because local variables in the JVM are stored in slots, - and the number of slots does not need to match the number - of local variables. In contrast, every local variable - in the CPython virtual machine correlates to a declared - local variable in the function, which is stored in a - dictionary. This leads to CPython using the stack as - storage for compiler local variables. For instance, -+ -```python -def my_fun(iterable): - total = 0 - for item in iterable: - total += item - return total -``` -+ -translates roughly to -+ -``` -// [], locals: {} -LOAD_CONSTANT 1 (0) - -// [0], locals: {} -STORE_LOCAL 1 (total) - -// [], locals: {total: 0} -LOAD_LOCAL 0 (iterable) - -// [iterable], locals: {total: 0} -GET_ITER - -[forStart]<---------------------------------------------------------| -// [iter(iterable)], locals: {total: int} | -FOR_ITER afterFor >-------------------------------------------------+--| - | | -// [iter(iterable), int], locals: {total: int} | | -STORE_LOCAL 2 (item) | | - | | -// [iter(iterable)], locals: {total: int, item: int} | | -LOAD_LOCAL 1 (total) | | - | | -// [iter(iterable), total], locals: {total: int, item: int} | | -LOAD_LOCAL 2 (item) | | - | | -// [iter(iterable), total, item], locals: {total: int, item: int} | | -BINARY_OP 13 (+=) | | - | | -// [iter(iterable), (total += item)] | | -STORE_LOCAL 1 (total) | | - | | -// [iter(iterable)], locals: {total: int, item: int} | | -GOTO forStart>------------------------------------------------------| | - | -[afterFor]<------------------------------------------------------------| -// [], locals: {total: int, item: int} -LOAD_LOCAL 1 (total) - -// [total], locals: {total: int, item: int} -RETURN_VALUE -``` -+ -Note that despite the fact `iter(iterable)` is not used inside -the for block, it remains on the stack for the entire duration of the for block so it can be reused by `FOR_ITER`. -In Java, the above code instead would roughly translate to -+ -``` -// [], locals: {} -LOAD_CONSTANT 0 (0) - -// [0], locals: {} -STORE_LOCAL 1 (total) - -// [], locals: {total: 0} -LOAD_LOCAL 0 (iterable) - -// [iterable], locals: {total: 0} -INVOKE iterator() - -// [iterable.iterator()], locals: {total: 0} -STORE_LOCAL 2 (iterator) - -[forStart]<---------------------------------------------------------| -// [], locals: {total: 0, iterator: iterator} | -LOAD_LOCAL 2 (iterator) | - | -// [iterator], locals: {total: 0, iterator: iterator} | -INVOKE hasNext() | - | -// [boolean], locals: {total: 0, iterator: iterator} | -JUMP_IF_FALSE afterFor >--------------------------------------------+--| - | | -// [], locals: {total: int} | | -LOAD_LOCAL 2 (iterator) | | - | | -// [iterator], locals: {total: int} | | -INVOKE next | | | - | | -// [int], locals: {total: int} | | -STORE_LOCAL 3 (item) | | - | | -// [], locals: {total: int, item: int} | | -LOAD_LOCAL 1 (total) | | - | | -// [total], locals: {total: int, item: int} | | -LOAD_LOCAL 3 (item) | | - | | -// [total, item], locals: {total: int, item: int} | | -INT_ADD | | - | | -// [total + item] | | -STORE_LOCAL 1 (total) | | - | | -// [], locals: {total: int, item: int} | | -GOTO forStart>------------------------------------------------------| | - | -[afterFor]<------------------------------------------------------------| -// [], locals: {total: int, item: int} -LOAD_LOCAL 1 (total) - -// [total], locals: {total: int, item: int} -RETURN_VALUE -``` -+ -That is, instead of the iterator remaining on the stack, it got stored in a compiler local variable. - -- The stack state is before a try-block is preserved in Python, so it can - be restored when an exception occurs. Thus, when an exception occurs in - CPython, the stack state is `, ` - (where `` is either `, traceback, exception, exception_type` if the Python version is before Python 3.11, `exception` - otherwise). In contrast, after an exception occurs in Java, the stack state is - `exception`. For example, this code: -+ -```python -def my_fun(session_list): - for session in session_list: - try: - session.start() - except IOError as e: - print('Could not start session: ' + str(e)) -``` -+ -translates roughly in python (3.11) to: -+ -``` -// [], {} -LOAD_LOCAL 0 (session_list) - -// [session_list], {} -GET_ITER - -[forStart] -// [iter], {} -FOR_ITER after_for - -[tryStart] -// [iter, any], {} -STORE_LOCAL 1 (session) - -// [iter], {session: any} -LOAD_LOCAL 1 (session) - -// [iter, session], {session: any} -LOAD_METHOD 'start' - -// [iter, session, method], {session: any} -CALL 1 - -// [iter], {session: any} -GOTO forStart - -[except] -// [iter, exception], {} -PUSH_EXC_INFO - -// [iter, exception, exception], {} -LOAD_GLOBAL 2 (IOERROR) - -// [iter, exception, exception, IOError], {} -CHECK_EXC_MATCH - -// [iter, exception, bool], {} -POP_JUMP_FORWARD_IF_FALSE finally - -[ioError] -// [iter, exception], {} -STORE_LOCAL 2 (e) - -// [iter], {e: Error} -LOAD_GLOBAL 5 (print) - -// [iter, print], {e: Error} -LOAD_CONST 1 ('Could not start session: ') - -// [iter, print, msg], {e: Error} -LOAD_GLOBAL 7 (str) - -// [iter, print, msg, str], {e: Error} -LOAD_LOCAL 2 (e) - -// [iter, print, msg, str, e], {e: Error} -CALL 1 - -// [iter, print, msg, str(e)], {e: Error} -BINARY_OP 0 (+) - -// [iter, print, msg + str(e)], {e: Error} -CALL 1 - -// [iter, None], {e: Error} -POP_TOP - -// [iter], {e: Error} -POP_EXCEPT - -// [iter], {e: Error} -LOAD_CONST 0 (None) - -// [iter, None], {e: Error} -STORE_LOCAL 2 (e) - -// [iter], {e: None} -DELETE_LOCAL 2 (e) - -// [iter], {} -GOTO forStart - -[exceptionInExceptFinally] -// [iter, exception], {} -LOAD_CONST 0 (None) - -// [iter, exception, None], {} -STORE_FAST 2 (e) - -// [iter, exception], {e: None} -DELETE_FAST 2 (e) - -// [iter, exception], {} -RERAISE - -[unmatchExceptionTypeFinally] -// [iter, exception], {} -RERAISE - -[exceptionInSetupCleanupFinally] -// [iter, exception], {} -POP_EXCEPT - -// [iter, exception], {} -RERAISE - -[forEnd] -// [], {} -LOAD_CONST 0 (None) - -// [None], {} -RETURN_VALUE - -ExceptionHandlers: - Any: (tryStart, except) -> except - Any: (except, ioError) -> exceptionInSetupCleanupFinally - Any: (ioError, exceptionInExceptFinally) -> exceptionInExceptFinally - Any: (exceptionInExceptFinally, unmatchExceptionTypeFinally) -> exceptionInSetupCleanupFinally -``` -+ -(In 3.10 and below, the ExceptionHandlers are done via the SETUP_FINALLY opcode which create the corresponding try/except blocks). In Java, the above code would instead roughly translate to: -+ -``` -// [], {} -LOAD_LOCAL 0 (session_list) - -// [session_list], {} -INVOKE iterator() - -// [iterator], {} -STORE_LOCAL 3 (iterator) - -[forStart] -// [], {iterator: iterator} -LOAD_LOCAL 3 (iterator) - -// [iterator], {iterator: iterator} -INVOKE hasNext() - -// [bool], {iterator: iterator} -JUMP_IF_FALSE afterFor - -// [], {iterator: iterator} -LOAD_LOCAL 3 (iterator) - -// [iterator], {iterator: iterator} -INVOKE next() - -[tryStart] - -// [item], {iterator: iterator} -STORE_LOCAL 1 (session) - -// [], {iterator: iterator, session: item} -LOAD_LOCAL 1 (session) - -// [item], {iterator: iterator, session: item} -INVOKE start() - -// [], {iterator: iterator, session: item} -GOTO forStart - -[except] -// [exception], {iterator: iterator} -STORE_LOCAL 2 (e) - -// [], {iterator: iterator, e: exception} -LOAD_CONST 1 ('Could not start session: ') - -// [message], {iterator: iterator, e: exception} -LOAD_LOCAL 2 (e) - -// [message, e], {iterator: iterator, e: exception} -INVOKE toString() - -// [message, e.toString()], {iterator: iterator, e: exception} -INVOKE concat(String) - -// [message + e.toString()], {iterator: iterator, e: exception} -INVOKESTATIC print(String) - -// [], {iterator: iterator, e: exception} -GOTO forStart - -[exceptionInExceptFinally] -// [exception], {iterator: iterator} -THROW - -[forEnd] -// [], {iterator: iterator} -LOAD_CONST 0 (None) - -// [None], {iterator: iterator} -RETURN_VALUE - -ExceptionHandlers: - IOError: (tryStart, except) -> except - Any: (tryStart, exceptionInExceptFinally) -> exceptionInSetupCleanupFinally -``` - -- Methods and types are objects on the stack in CPython. In contrast, methods and types are arguments to opcodes in Java. For example, this Python code: -+ -```python -def my_function(obj): - return obj.my_function() -``` -+ -roughly translates to this bytecode in CPython -+ -``` -// [] -LOAD_LOCAL 0 (obj) - -// [obj] -LOAD_METHOD 'my_function' - -// [obj, my_function] -CALL 1 - -// [value] -RETURN_VALUE -``` -+ -In Java, it would roughly translate to this instead: -+ -``` -// [] -LOAD_LOCAL 0 (obj) - -// [obj] -INVOKE ObjectType::my_function() - -// [value] -RETURN_VALUE -``` diff --git a/jpyinterpreter/examples/.gitignore b/jpyinterpreter/examples/.gitignore deleted file mode 100644 index cc3c0ab1..00000000 --- a/jpyinterpreter/examples/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/dist -/*.egg-info -/target -/*-stubs - -# Eclipse, Netbeans and IntelliJ files -/.* -!.gitignore -!.dockerignore -!.mvn -/nbproject -/*.ipr -/*.iws -/*.iml diff --git a/jpyinterpreter/examples/example.py b/jpyinterpreter/examples/example.py deleted file mode 100644 index 111a8596..00000000 --- a/jpyinterpreter/examples/example.py +++ /dev/null @@ -1,51 +0,0 @@ -import jpype.imports -import jpyinterpreter - -jpyinterpreter.init(path=['target/example-1.0.0.jar']) - -from java.util.function import Function - -from org.acme import MyClass - - -def time(iterations, function, argument): - from timeit import default_timer as timer - java_function = jpyinterpreter.translate_python_bytecode_to_java_bytecode(function, Function) - - start = timer() - - result = MyClass.iterate(iterations, argument, function) - - end = timer() - - print(f"Python Result: {result}") - print(f"Time for Python: {end - start}s") - - start = timer() - - result = MyClass.iterate(iterations, argument, java_function) - - end = timer() - - print(f"Java Result (translated from Python bytecode): {result}") - print(f"Time for Java (translated from Python bytecode): {end - start}s") - - -def test1(arg): - return arg + 1 - -def test2(arg): - index = 0 - while index <= 10: - index += 1 - return arg + 1 - -def test3(arg): - index = 0 - while index <= 100: - index += 1 - return arg + 1 - -time(100000, test1, 0) -time(100000, test2, 0) -time(100000, test3, 0) diff --git a/jpyinterpreter/examples/pom.xml b/jpyinterpreter/examples/pom.xml deleted file mode 100644 index 6a7b3f60..00000000 --- a/jpyinterpreter/examples/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - ai.timefold.solver - timefold-solver-build-parent - 1.7.0 - - - org.acme - example - Example - 1.0.0 - - - acme - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - analyze-only - none - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.jboss.logmanager.LogManager - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - diff --git a/jpyinterpreter/examples/src/main/java/org/acme/MyClass.java b/jpyinterpreter/examples/src/main/java/org/acme/MyClass.java deleted file mode 100644 index 47d91a62..00000000 --- a/jpyinterpreter/examples/src/main/java/org/acme/MyClass.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.acme; - -import java.util.function.Function; - -public class MyClass { - public static T iterate(int times, T start, Function reducer) { - T current = start; - for (int i = 0; i < times; i++) { - current = reducer.apply(current); - } - return current; - } -} diff --git a/jpyinterpreter/mvnw b/jpyinterpreter/mvnw deleted file mode 100755 index 633bbb74..00000000 --- a/jpyinterpreter/mvnw +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -(CYGWIN*|MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ] ; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v java)" || : - JAVACCMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v javac)" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ] ; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - h=$(( ( h * 31 + $(LC_CTYPE=C printf %d "'$str") ) % 4294967296 )) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl="${value-}" ;; - distributionSha256Sum) distributionSha256Sum="${value-}" ;; - esac -done < "${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - - -case "${distributionUrl##*/}" in -(maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - (*AMD64:CYGWIN*|*AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - (:Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - (:Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - (:Linux*x86_64*) distributionPlatform=linux-amd64 ;; - (*) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -(maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -(*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -(*?-bin.zip|*?maven-mvnd-?*-?*.zip) ;; -(*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget > /dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl > /dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat > "$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( new java.net.URL( args[0] ).openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum > /dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c > /dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c > /dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip > /dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" -fi -printf %s\\n "$distributionUrl" > "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/jpyinterpreter/mvnw.cmd b/jpyinterpreter/mvnw.cmd deleted file mode 100644 index dd02e16f..00000000 --- a/jpyinterpreter/mvnw.cmd +++ /dev/null @@ -1,145 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/jpyinterpreter/pom.xml b/jpyinterpreter/pom.xml deleted file mode 100644 index 5b6bcca9..00000000 --- a/jpyinterpreter/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - ai.timefold.solver - timefold-solver-python-parent - 999-SNAPSHOT - ../pom.xml - - 4.0.0 - jpyinterpreter - - - ai.timefold.jpyinterpreter - 9.7 - - - - - - org.ow2.asm - asm-util - ${version.ow2.asm} - - - org.ow2.asm - asm-tree - ${version.ow2.asm} - - - - - - - ai.timefold.solver - timefold-solver-core - - - - org.ow2.asm - asm - - - org.ow2.asm - asm-util - - - org.ow2.asm - asm-tree - - - - - org.apache.commons - commons-collections4 - - - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - runtime - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-dependencies - - copy-dependencies - - - - dependency-classpath - - build-classpath - - - target/classpath.txt - - - - - - - diff --git a/jpyinterpreter/pyproject.toml b/jpyinterpreter/pyproject.toml deleted file mode 100644 index 3228d3a0..00000000 --- a/jpyinterpreter/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = [ - "setuptools>=69.1.1", - "stubgenj>=0.2.5", - "JPype1>=1.4.1", - "wheel" -] -build-backend = "setuptools.build_meta" diff --git a/jpyinterpreter/setup.py b/jpyinterpreter/setup.py deleted file mode 100644 index 0f1b6359..00000000 --- a/jpyinterpreter/setup.py +++ /dev/null @@ -1,88 +0,0 @@ -try: - from setuptools import setup -except ImportError: - from distutils.core import setup -from distutils.command.build_py import build_py -import glob -import os -import platform -import subprocess -from pathlib import Path -from shutil import copyfile -import sys - -class FetchDependencies(build_py): - """ - A command class that fetch Java Dependencies and - add them as files within a python package - """ - def create_stubs(self, project_root, command): - subprocess.run([str((project_root / command).absolute()), 'dependency:copy-dependencies'], - cwd=project_root, check=True) - subprocess.run([str((project_root / command).absolute()), 'dependency:copy-dependencies', - '-Dclassifier=javadoc'], cwd=project_root, check=True) - - - def run(self): - if not self.dry_run: - project_root = Path(__file__).parent - # Do a mvn clean install - # which is configured to add dependency jars to 'target/dependency' - command = 'mvnw' - if platform.system() == 'Windows': - command = 'mvnw.cmd' - self.create_stubs(project_root, command) - subprocess.run([str((project_root / command).absolute()), 'clean', 'install'], cwd=project_root, check=True) - classpath_jars = [] - # Add the main artifact - classpath_jars.extend(glob.glob(os.path.join(project_root, 'target', '*.jar'))) - # Add the main artifact's dependencies - classpath_jars.extend(glob.glob(os.path.join(project_root, 'target', 'dependency', '*.jar'))) - self.mkpath(os.path.join(self.build_lib, 'jpyinterpreter', 'jars')) - - # Copy classpath jars to jpyinterpreter.jars - for file in classpath_jars: - copyfile(file, os.path.join(self.build_lib, 'jpyinterpreter', 'jars', os.path.basename(file))) - - # Make jpyinterpreter.jars a Python module - fp = open(os.path.join(self.build_lib, 'jpyinterpreter', 'jars', '__init__.py'), 'w') - fp.close() - build_py.run(self) - -this_directory = Path(__file__).parent - -setup( - name='jpyinterpreter', - version='0.0.0a0', - license='Apache License Version 2.0', - license_file='LICENSE', - description='A Python bytecode to Java bytecode translator', - classifiers=[ - 'Development Status :: 1 - Planning', - 'Programming Language :: Python :: 3', - 'Topic :: Software Development :: Libraries :: Java Libraries', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent' - ], - packages=['jpyinterpreter', 'java-stubs', 'jpype-stubs', 'org-stubs'], - package_dir={ - 'jpyinterpreter': 'src/main/python', - # Setup tools need a non-empty directory to use as base - # Since these packages are generated during the build, - # we use the src/main/resources package, which does - # not contain any python files and is already included - # in the build - 'java-stubs': 'src/main/resources', - 'jpype-stubs': 'src/main/resources', - 'org-stubs': 'src/main/resources', - }, - test_suite='tests', - python_requires='>=3.10', - install_requires=[ - 'JPype1>=1.5.0', - ], - cmdclass={'build_py': FetchDependencies}, - package_data={ - 'jpyinterpreter.jars': ['*.jar'], - }, -) diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/AnnotationMetadata.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/AnnotationMetadata.java deleted file mode 100644 index f3593da0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/AnnotationMetadata.java +++ /dev/null @@ -1,102 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.annotation.Annotation; -import java.lang.annotation.Repeatable; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; - -public record AnnotationMetadata(Class annotationType, Map annotationValueMap) { - public void addAnnotationTo(ClassVisitor classVisitor) { - visitAnnotation(classVisitor.visitAnnotation(Type.getDescriptor(annotationType), true)); - } - - public void addAnnotationTo(FieldVisitor fieldVisitor) { - visitAnnotation(fieldVisitor.visitAnnotation(Type.getDescriptor(annotationType), true)); - } - - public void addAnnotationTo(MethodVisitor methodVisitor) { - visitAnnotation(methodVisitor.visitAnnotation(Type.getDescriptor(annotationType), true)); - } - - public static List getAnnotationListWithoutRepeatable(List metadata) { - List out = new ArrayList<>(); - Map, List> repeatableAnnotationMap = new LinkedHashMap<>(); - for (AnnotationMetadata annotation : metadata) { - Repeatable repeatable = annotation.annotationType().getAnnotation(Repeatable.class); - if (repeatable == null) { - out.add(annotation); - continue; - } - var annotationContainer = repeatable.value(); - repeatableAnnotationMap.computeIfAbsent(annotationContainer, - ignored -> new ArrayList<>()).add(annotation); - } - for (var entry : repeatableAnnotationMap.entrySet()) { - out.add(new AnnotationMetadata(entry.getKey(), - Map.of("value", entry.getValue().toArray(AnnotationMetadata[]::new)))); - } - return out; - } - - public static Type getValueAsType(String className) { - return Type.getType("L" + className.replace('.', '/') + ";"); - } - - private void visitAnnotation(AnnotationVisitor annotationVisitor) { - for (var entry : annotationValueMap.entrySet()) { - var annotationAttributeName = entry.getKey(); - var annotationAttributeValue = entry.getValue(); - - visitAnnotationAttribute(annotationVisitor, annotationAttributeName, annotationAttributeValue); - } - annotationVisitor.visitEnd(); - } - - private void visitAnnotationAttribute(AnnotationVisitor annotationVisitor, String attributeName, Object attributeValue) { - if (attributeValue instanceof Number - || attributeValue instanceof Boolean - || attributeValue instanceof Character - || attributeValue instanceof String) { - annotationVisitor.visit(attributeName, attributeValue); - return; - } - - if (attributeValue instanceof Type type) { - annotationVisitor.visit(attributeName, type); - return; - } - - if (attributeValue instanceof AnnotationMetadata annotationMetadata) { - annotationMetadata.visitAnnotation( - annotationVisitor.visitAnnotation(attributeName, Type.getDescriptor(annotationMetadata.annotationType))); - return; - } - - if (attributeValue instanceof Enum enumValue) { - annotationVisitor.visitEnum(attributeName, Type.getDescriptor(enumValue.getClass()), - enumValue.name()); - return; - } - - if (attributeValue.getClass().isArray()) { - var arrayAnnotationVisitor = annotationVisitor.visitArray(attributeName); - var arrayLength = Array.getLength(attributeValue); - for (int i = 0; i < arrayLength; i++) { - visitAnnotationAttribute(arrayAnnotationVisitor, attributeName, Array.get(attributeValue, i)); - } - arrayAnnotationVisitor.visitEnd(); - return; - } - throw new IllegalArgumentException("Annotation of type %s has an illegal value %s for attribute %s." - .formatted(annotationType, attributeValue, attributeName)); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/BytecodeSwitchImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/BytecodeSwitchImplementor.java deleted file mode 100644 index 5fc60ed6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/BytecodeSwitchImplementor.java +++ /dev/null @@ -1,164 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class BytecodeSwitchImplementor { - - public static void createStringSwitch(MethodVisitor methodVisitor, Collection keyNames, - int switchVariable, Consumer caseWriter, Runnable defaultCase, boolean doesEachCaseReturnEarly) { - if (keyNames.isEmpty()) { - defaultCase.run(); - return; - } - - // keys in lookup switch MUST be sorted - SortedMap> hashCodeToMatchingFieldList = new TreeMap<>(); - for (String fieldName : keyNames) { - hashCodeToMatchingFieldList.computeIfAbsent(fieldName.hashCode(), hash -> new ArrayList<>()).add(fieldName); - } - - int[] keys = new int[hashCodeToMatchingFieldList.size()]; - Label[] hashCodeLabels = new Label[keys.length]; - - { // Scoped to hide keyIndex - int keyIndex = 0; - for (Integer key : hashCodeToMatchingFieldList.keySet()) { - keys[keyIndex] = key; - hashCodeLabels[keyIndex] = new Label(); - keyIndex++; - } - } - - final int TOS_VARIABLE = switchVariable; - final int CASE_VARIABLE = TOS_VARIABLE + 1; - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ASTORE, TOS_VARIABLE); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(String.class), "hashCode", - Type.getMethodDescriptor(Type.INT_TYPE), false); - Map switchVariableValueToField = new HashMap<>(); - - Label endOfKeySwitch = new Label(); - - methodVisitor.visitLdcInsn(-1); - methodVisitor.visitVarInsn(Opcodes.ISTORE, CASE_VARIABLE); - - methodVisitor.visitLookupSwitchInsn(endOfKeySwitch, keys, hashCodeLabels); - - int totalEntries = 0; - for (int i = 0; i < keys.length; i++) { - methodVisitor.visitLabel(hashCodeLabels[i]); - List matchingFields = hashCodeToMatchingFieldList.get(keys[i]); - - for (int fieldIndex = 0; fieldIndex < matchingFields.size(); fieldIndex++) { - String field = matchingFields.get(fieldIndex); - Label ifDoesNotMatchLabel = new Label(); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, TOS_VARIABLE); - methodVisitor.visitLdcInsn(field); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(String.class), "equals", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), false); - if (fieldIndex != matchingFields.size() - 1) { - methodVisitor.visitJumpInsn(Opcodes.IFEQ, ifDoesNotMatchLabel); - } else { - methodVisitor.visitJumpInsn(Opcodes.IFEQ, endOfKeySwitch); - } - methodVisitor.visitLdcInsn(totalEntries); - methodVisitor.visitVarInsn(Opcodes.ISTORE, CASE_VARIABLE); - if (fieldIndex != matchingFields.size() - 1) { - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfKeySwitch); - methodVisitor.visitLabel(ifDoesNotMatchLabel); - } - - switchVariableValueToField.put(totalEntries, field); - totalEntries++; - } - if (totalEntries != keyNames.size()) { - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfKeySwitch); - } - } - methodVisitor.visitLabel(endOfKeySwitch); - - Label missingField = new Label(); - Label endOfFieldsSwitch = new Label(); - Label[] fieldHandlerLabels = new Label[totalEntries]; - for (int i = 0; i < fieldHandlerLabels.length; i++) { - fieldHandlerLabels[i] = new Label(); - } - - methodVisitor.visitVarInsn(Opcodes.ILOAD, CASE_VARIABLE); - methodVisitor.visitTableSwitchInsn(0, totalEntries - 1, missingField, fieldHandlerLabels); - - for (int i = 0; i < totalEntries; i++) { - methodVisitor.visitLabel(fieldHandlerLabels[i]); - String field = switchVariableValueToField.get(i); - caseWriter.accept(field); - if (!doesEachCaseReturnEarly) { - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfFieldsSwitch); - } - } - methodVisitor.visitLabel(missingField); - defaultCase.run(); - if (!doesEachCaseReturnEarly) { - methodVisitor.visitLabel(endOfFieldsSwitch); - } - } - - public static void createIntSwitch(MethodVisitor methodVisitor, Collection keySet, - Consumer caseWriter, Runnable defaultCase, - boolean doesEachCaseReturnEarly) { - if (keySet.isEmpty()) { - defaultCase.run(); - return; - } - - List sortedKeyList = keySet.stream().sorted().collect(Collectors.toList()); - - int[] keys = new int[sortedKeyList.size()]; - Label[] keyLabels = new Label[keys.length]; - - { // Scoped to hide keyIndex - int keyIndex = 0; - for (Integer key : sortedKeyList) { - keys[keyIndex] = key; - keyLabels[keyIndex] = new Label(); - keyIndex++; - } - } - - Label endOfKeySwitch = new Label(); - Label missingKey = new Label(); - - methodVisitor.visitLookupSwitchInsn(missingKey, keys, keyLabels); - - for (int i = 0; i < keys.length; i++) { - methodVisitor.visitLabel(keyLabels[i]); - Integer matchingKey = sortedKeyList.get(i); - - caseWriter.accept(matchingKey); - if (!doesEachCaseReturnEarly) { - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfKeySwitch); - } - } - methodVisitor.visitLabel(missingKey); - - defaultCase.run(); - - if (!doesEachCaseReturnEarly) { - methodVisitor.visitLabel(endOfKeySwitch); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CPythonBackedPythonInterpreter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CPythonBackedPythonInterpreter.java deleted file mode 100644 index 90f08c83..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CPythonBackedPythonInterpreter.java +++ /dev/null @@ -1,213 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonModule; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.PythonTraceback; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; -import ai.timefold.jpyinterpreter.types.wrappers.PythonObjectWrapper; -import ai.timefold.jpyinterpreter.util.ConcurrentWeakIdentityHashMap; -import ai.timefold.jpyinterpreter.util.function.PentaFunction; -import ai.timefold.jpyinterpreter.util.function.QuadConsumer; -import ai.timefold.jpyinterpreter.util.function.QuadFunction; -import ai.timefold.jpyinterpreter.util.function.TriFunction; - -public class CPythonBackedPythonInterpreter implements PythonInterpreter { - InputStream standardInput; - PrintStream standardOutput; - Scanner inputScanner; - - Map moduleSpecToModuleMap = new HashMap<>(); - - final Set hasReferenceSet = - Collections.newSetFromMap(new ConcurrentWeakIdentityHashMap<>()); - - public static Map pythonObjectIdToConvertedObjectMap = new HashMap<>(); - - public static Function lookupPythonReferenceIdPythonFunction; - - public static Function lookupPythonReferenceTypePythonFunction; - public static BiFunction lookupAttributeOnPythonReferencePythonFunction; - public static BiFunction lookupPointerForAttributeOnPythonReferencePythonFunction; - public static BiFunction lookupPointerArrayForAttributeOnPythonReferencePythonFunction; - public static BiConsumer, String> loadObjectFromPythonGlobalDict; - - public static TriFunction, PythonLikeObject> lookupAttributeOnPythonReferenceWithMapPythonFunction; - public static QuadConsumer setAttributeOnPythonReferencePythonFunction; - public static BiConsumer deleteAttributeOnPythonReferencePythonFunction; - public static BiFunction, Map> lookupDictOnPythonReferencePythonFunction; - public static TriFunction, Map, PythonLikeObject> callPythonFunction; - - public static PentaFunction, Map, List, Long, PythonModule> importModuleFunction; - public static QuadFunction, PythonLikeTuple, PythonString, PythonObjectWrapper> createFunctionFromCodeFunction; - - public CPythonBackedPythonInterpreter() { - this(System.in, System.out); - } - - public CPythonBackedPythonInterpreter(InputStream standardInput, PrintStream standardOutput) { - this.standardInput = standardInput; - this.standardOutput = standardOutput; - this.inputScanner = new Scanner(standardInput); - } - - public static Number getPythonReferenceId(OpaquePythonReference reference) { - return lookupPythonReferenceIdPythonFunction.apply(reference); - } - - public static OpaquePythonReference getPythonReferenceType(OpaquePythonReference reference) { - return lookupPythonReferenceTypePythonFunction.apply(reference); - } - - public static PythonLikeObject lookupAttributeOnPythonReference(OpaquePythonReference object, String attribute) { - return lookupAttributeOnPythonReferencePythonFunction.apply(object, attribute); - } - - public static PythonLikeObject lookupAttributeOnPythonReference(OpaquePythonReference object, String attribute, - Map map) { - return lookupAttributeOnPythonReferenceWithMapPythonFunction.apply(object, attribute, map); - } - - public static OpaquePythonReference lookupPointerForAttributeOnPythonReference(OpaquePythonReference object, - String attribute) { - return lookupPointerForAttributeOnPythonReferencePythonFunction.apply(object, attribute); - } - - public static OpaquePythonReference[] lookupPointerArrayForAttributeOnPythonReference(OpaquePythonReference object, - String attribute) { - return lookupPointerArrayForAttributeOnPythonReferencePythonFunction.apply(object, attribute); - } - - public static void setAttributeOnPythonReference(OpaquePythonReference object, OpaquePythonReference cloneMap, - String attribute, Object value) { - setAttributeOnPythonReferencePythonFunction.accept(object, cloneMap, attribute, value); - } - - public static void deleteAttributeOnPythonReference(OpaquePythonReference object, String attribute) { - deleteAttributeOnPythonReferencePythonFunction.accept(object, attribute); - } - - public static Map getPythonReferenceDict(OpaquePythonReference object, - Map referenceMap) { - return lookupDictOnPythonReferencePythonFunction.apply(object, referenceMap); - } - - public static void updateJavaObjectFromPythonObject(CPythonBackedPythonLikeObject javaObject, - OpaquePythonReference pythonObject, - Map instanceMap) { - javaObject.$setInstanceMap(instanceMap); - javaObject.$setCPythonReference(pythonObject); - javaObject.$readFieldsFromCPythonReference(); - } - - public static void updateJavaObjectFromPythonObject(PythonLikeObject javaObject, - OpaquePythonReference pythonObject, - Map instanceMap) { - Map dict = getPythonReferenceDict(pythonObject, instanceMap); - dict.forEach(javaObject::$setAttribute); - } - - public static PythonLikeObject callPythonReference(OpaquePythonReference object, List positionalArguments, - Map keywordArguments) { - return callPythonFunction.apply(object, positionalArguments, keywordArguments); - } - - public static PythonObjectWrapper createPythonFunctionWrapper( - OpaquePythonReference codeObject, - Map globals, - PythonLikeTuple closure, - PythonString name) { - return createFunctionFromCodeFunction.apply(codeObject, globals, closure, name); - } - - @Override - public boolean hasValidPythonReference(PythonLikeObject instance) { - return hasReferenceSet.contains(instance); - } - - @Override - public void setPythonReference(PythonLikeObject instance, OpaquePythonReference reference) { - if (instance instanceof CPythonBackedPythonLikeObject backedObject) { - backedObject.$cpythonReference = reference; - backedObject.$cpythonId = PythonInteger.valueOf(getPythonReferenceId(reference).longValue()); - hasReferenceSet.add(backedObject); - } else { - throw new IllegalArgumentException( - "Can only call this method on %s objects.".formatted(CPythonBackedPythonLikeObject.class.getSimpleName())); - } - } - - @Override - public PythonLikeObject getGlobal(Map globalsMap, String name) { - if (!globalsMap.containsKey(name)) { - // This will put 'null' in the map if it doesn't exist, so we don't - // do an expensive CPython lookup everytime we are getting an attribute - loadObjectFromPythonGlobalDict.accept(globalsMap, name); - } - PythonLikeObject out = globalsMap.get(name); - if (out == null) { - return GlobalBuiltins.lookupOrError(this, name); - } - return out; - } - - @Override - public void setGlobal(Map globalsMap, String name, PythonLikeObject value) { - globalsMap.put(name, value); - } - - @Override - public void deleteGlobal(Map globalsMap, String name) { - globalsMap.remove(name); - } - - @Override - public PythonModule importModule(PythonInteger level, List fromList, Map globalsMap, - Map localsMap, String moduleName) { - // See https://docs.python.org/3/library/functions.html#import__ for semantics - ModuleSpec moduleSpec = new ModuleSpec(level, fromList, globalsMap, localsMap, moduleName); - - return moduleSpecToModuleMap.computeIfAbsent(moduleSpec, spec -> { - Long theLevel = level.getValue().longValue(); - List importNameList = new ArrayList<>(fromList.size()); - for (PythonString name : fromList) { - importNameList.add(name.getValue()); - } - - return importModuleFunction.apply(moduleName, globalsMap, localsMap, importNameList, theLevel); - }); - } - - @Override - public void write(String output) { - standardOutput.print(output); - } - - @Override - public String readLine() { - // TODO: Raise EOFError on end of file - return inputScanner.nextLine(); - } - - @Override - public PythonTraceback getTraceback() { - // TODO: Implement this with an actually traceback - return new PythonTraceback(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java deleted file mode 100644 index 8ef59bd1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Objects; - -public enum CompareOp { - LESS_THAN("<", "__lt__"), - LESS_THAN_OR_EQUALS("<=", "__le__"), - EQUALS("==", "__eq__"), - NOT_EQUALS("!=", "__ne__"), - GREATER_THAN(">", "__gt__"), - GREATER_THAN_OR_EQUALS(">=", "__ge__"); - - public final String id; - public final String dunderMethod; - - CompareOp(String id, String dunderMethod) { - this.id = id; - this.dunderMethod = dunderMethod; - } - - public static CompareOp getOpForDunderMethod(String dunderMethod) { - for (CompareOp op : CompareOp.values()) { - if (op.dunderMethod.equals(dunderMethod)) { - return op; - } - } - throw new IllegalArgumentException("No Op corresponds to dunder method (" + dunderMethod + ")"); - } - - public static CompareOp getOp(String id) { - for (CompareOp op : CompareOp.values()) { - if (Objects.equals(op.id, id)) { - return op; - } - } - throw new IllegalArgumentException("No Op corresponds to id (" + id + ")"); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ExceptionBlock.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ExceptionBlock.java deleted file mode 100644 index e98eed49..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ExceptionBlock.java +++ /dev/null @@ -1,102 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Objects; -import java.util.Set; - -public class ExceptionBlock { - - /** - * The first instruction in the try block - */ - final int blockStartInstructionInclusive; - - /** - * The first instruction after the try block - */ - final int blockEndInstructionExclusive; - - /** - * Where to jump to if an exception happens - */ - final int targetInstruction; - - /** - * The expected stack size for this exception - */ - final int stackDepth; - - /** - * If true, push the offset that the exception was raised at before pushing the exception. - */ - final boolean pushLastIndex; - - public ExceptionBlock(int blockStartInstructionInclusive, int blockEndInstructionExclusive, int targetInstruction, - int stackDepth, boolean pushLastIndex) { - this.blockStartInstructionInclusive = blockStartInstructionInclusive; - this.blockEndInstructionExclusive = blockEndInstructionExclusive; - this.targetInstruction = targetInstruction; - this.stackDepth = stackDepth; - this.pushLastIndex = pushLastIndex; - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder().append(blockStartInstructionInclusive).append(" to ") - .append(blockEndInstructionExclusive) - .append(" -> ").append(targetInstruction).append(" [").append(stackDepth).append("]"); - if (pushLastIndex) { - out.append(" lasti"); - } - return out.toString(); - } - - public int getBlockStartInstructionInclusive() { - return blockStartInstructionInclusive; - } - - public int getBlockEndInstructionExclusive() { - return blockEndInstructionExclusive; - } - - public int getTargetInstruction() { - return targetInstruction; - } - - public int getStackDepth() { - return stackDepth; - } - - public boolean isPushLastIndex() { - return pushLastIndex; - } - - public boolean containsAnyTargetInSet(Set possibleJumpTargetSet) { - for (int target : possibleJumpTargetSet) { - if (target <= blockStartInstructionInclusive && target < blockEndInstructionExclusive) { - return true; - } - } - return false; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExceptionBlock that = (ExceptionBlock) o; - return blockStartInstructionInclusive == that.blockStartInstructionInclusive - && blockEndInstructionExclusive == that.blockEndInstructionExclusive - && targetInstruction == that.targetInstruction && stackDepth == that.stackDepth - && pushLastIndex == that.pushLastIndex; - } - - @Override - public int hashCode() { - return Objects.hash(blockStartInstructionInclusive, blockEndInstructionExclusive, targetInstruction, stackDepth, - pushLastIndex); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FieldDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FieldDescriptor.java deleted file mode 100644 index 2bbb7255..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FieldDescriptor.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public record FieldDescriptor(String pythonFieldName, String javaFieldName, String declaringClassInternalName, - String javaFieldTypeDescriptor, PythonLikeType fieldPythonLikeType, - boolean isTrueFieldDescriptor, boolean isJavaType) { - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FieldDescriptor that = (FieldDescriptor) o; - return pythonFieldName.equals(that.pythonFieldName) && javaFieldName.equals(that.javaFieldName) - && declaringClassInternalName.equals(that.declaringClassInternalName) - && javaFieldTypeDescriptor.equals(that.javaFieldTypeDescriptor) - && fieldPythonLikeType.equals(that.fieldPythonLikeType) - && isTrueFieldDescriptor == that.isTrueFieldDescriptor; - } - - @Override - public String toString() { - return "FieldDescriptor{" + - "pythonFieldName='" + pythonFieldName + '\'' + - ", javaFieldName='" + javaFieldName + '\'' + - ", declaringClassInternalName='" + declaringClassInternalName + '\'' + - ", javaFieldTypeDescriptor='" + javaFieldTypeDescriptor + '\'' + - ", fieldPythonLikeType=" + fieldPythonLikeType + - ", isTrueFieldDescriptor=" + isTrueFieldDescriptor + - '}'; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FunctionMetadata.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FunctionMetadata.java deleted file mode 100644 index 1e0a8055..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/FunctionMetadata.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.List; -import java.util.Map; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - -public class FunctionMetadata { - - public FunctionMetadata() { - - } - - public PythonFunctionType functionType; - public MethodDescriptor method; - public String className; - public MethodVisitor methodVisitor; - public PythonCompiledFunction pythonCompiledFunction; - public Map bytecodeCounterToLabelMap; - public Map> bytecodeCounterToCodeArgumenterList; -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/GeneratorLocalVariableHelper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/GeneratorLocalVariableHelper.java deleted file mode 100644 index a4bad3a2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/GeneratorLocalVariableHelper.java +++ /dev/null @@ -1,248 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import ai.timefold.jpyinterpreter.types.PythonCell; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class GeneratorLocalVariableHelper extends LocalVariableHelper { - - final ClassWriter classWriter; - - final String classInternalName; - - final int cellStart; - - final int freeStart; - - int maxTemps; - - Map slotToLocalName; - - Map slotToLocalTypeDescriptor; - - public GeneratorLocalVariableHelper(ClassWriter classWriter, String classInternalName, - Type[] parameters, PythonCompiledFunction compiledFunction) { - super(parameters, compiledFunction); - this.classWriter = classWriter; - this.classInternalName = classInternalName; - cellStart = - compiledFunction.co_varnames.size(); - freeStart = compiledFunction.co_varnames.size() + compiledFunction.co_cellvars.size(); - slotToLocalName = new HashMap<>(); - slotToLocalTypeDescriptor = new HashMap<>(); - - for (int i = 0; i < compiledFunction.co_varnames.size(); i++) { - slotToLocalName.put(i, compiledFunction.co_varnames.get(i)); - } - - for (int i = 0; i < compiledFunction.co_cellvars.size(); i++) { - slotToLocalName.put(i + compiledFunction.co_varnames.size(), compiledFunction.co_cellvars.get(i)); - } - - for (int i = 0; i < compiledFunction.co_freevars.size(); i++) { - slotToLocalName.put(i + compiledFunction.co_varnames.size() + compiledFunction.co_cellvars.size(), - compiledFunction.co_freevars.get(i)); - } - - // Cannot use parameter types as the type descriptor, since the variables assigned to the - // Python parameter can change types in the middle of code - for (int i = 0; i < compiledFunction.co_varnames.size(); i++) { - slotToLocalTypeDescriptor.put(i, Type.getDescriptor(PythonLikeObject.class)); - } - - for (int i = 0; i < compiledFunction.co_cellvars.size(); i++) { - slotToLocalTypeDescriptor.put(i + compiledFunction.co_varnames.size(), Type.getDescriptor(PythonCell.class)); - } - - for (int i = 0; i < compiledFunction.co_freevars.size(); i++) { - slotToLocalTypeDescriptor.put(i + compiledFunction.co_varnames.size() + compiledFunction.co_cellvars.size(), - Type.getDescriptor(PythonCell.class)); - } - } - - GeneratorLocalVariableHelper(Type[] parameters, int argcount, int parameterSlotsEnd, int pythonCellVariablesStart, - int pythonFreeVariablesStart, int pythonLocalVariablesSlotEnd, - int pythonBoundVariables, int pythonFreeVariables, Map boundCellIndexToVariableIndex, - int currentExceptionVariableSlot, int callKeywordsSlot, Map exceptionTableTargetToSavedStackMap, - ClassWriter classWriter, String classInternalName, int maxTemps, - int cellStart, int freeStart, Map slotToLocalName, - Map slotToLocalTypeDescriptor) { - super(parameters, argcount, parameterSlotsEnd, pythonCellVariablesStart, - pythonFreeVariablesStart, pythonLocalVariablesSlotEnd, - pythonBoundVariables, pythonFreeVariables, boundCellIndexToVariableIndex, - currentExceptionVariableSlot, callKeywordsSlot, exceptionTableTargetToSavedStackMap); - this.classWriter = classWriter; - this.classInternalName = classInternalName; - this.maxTemps = maxTemps; - this.cellStart = cellStart; - this.freeStart = freeStart; - this.slotToLocalName = new HashMap<>(slotToLocalName); - this.slotToLocalTypeDescriptor = new HashMap<>(slotToLocalTypeDescriptor); - } - - public GeneratorLocalVariableHelper copy() { - GeneratorLocalVariableHelper out = new GeneratorLocalVariableHelper(parameters, argcount, parameterSlotsEnd, - pythonCellVariablesStart, - pythonFreeVariablesStart, pythonLocalVariablesSlotEnd, pythonBoundVariables, pythonFreeVariables, - boundCellIndexToVariableIndex, currentExceptionVariableSlot, callKeywordsSlot, - exceptionTableTargetToSavedStackMap, - classWriter, classInternalName, maxTemps, cellStart, freeStart, slotToLocalName, slotToLocalTypeDescriptor); - out.usedLocals = usedLocals; - return out; - } - - private String slotToFieldName(int slot) { - return "$temp" + slot; - } - - @Override - public int newLocal() { - int slot = pythonLocalVariablesSlotEnd + usedLocals; - usedLocals++; - if (usedLocals > maxTemps) { - maxTemps = usedLocals; - classWriter.visitField(Modifier.PRIVATE, slotToFieldName(slot), Type.getDescriptor(Object.class), null, null); - } - return slot; - } - - @Override - public void freeLocal() { - usedLocals--; - } - - @Override - public int getUsedLocals() { - return usedLocals; - } - - @Override - public void readLocal(MethodVisitor methodVisitor, int local) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, slotToLocalName.get(local), - slotToLocalTypeDescriptor.get(local)); - } - - @Override - public void writeLocal(MethodVisitor methodVisitor, int local) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, slotToLocalName.get(local), - slotToLocalTypeDescriptor.get(local)); - } - - @Override - public void readCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, slotToLocalName.get(cellStart + cell), - slotToLocalTypeDescriptor.get(cellStart + cell)); - } - - @Override - public void writeCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, slotToLocalName.get(cellStart + cell), - slotToLocalTypeDescriptor.get(cellStart + cell)); - } - - @Override - public void writeFreeCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, slotToLocalName.get(freeStart + cell), - slotToLocalTypeDescriptor.get(freeStart + cell)); - } - - @Override - public void readCurrentException(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, PythonGeneratorTranslator.CURRENT_EXCEPTION, - Type.getDescriptor(Throwable.class)); - } - - @Override - public void writeCurrentException(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, PythonGeneratorTranslator.CURRENT_EXCEPTION, - Type.getDescriptor(Throwable.class)); - } - - @Override - public void readExceptionTableTargetStack(MethodVisitor methodVisitor, int target) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, - PythonGeneratorTranslator.exceptionHandlerTargetStackLocal(target), - Type.getDescriptor(PythonLikeObject[].class)); - } - - @Override - public void writeExceptionTableTargetStack(MethodVisitor methodVisitor, int target) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, - PythonGeneratorTranslator.exceptionHandlerTargetStackLocal(target), - Type.getDescriptor(PythonLikeObject[].class)); - } - - @Override - public void readTemp(MethodVisitor methodVisitor, Type type, int temp) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, slotToFieldName(temp), - Type.getDescriptor(Object.class)); - - switch (type.getSort()) { - case Type.OBJECT: - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName()); - return; - - case Type.INT: { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Integer.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", - Type.getMethodDescriptor(Type.INT_TYPE), false); - } - - default: { - throw new IllegalArgumentException("Unsupported sort: " + type.getSort()); - } - } - } - - @Override - public void writeTemp(MethodVisitor methodVisitor, Type type, int temp) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - - switch (type.getSort()) { - case Type.OBJECT: - break; - - case Type.INT: { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", - Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); - } - - default: { - throw new IllegalArgumentException("Unsupported sort: " + type.getSort()); - } - } - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, slotToFieldName(temp), - Type.getDescriptor(Object.class)); - } - - @Override - public void incrementTemp(MethodVisitor methodVisitor, int temp) { - readTemp(methodVisitor, Type.INT_TYPE, temp); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IADD); - writeTemp(methodVisitor, Type.INT_TYPE, temp); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterfaceProxyGenerator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterfaceProxyGenerator.java deleted file mode 100644 index 5e6e1ee3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterfaceProxyGenerator.java +++ /dev/null @@ -1,292 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Set; - -import ai.timefold.jpyinterpreter.implementors.DelegatingInterfaceImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.util.MethodVisitorAdapters; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class InterfaceProxyGenerator { - /** - * Generate an interface that just calls an existing instance of the interface. - * Needed so Java libraries that construct new instances using the no-args - * constructor use the correct instance of the function (the one with __closure__ - * and other instance fields). - */ - public static Class generateProxyForFunction(Class interfaceClass, T interfaceInstance) { - String maybeClassName = interfaceInstance.getClass().getCanonicalName() + "$Proxy"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - var classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(Object.class), new String[] { Type.getInternalName(interfaceClass) }); - - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, "proxy", - Type.getDescriptor(interfaceClass), null, null); - - var constructor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), null, null); - - // Generates Proxy() {} - constructor.visitCode(); - constructor.visitVarInsn(Opcodes.ALOAD, 0); - constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - constructor.visitInsn(Opcodes.RETURN); - constructor.visitMaxs(0, 0); - constructor.visitEnd(); - - var interfaceMethod = PythonBytecodeToJavaBytecodeTranslator.getFunctionalInterfaceMethod(interfaceClass); - var interfaceMethodDescriptor = Type.getMethodDescriptor(interfaceMethod); - - // Generates interfaceMethod(A a, B b, ...) { return proxy.interfaceMethod(a, b, ...); } - var interfaceMethodVisitor = classWriter.visitMethod(Modifier.PUBLIC, interfaceMethod.getName(), - interfaceMethodDescriptor, null, null); - - for (var parameter : interfaceMethod.getParameters()) { - interfaceMethodVisitor.visitParameter(parameter.getName(), 0); - } - interfaceMethodVisitor.visitCode(); - interfaceMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, "proxy", - Type.getDescriptor(interfaceClass)); - for (int i = 0; i < interfaceMethod.getParameterCount(); i++) { - interfaceMethodVisitor.visitVarInsn(Type.getType(interfaceMethod.getParameterTypes()[i]).getOpcode(Opcodes.ILOAD), - i + 1); - } - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(interfaceClass), - interfaceMethod.getName(), interfaceMethodDescriptor, true); - interfaceMethodVisitor.visitInsn(Type.getType(interfaceMethod.getReturnType()).getOpcode(Opcodes.IRETURN)); - interfaceMethodVisitor.visitMaxs(0, 0); - interfaceMethodVisitor.visitEnd(); - - classWriter.visitEnd(); - - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - compiledClass.getField("proxy").set(null, interfaceInstance); - return compiledClass; - } catch (ClassNotFoundException e) { - throw new IllegalStateException(("Impossible State: Unable to load generated class (%s)" + - " despite it being just generated.").formatted(className), e); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException(("Impossible State: Unable to access field on generated class (%s).") - .formatted(className), e); - } - } - - /** - * Generate an interface that construct a new instance of a type and delegate all calls to that type's methods. - */ - public static Class generateProxyForClass(Class interfaceClass, PythonLikeType delegateType) { - String maybeClassName = delegateType.getClass().getCanonicalName() + "$" + interfaceClass.getSimpleName() + "$Proxy"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - var classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(Object.class), new String[] { Type.getInternalName(interfaceClass) }); - - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, "delegate", - delegateType.getJavaTypeDescriptor(), null, null); - - var createdNameSet = new HashSet(); - for (var interfaceMethod : interfaceClass.getMethods()) { - addArgumentSpecFieldForMethod(classWriter, delegateType, interfaceMethod, createdNameSet); - } - - var constructor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), null, null); - - // Generates Proxy() { - // delegate = new Delegate(); - // } - constructor.visitCode(); - constructor.visitVarInsn(Opcodes.ALOAD, 0); - constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - constructor.visitVarInsn(Opcodes.ALOAD, 0); - constructor.visitTypeInsn(Opcodes.NEW, delegateType.getJavaTypeInternalName()); - constructor.visitInsn(Opcodes.DUP); - constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, delegateType.getJavaTypeInternalName(), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - constructor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, "delegate", delegateType.getJavaTypeDescriptor()); - constructor.visitInsn(Opcodes.RETURN); - constructor.visitMaxs(0, 0); - constructor.visitEnd(); - - for (var interfaceMethod : interfaceClass.getMethods()) { - createMethodDelegate(classWriter, internalClassName, delegateType, interfaceMethod); - } - - classWriter.visitEnd(); - - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - for (var interfaceMethod : interfaceClass.getMethods()) { - if (!interfaceMethod.getDeclaringClass().isInterface()) { - continue; - } - if (interfaceMethod.isDefault()) { - // Default method, does not need to be present - var methodType = delegateType.getMethodType(interfaceMethod.getName()); - if (methodType.isEmpty()) { - continue; - } - var function = methodType.get().getDefaultFunctionSignature(); - if (function.isEmpty()) { - continue; - } - compiledClass.getField("argumentSpec$" + interfaceMethod.getName()).set(null, - function.get().getArgumentSpec()); - } else { - compiledClass.getField("argumentSpec$" + interfaceMethod.getName()).set(null, - delegateType.getMethodType(interfaceMethod.getName()) - .orElseThrow() - .getDefaultFunctionSignature() - .orElseThrow() - .getArgumentSpec()); - } - } - return compiledClass; - } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { - throw new IllegalStateException(("Impossible State: Unable to load generated class (%s)" + - " despite it being just generated.").formatted(className), e); - } - } - - private static void addArgumentSpecFieldForMethod(ClassWriter classWriter, - PythonLikeType delegateType, Method interfaceMethod, Set createdNameSet) { - if (createdNameSet.contains(interfaceMethod.getName()) || !interfaceMethod.getDeclaringClass().isInterface()) { - return; - } - var methodType = delegateType.getMethodType(interfaceMethod.getName()); - if (methodType.isEmpty()) { - if (interfaceMethod.isDefault()) { - return; - } - throw new IllegalArgumentException("Type %s cannot implement interface %s because it missing method %s." - .formatted(delegateType, interfaceMethod.getDeclaringClass(), interfaceMethod)); - } - var function = methodType.get().getDefaultFunctionSignature(); - if (function.isEmpty()) { - throw new IllegalStateException(); - } - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, "argumentSpec$" + interfaceMethod.getName(), - Type.getDescriptor(ArgumentSpec.class), null, null); - createdNameSet.add(interfaceMethod.getName()); - } - - private static void createMethodDelegate(ClassWriter classWriter, - String wrapperInternalName, - PythonLikeType delegateType, Method interfaceMethod) { - if (!interfaceMethod.getDeclaringClass().isInterface()) { - return; - } - if (interfaceMethod.isDefault()) { - // Default method, does not need to be present - var methodType = delegateType.getMethodType(interfaceMethod.getName()); - if (methodType.isEmpty()) { - return; - } - var function = methodType.get().getDefaultFunctionSignature(); - if (function.isEmpty()) { - return; - } - } - var interfaceMethodDescriptor = Type.getMethodDescriptor(interfaceMethod); - - // Generates interfaceMethod(A a, B b, ...) { return delegate.interfaceMethod(a, b, ...); } - var interfaceMethodVisitor = classWriter.visitMethod(Modifier.PUBLIC, interfaceMethod.getName(), - interfaceMethodDescriptor, null, null); - - interfaceMethodVisitor = - MethodVisitorAdapters.adapt(interfaceMethodVisitor, interfaceMethod.getName(), interfaceMethodDescriptor); - - for (var parameter : interfaceMethod.getParameters()) { - interfaceMethodVisitor.visitParameter(parameter.getName(), 0); - } - interfaceMethodVisitor.visitCode(); - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - interfaceMethodVisitor.visitFieldInsn(Opcodes.GETFIELD, wrapperInternalName, "delegate", - delegateType.getJavaTypeDescriptor()); - interfaceMethodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IdentityHashMap.class)); - interfaceMethodVisitor.visitInsn(Opcodes.DUP); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IdentityHashMap.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - interfaceMethodVisitor.visitVarInsn(Opcodes.ASTORE, interfaceMethod.getParameterCount() + 1); - - interfaceMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC, wrapperInternalName, - "argumentSpec$" + interfaceMethod.getName(), - Type.getDescriptor(ArgumentSpec.class)); - - var functionSignature = delegateType.getMethodType(interfaceMethod.getName()) - .orElseThrow(() -> new IllegalArgumentException( - "Type %s cannot implement interface %s because it missing method %s." - .formatted(delegateType, interfaceMethod.getDeclaringClass(), interfaceMethod))) - .getDefaultFunctionSignature() - .orElseThrow(); - DelegatingInterfaceImplementor.prepareParametersForMethodCallFromArgumentSpec( - interfaceMethod, interfaceMethodVisitor, functionSignature.getParameterTypes().length, - Type.getType(functionSignature.getMethodDescriptor().getMethodDescriptor()), - false); - - functionSignature.getMethodDescriptor().callMethod(interfaceMethodVisitor); - - var returnType = interfaceMethod.getReturnType(); - if (returnType.equals(void.class)) { - interfaceMethodVisitor.visitInsn(Opcodes.RETURN); - } else { - if (returnType.isPrimitive()) { - DelegatingInterfaceImplementor.loadBoxedPrimitiveTypeClass(returnType, interfaceMethodVisitor); - } else { - interfaceMethodVisitor.visitLdcInsn(Type.getType(returnType)); - } - interfaceMethodVisitor.visitInsn(Opcodes.SWAP); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "convertPythonObjectToJavaType", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Class.class), Type.getType( - PythonLikeObject.class)), - false); - if (returnType.isPrimitive()) { - DelegatingInterfaceImplementor.unboxBoxedPrimitiveType(returnType, interfaceMethodVisitor); - interfaceMethodVisitor.visitInsn(Type.getType(returnType).getOpcode(Opcodes.IRETURN)); - } else { - interfaceMethodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(returnType)); - interfaceMethodVisitor.visitInsn(Opcodes.ARETURN); - } - } - interfaceMethodVisitor.visitMaxs(interfaceMethod.getParameterCount() + 2, 1); - interfaceMethodVisitor.visitEnd(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterpreterStartupOptions.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterpreterStartupOptions.java deleted file mode 100644 index 10b92de5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/InterpreterStartupOptions.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.nio.file.Path; - -/** - * A class that holds startup options for the interpreter that are used when the JVM starts - */ -public final class InterpreterStartupOptions { - - /** - * Where to output class files; defaults to null (which cause not class files to not be written) - */ - public static Path classOutputRootPath = null; -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/LocalVariableHelper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/LocalVariableHelper.java deleted file mode 100644 index e32e401f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/LocalVariableHelper.java +++ /dev/null @@ -1,247 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class LocalVariableHelper { - - public final Type[] parameters; - public final int argcount; - public final int parameterSlotsEnd; - public final int pythonCellVariablesStart; - public final int pythonFreeVariablesStart; - public final int pythonLocalVariablesSlotEnd; - - public final int pythonBoundVariables; - public final int pythonFreeVariables; - public final Map boundCellIndexToVariableIndex; - - public final int currentExceptionVariableSlot; - public final int callKeywordsSlot; - public final Map exceptionTableTargetToSavedStackMap; - - int usedLocals; - - public LocalVariableHelper(Type[] parameters, PythonCompiledFunction compiledFunction) { - this.argcount = compiledFunction.totalArgCount(); - this.parameters = parameters; - int slotsUsedByParameters = 1; - for (Type parameter : parameters) { - if (parameter.equals(Type.LONG_TYPE) || parameter.equals(Type.DOUBLE_TYPE)) { - slotsUsedByParameters += 2; - } else { - slotsUsedByParameters += 1; - } - } - - pythonBoundVariables = compiledFunction.co_cellvars.size(); - pythonFreeVariables = compiledFunction.co_freevars.size(); - parameterSlotsEnd = slotsUsedByParameters; - pythonCellVariablesStart = parameterSlotsEnd + compiledFunction.co_varnames.size(); - pythonFreeVariablesStart = pythonCellVariablesStart + pythonBoundVariables; - currentExceptionVariableSlot = pythonFreeVariablesStart + pythonFreeVariables; - callKeywordsSlot = currentExceptionVariableSlot + 1; - exceptionTableTargetToSavedStackMap = new HashMap<>(); - for (int target : compiledFunction.co_exceptiontable.getJumpTargetSet()) { - exceptionTableTargetToSavedStackMap.put(target, callKeywordsSlot + 1 + exceptionTableTargetToSavedStackMap.size()); - } - pythonLocalVariablesSlotEnd = callKeywordsSlot + 1 + exceptionTableTargetToSavedStackMap.size(); - boundCellIndexToVariableIndex = new HashMap<>(); - for (int i = 0; i < compiledFunction.co_cellvars.size(); i++) { - for (int j = 0; j < compiledFunction.co_varnames.size(); j++) { - if (compiledFunction.co_cellvars.get(i).equals(compiledFunction.co_varnames.get(j))) { - boundCellIndexToVariableIndex.put(i, j); - break; - } - } - if (!boundCellIndexToVariableIndex.containsKey(i)) { - boundCellIndexToVariableIndex.put(i, pythonCellVariablesStart + i); - } - } - } - - LocalVariableHelper(Type[] parameters, int argcount, int parameterSlotsEnd, int pythonCellVariablesStart, - int pythonFreeVariablesStart, int pythonLocalVariablesSlotEnd, - int pythonBoundVariables, int pythonFreeVariables, Map boundCellIndexToVariableIndex, - int currentExceptionVariableSlot, int callKeywordsSlot, Map exceptionTableTargetToSavedStackMap) { - this.argcount = argcount; - this.parameters = parameters; - this.parameterSlotsEnd = parameterSlotsEnd; - this.pythonCellVariablesStart = pythonCellVariablesStart; - this.pythonFreeVariablesStart = pythonFreeVariablesStart; - this.pythonLocalVariablesSlotEnd = pythonLocalVariablesSlotEnd; - this.pythonBoundVariables = pythonBoundVariables; - this.pythonFreeVariables = pythonFreeVariables; - this.boundCellIndexToVariableIndex = boundCellIndexToVariableIndex; - this.currentExceptionVariableSlot = currentExceptionVariableSlot; - this.callKeywordsSlot = callKeywordsSlot; - this.exceptionTableTargetToSavedStackMap = exceptionTableTargetToSavedStackMap; - } - - public LocalVariableHelper copy() { - LocalVariableHelper out = new LocalVariableHelper(parameters, argcount, parameterSlotsEnd, pythonCellVariablesStart, - pythonFreeVariablesStart, pythonLocalVariablesSlotEnd, - pythonBoundVariables, pythonFreeVariables, boundCellIndexToVariableIndex, currentExceptionVariableSlot, - callKeywordsSlot, exceptionTableTargetToSavedStackMap); - out.usedLocals = usedLocals; - return out; - } - - public int getParameterSlot(int parameterIndex) { - if (parameterIndex > parameters.length) { - throw new IndexOutOfBoundsException("Asked for the slot corresponding to the (" + parameterIndex + ") " + - "parameter, but there are only (" + parameters.length + ") parameters (" + Arrays.toString(parameters) - + ")."); - } - int slotsUsedByParameters = 1; - for (int i = 0; i < parameterIndex; i++) { - if (parameters[i].equals(Type.LONG_TYPE) || parameters[i].equals(Type.DOUBLE_TYPE)) { - slotsUsedByParameters += 2; - } else { - slotsUsedByParameters += 1; - } - } - return slotsUsedByParameters; - } - - public int getPythonLocalVariableSlot(int index) { - return parameterSlotsEnd + index; - } - - public int getPythonCellOrFreeVariableSlot(int index) { - return pythonCellVariablesStart + index; - } - - public int getCurrentExceptionVariableSlot() { - return currentExceptionVariableSlot; - } - - public int getCallKeywordsSlot() { - return callKeywordsSlot; - } - - public int getNumberOfFreeCells() { - return pythonFreeVariables; - } - - public int getNumberOfBoundCells() { - return pythonBoundVariables; - } - - public int getNumberOfCells() { - return pythonBoundVariables + pythonFreeVariables; - } - - public int getNumberOfLocalVariables() { - return pythonCellVariablesStart - parameterSlotsEnd; - } - - public int newLocal() { - int slot = pythonLocalVariablesSlotEnd + usedLocals; - usedLocals++; - return slot; - } - - public void freeLocal() { - usedLocals--; - } - - public int getUsedLocals() { - return usedLocals; - } - - public void readLocal(MethodVisitor methodVisitor, int local) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, getPythonLocalVariableSlot(local)); - } - - public void writeLocal(MethodVisitor methodVisitor, int local) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, getPythonLocalVariableSlot(local)); - } - - public void readCellInitialValue(MethodVisitor methodVisitor, int cell) { - if (boundCellIndexToVariableIndex.containsKey(cell)) { - int boundedVariable = boundCellIndexToVariableIndex.get(cell); - if (boundedVariable >= argcount) { // not a parameter - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } else { // it is a parameter - readLocal(methodVisitor, boundCellIndexToVariableIndex.get(cell)); - } - } else { - throw new IllegalStateException("Cannot find corresponding slot for bounded cell " + cell + " in map " - + boundCellIndexToVariableIndex); - } - } - - public void readCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, getPythonCellOrFreeVariableSlot(cell)); - } - - public void writeCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, getPythonCellOrFreeVariableSlot(cell)); - } - - public void writeFreeCell(MethodVisitor methodVisitor, int cell) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, pythonFreeVariablesStart + cell); - } - - public void readCurrentException(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, getCurrentExceptionVariableSlot()); - } - - public void writeCurrentException(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, getCurrentExceptionVariableSlot()); - } - - public int getExceptionTableTargetStackSlot(int target) { - return exceptionTableTargetToSavedStackMap.get(target); - } - - public void readExceptionTableTargetStack(MethodVisitor methodVisitor, int target) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, getExceptionTableTargetStackSlot(target)); - } - - public void writeExceptionTableTargetStack(MethodVisitor methodVisitor, int target) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, getExceptionTableTargetStackSlot(target)); - } - - public void setupInitialStoredExceptionStacks(MethodVisitor methodVisitor) { - for (Integer target : exceptionTableTargetToSavedStackMap.keySet()) { - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - writeExceptionTableTargetStack(methodVisitor, target); - } - } - - public void readCallKeywords(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, getCallKeywordsSlot()); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeTuple.class)); - } - - public void writeCallKeywords(MethodVisitor methodVisitor) { - methodVisitor.visitVarInsn(Opcodes.ASTORE, getCallKeywordsSlot()); - } - - public void resetCallKeywords(MethodVisitor methodVisitor) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonLikeTuple.class), "EMPTY", - Type.getDescriptor(PythonLikeTuple.class)); - methodVisitor.visitVarInsn(Opcodes.ASTORE, getCallKeywordsSlot()); - } - - public void readTemp(MethodVisitor methodVisitor, Type type, int temp) { - methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), temp); - } - - public void writeTemp(MethodVisitor methodVisitor, Type type, int temp) { - methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ISTORE), temp); - } - - public void incrementTemp(MethodVisitor methodVisitor, int temp) { - methodVisitor.visitIincInsn(temp, 1); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/MethodDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/MethodDescriptor.java deleted file mode 100644 index 358679c6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/MethodDescriptor.java +++ /dev/null @@ -1,220 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Constructor; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class MethodDescriptor { - private final String declaringClassInternalName; - private final String methodName; - private final String methodDescriptor; - - private final MethodType methodType; - - private static Type resolveGenericType(java.lang.reflect.Type type, TypeVariable[] interfaceTypeVariables, - List> typeArgumentList) { - if (type instanceof Class) { - return Type.getType((Class) type); - } else if (type instanceof TypeVariable) { - for (int i = 0; i < interfaceTypeVariables.length; i++) { - if (interfaceTypeVariables[i].equals(type)) { - return Type.getType(typeArgumentList.get(i)); - } - } - throw new IllegalStateException("Unknown TypeVariable " + type); - } else if (type instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) type; - java.lang.reflect.Type[] lowerBounds = wildcardType.getLowerBounds(); - java.lang.reflect.Type[] upperBounds = wildcardType.getUpperBounds(); - - if (lowerBounds.length > 0) { - return resolveGenericType(lowerBounds[0], interfaceTypeVariables, typeArgumentList); - } - - if (upperBounds.length > 0) { - return resolveGenericType(upperBounds[0], interfaceTypeVariables, typeArgumentList); - } - - throw new IllegalStateException("Wildcard Type " + wildcardType + " has no upper or lower bounds."); - } else if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - return resolveGenericType(parameterizedType.getRawType(), interfaceTypeVariables, typeArgumentList); - } else if (type instanceof GenericArrayType) { - GenericArrayType genericArrayType = (GenericArrayType) type; - return Type.getType('[' + - resolveGenericType(genericArrayType.getGenericComponentType(), - interfaceTypeVariables, - typeArgumentList).getDescriptor()); - } else { - throw new IllegalArgumentException("Unknown class (" + type.getClass() + ") of argument (" + type + ")"); - } - } - - public MethodDescriptor(Class interfaceClass, Method method, List> typeArgumentList) { - TypeVariable[] interfaceTypeVariables = interfaceClass.getTypeParameters(); - if (interfaceTypeVariables.length != typeArgumentList.size()) { - throw new IllegalArgumentException("Type argument list (" + typeArgumentList + ") does not have same size as " + - interfaceClass + " generic argument list (" + Arrays.toString(interfaceTypeVariables) + ")"); - } - this.declaringClassInternalName = Type.getInternalName(method.getDeclaringClass()); - this.methodName = method.getName(); - if (method.getDeclaringClass().isInterface()) { - this.methodType = MethodType.INTERFACE; - } else if (Modifier.isStatic(method.getModifiers())) { - this.methodType = MethodType.STATIC; - } else { - this.methodType = MethodType.VIRTUAL; - } - - String methodDescriptorString = ""; - Type returnType = resolveGenericType(method.getGenericReturnType(), interfaceTypeVariables, typeArgumentList); - Type[] argumentTypes = new Type[method.getParameterCount()]; - for (int i = 0; i < argumentTypes.length; i++) { - argumentTypes[i] = - resolveGenericType(method.getGenericParameterTypes()[i], interfaceTypeVariables, typeArgumentList); - } - this.methodDescriptor = Type.getMethodDescriptor(returnType, argumentTypes); - } - - public MethodDescriptor(Method method) { - this.declaringClassInternalName = Type.getInternalName(method.getDeclaringClass()); - this.methodName = method.getName(); - this.methodDescriptor = Type.getMethodDescriptor(method); - if (method.getDeclaringClass().isInterface()) { - this.methodType = MethodType.INTERFACE; - } else if (Modifier.isStatic(method.getModifiers())) { - this.methodType = MethodType.STATIC; - } else { - this.methodType = MethodType.VIRTUAL; - } - } - - public MethodDescriptor(Method method, MethodType type) { - this.declaringClassInternalName = Type.getInternalName(method.getDeclaringClass()); - this.methodName = method.getName(); - this.methodDescriptor = Type.getMethodDescriptor(method); - this.methodType = type; - } - - public MethodDescriptor(Constructor constructor) { - this.declaringClassInternalName = Type.getInternalName(constructor.getDeclaringClass()); - this.methodName = constructor.getName(); - this.methodDescriptor = Type.getConstructorDescriptor(constructor); - this.methodType = MethodType.CONSTRUCTOR; - } - - public MethodDescriptor(String declaringClassInternalName, MethodType methodType, String methodName, - String methodDescriptor) { - this.declaringClassInternalName = declaringClassInternalName; - this.methodName = methodName; - this.methodDescriptor = methodDescriptor; - this.methodType = methodType; - } - - public MethodDescriptor(Class declaringClass, MethodType methodType, - String methodName, Class returnType, Class... parameterTypes) { - this.declaringClassInternalName = Type.getInternalName(declaringClass); - this.methodName = methodName; - Type[] parameterAsmTypes = new Type[parameterTypes.length]; - for (int i = 0; i < parameterAsmTypes.length; i++) { - parameterAsmTypes[i] = Type.getType(parameterTypes[i]); - } - this.methodDescriptor = Type.getMethodDescriptor(Type.getType(returnType), parameterAsmTypes); - this.methodType = methodType; - } - - public static MethodDescriptor useStaticMethodAsVirtual(Method method) { - return new MethodDescriptor(method.getDeclaringClass(), MethodType.STATIC_AS_VIRTUAL, - method.getName(), method.getReturnType(), method.getParameterTypes()); - } - - public String getDeclaringClassInternalName() { - return declaringClassInternalName; - } - - public String getMethodName() { - return methodName; - } - - public String getMethodDescriptor() { - return methodDescriptor; - } - - public MethodType getMethodType() { - return methodType; - } - - public void callMethod(MethodVisitor methodVisitor) { - methodVisitor.visitMethodInsn(getMethodType().getOpcode(), getDeclaringClassInternalName(), getMethodName(), - getMethodDescriptor(), - getMethodType() == MethodType.INTERFACE); - } - - public MethodDescriptor withReturnType(Type returnType) { - return new MethodDescriptor(getDeclaringClassInternalName(), getMethodType(), getMethodName(), - Type.getMethodDescriptor(returnType, Type.getArgumentTypes(getMethodDescriptor()))); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MethodDescriptor that = (MethodDescriptor) o; - return getDeclaringClassInternalName().equals(that.getDeclaringClassInternalName()) - && getMethodName().equals(that.getMethodName()) - && getMethodDescriptor().equals(that.getMethodDescriptor()); - } - - @Override - public int hashCode() { - return Objects.hash(getDeclaringClassInternalName(), getMethodName(), getMethodDescriptor()); - } - - public Type getReturnType() { - return Type.getReturnType(getMethodDescriptor()); - } - - public Type[] getParameterTypes() { - return Type.getArgumentTypes(getMethodDescriptor()); - } - - public enum MethodType { - VIRTUAL(Opcodes.INVOKEVIRTUAL, false), - STATIC(Opcodes.INVOKESTATIC, true), - CLASS(Opcodes.INVOKESTATIC, true), - STATIC_AS_VIRTUAL(Opcodes.INVOKESTATIC, true), - INTERFACE(Opcodes.INVOKEINTERFACE, false), - CONSTRUCTOR(Opcodes.INVOKESPECIAL, false); - - private final int opcode; - private final boolean isStatic; - - MethodType(int opcode, boolean isStatic) { - this.opcode = opcode; - this.isStatic = isStatic; - } - - public int getOpcode() { - return opcode; - } - - public boolean isStatic() { - return isStatic; - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ModuleSpec.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ModuleSpec.java deleted file mode 100644 index c8c52017..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ModuleSpec.java +++ /dev/null @@ -1,55 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public final class ModuleSpec { - final PythonInteger level; - final List fromList; - final Map globalsMap; - final Map localsMap; - final String name; - - public ModuleSpec(PythonInteger level, List fromList, Map globalsMap, - Map localsMap, String name) { - this.level = level; - this.fromList = fromList; - this.globalsMap = globalsMap; - this.localsMap = localsMap; - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ModuleSpec that = (ModuleSpec) o; - return level.equals(that.level) && fromList.equals(that.fromList) && globalsMap == that.globalsMap - && localsMap == that.localsMap && name.equals(that.name); - } - - @Override - public int hashCode() { - return Objects.hash(level, fromList, System.identityHashCode(globalsMap), - System.identityHashCode(localsMap), name); - } - - @Override - public String toString() { - return "ModuleSpec{" + - "name='" + name + '\'' + - ", level=" + level + - ", fromList=" + fromList + - ", globalsMap=" + System.identityHashCode(globalsMap) + - ", localsMap=" + System.identityHashCode(localsMap) + - '}'; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBinaryOperator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBinaryOperator.java deleted file mode 100644 index 88574dd0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBinaryOperator.java +++ /dev/null @@ -1,179 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Optional; - -import ai.timefold.jpyinterpreter.opcodes.dunder.BinaryDunderOpcode; - -/** - * The list of all Python Binary Operators, which are performed - * by calling the left operand's corresponding dunder method on the - * right operand - * - * ex: a.__add__(b) - */ -public enum PythonBinaryOperator { - // Comparable operations ( from https://docs.python.org/3/reference/datamodel.html#object.__lt__ ) - LESS_THAN("<", "__lt__", "__gt__", true), - LESS_THAN_OR_EQUAL("<=", "__le__", "__ge__", true), - GREATER_THAN(">", "__gt__", "__lt__", true), - GREATER_THAN_OR_EQUAL(">=", "__ge__", "__le__", true), - EQUAL("==", "__eq__", "__eq__", true), - NOT_EQUAL("!=", "__ne__", "__ne__", true), - - // Numeric binary operations ( from https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types ) - POWER("**", "__pow__", "__rpow__"), - MULTIPLY("*", "__mul__", "__rmul__"), - MATRIX_MULTIPLY("@", "__matmul__", "__rmatmul__"), - FLOOR_DIVIDE("//", "__floordiv__", "__rfloordiv__"), - TRUE_DIVIDE("/", "__truediv__", "__rtruediv__"), - MODULO("%", "__mod__", "__rmod__"), - ADD("+", "__add__", "__radd__"), - SUBTRACT("-", "__sub__", "__rsub__"), - LSHIFT("<<", "__lshift__", "__rlshift__"), - RSHIFT(">>", "__rshift__", "__rrshift__"), - AND("&", "__and__", "__rand__"), - XOR("^", "__xor__", "__rxor__"), - OR("|", "__or__", "__ror__"), - - // In-place numeric binary operations variations - INPLACE_POWER("**=", "__ipow__", PythonBinaryOperator.POWER), - INPLACE_MULTIPLY("*=", "__imul__", PythonBinaryOperator.MULTIPLY), - INPLACE_MATRIX_MULTIPLY("@=", "__imatmul__", PythonBinaryOperator.MATRIX_MULTIPLY), - INPLACE_FLOOR_DIVIDE("//=", "__ifloordiv__", PythonBinaryOperator.FLOOR_DIVIDE), - INPLACE_TRUE_DIVIDE("/=", "__itruediv__", PythonBinaryOperator.TRUE_DIVIDE), - INPLACE_MODULO("%=", "__imod__", PythonBinaryOperator.MODULO), - INPLACE_ADD("+=", "__iadd__", PythonBinaryOperator.ADD), - INPLACE_SUBTRACT("-=", "__isub__", PythonBinaryOperator.SUBTRACT), - INPLACE_LSHIFT("<<=", "__ilshift__", PythonBinaryOperator.LSHIFT), - INPLACE_RSHIFT(">>=", "__irshift__", PythonBinaryOperator.RSHIFT), - INPLACE_AND("&=", "__iand__", PythonBinaryOperator.AND), - INPLACE_XOR("^=", "__ixor__", PythonBinaryOperator.XOR), - INPLACE_OR("|=", "__ior__", PythonBinaryOperator.OR), - - // List operations - // https://docs.python.org/3/reference/datamodel.html#object.__getitem__ - GET_ITEM("", "__getitem__"), - DELETE_ITEM("", "__delitem__"), - - // Generator operations - // https://docs.python.org/3/reference/expressions.html#generator-iterator-methods - SEND("", "send"), - THROW("", "throw"), - - // Membership operations - // https://docs.python.org/3/reference/expressions.html#membership-test-operations - CONTAINS("", "__contains__"), - - // Descriptor operations - // https://docs.python.org/3/howto/descriptor.html - DELETE("", "__delete__"), - - // Attribute access - GET_ATTRIBUTE("", "__getattribute__"), - GET_ATTRIBUTE_NOT_IN_SLOTS("", "__getattr__"), - DELETE_ATTRIBUTE("", "__delattr__"), - - // Format string: https://peps.python.org/pep-3101/ - FORMAT("", "__format__"), - - // global builtins - DIVMOD("", "__divmod__"); - - public final String operatorSymbol; - public final String dunderMethod; - public final String rightDunderMethod; - public final boolean isComparisonMethod; - public final PythonBinaryOperator fallbackOperation; - - PythonBinaryOperator(String operatorSymbol, String dunderMethod) { - this.operatorSymbol = operatorSymbol; - this.dunderMethod = dunderMethod; - this.rightDunderMethod = null; - this.isComparisonMethod = false; - this.fallbackOperation = null; - } - - PythonBinaryOperator(String operatorSymbol, String dunderMethod, String rightDunderMethod) { - this.operatorSymbol = operatorSymbol; - this.dunderMethod = dunderMethod; - this.rightDunderMethod = rightDunderMethod; - this.isComparisonMethod = false; - this.fallbackOperation = null; - } - - PythonBinaryOperator(String operatorSymbol, String dunderMethod, String rightDunderMethod, boolean isComparisonMethod) { - this.operatorSymbol = operatorSymbol; - this.dunderMethod = dunderMethod; - this.rightDunderMethod = rightDunderMethod; - this.isComparisonMethod = isComparisonMethod; - this.fallbackOperation = null; - } - - PythonBinaryOperator(String operatorSymbol, String dunderMethod, PythonBinaryOperator fallbackOperation) { - this.operatorSymbol = operatorSymbol; - this.dunderMethod = dunderMethod; - this.rightDunderMethod = null; - this.isComparisonMethod = false; - this.fallbackOperation = fallbackOperation; - } - - public String getOperatorSymbol() { - return operatorSymbol; - } - - public String getDunderMethod() { - return dunderMethod; - } - - public String getRightDunderMethod() { - return rightDunderMethod; - } - - public boolean hasRightDunderMethod() { - return rightDunderMethod != null; - } - - public boolean isComparisonMethod() { - return isComparisonMethod; - } - - public Optional getFallbackOperation() { - return Optional.ofNullable(fallbackOperation); - } - - public static BinaryDunderOpcode getBinaryOpcode(PythonBytecodeInstruction instruction) { - // As defined by https://github.com/python/cpython/blob/0faa0ba240e815614e5a2900e48007acac41b214/Python/ceval.c#L299 - - // Binary operations are in alphabetical order (note: CPython refer to Modulo as Remainder), - // and are followed by the inplace operations in the same order - return new BinaryDunderOpcode(instruction, switch (instruction.arg()) { - case 0 -> PythonBinaryOperator.ADD; - case 1 -> PythonBinaryOperator.AND; - case 2 -> PythonBinaryOperator.FLOOR_DIVIDE; - case 3 -> PythonBinaryOperator.LSHIFT; - case 4 -> PythonBinaryOperator.MATRIX_MULTIPLY; - case 5 -> PythonBinaryOperator.MULTIPLY; - case 6 -> PythonBinaryOperator.MODULO; - case 7 -> PythonBinaryOperator.OR; - case 8 -> PythonBinaryOperator.POWER; - case 9 -> PythonBinaryOperator.RSHIFT; - case 10 -> PythonBinaryOperator.SUBTRACT; - case 11 -> PythonBinaryOperator.TRUE_DIVIDE; - case 12 -> PythonBinaryOperator.XOR; - case 13 -> PythonBinaryOperator.INPLACE_ADD; - case 14 -> PythonBinaryOperator.INPLACE_AND; - case 15 -> PythonBinaryOperator.INPLACE_FLOOR_DIVIDE; - case 16 -> PythonBinaryOperator.INPLACE_LSHIFT; - case 17 -> PythonBinaryOperator.INPLACE_MATRIX_MULTIPLY; - case 18 -> PythonBinaryOperator.INPLACE_MULTIPLY; - case 19 -> PythonBinaryOperator.INPLACE_MODULO; - case 20 -> PythonBinaryOperator.INPLACE_OR; - case 21 -> PythonBinaryOperator.INPLACE_POWER; - case 22 -> PythonBinaryOperator.INPLACE_RSHIFT; - case 23 -> PythonBinaryOperator.INPLACE_SUBTRACT; - case 24 -> PythonBinaryOperator.INPLACE_TRUE_DIVIDE; - case 25 -> PythonBinaryOperator.INPLACE_XOR; - default -> throw new IllegalArgumentException("Unknown binary op id: " + instruction.arg()); - }); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBuiltinOperations.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBuiltinOperations.java deleted file mode 100644 index 9b6d8444..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBuiltinOperations.java +++ /dev/null @@ -1,4 +0,0 @@ -package ai.timefold.jpyinterpreter; - -public class PythonBuiltinOperations { -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java deleted file mode 100644 index 4999f56d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.OptionalInt; - -import ai.timefold.jpyinterpreter.opcodes.descriptor.OpcodeDescriptor; - -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); - } - - public static PythonBytecodeInstruction atOffset(OpcodeDescriptor instruction, int offset) { - return atOffset(instruction.name(), offset); - } - - public PythonBytecodeInstruction withArg(int newArg) { - 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, argRepr, OptionalInt.of(lineNumber), - isJumpTarget); - } - - public PythonBytecodeInstruction withIsJumpTarget(boolean isJumpTarget) { - return new PythonBytecodeInstruction(opname, offset, arg, argRepr, startsLine, isJumpTarget); - } - - public PythonBytecodeInstruction markAsJumpTarget() { - return new PythonBytecodeInstruction(opname, offset, arg, argRepr, startsLine, true); - } - - @Override - public String toString() { - return "[%d] %s (%d) %s" - .formatted(offset, opname, arg, isJumpTarget ? "{JUMP TARGET}" : ""); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java deleted file mode 100644 index d4664d05..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java +++ /dev/null @@ -1,1397 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.io.IOException; -import java.io.PrintStream; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.file.Files; -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; -import java.util.stream.Stream; - -import ai.timefold.jpyinterpreter.dag.FlowGraph; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; -import ai.timefold.jpyinterpreter.implementors.FunctionImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.implementors.VariableImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -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; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; -import ai.timefold.jpyinterpreter.types.wrappers.PythonObjectWrapper; -import ai.timefold.jpyinterpreter.util.JavaPythonClassWriter; -import ai.timefold.jpyinterpreter.util.MethodVisitorAdapters; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PythonBytecodeToJavaBytecodeTranslator { - - public static final String USER_PACKAGE_BASE = "org.jpyinterpreter.user."; - - public static final String GENERATED_PACKAGE_BASE = "org.jpyinterpreter.synthetic."; - - public static final String CONSTANTS_STATIC_FIELD_NAME = "co_consts"; - - public static final String NAMES_STATIC_FIELD_NAME = "co_names"; - - public static final String VARIABLE_NAMES_STATIC_FIELD_NAME = "co_varnames"; - - public static final String GLOBALS_MAP_STATIC_FIELD_NAME = "__globals__"; - - public static final String CLASS_CELL_STATIC_FIELD_NAME = "__class_cell__"; - - public static final String ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME = "__spec_getter__"; - - public static final String PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME = "__code__"; - - public static final String DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME = "__defaults__"; - - public static final String DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME = "__kwdefaults__"; - - public static final String ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME = "__annotations__"; - public static final String CELLS_INSTANCE_FIELD_NAME = "__closure__"; - - public static final String QUALIFIED_NAME_INSTANCE_FIELD_NAME = "__qualname__"; - - public static final String ARGUMENT_SPEC_INSTANCE_FIELD_NAME = "__spec__"; - - public static final String INTERPRETER_INSTANCE_FIELD_NAME = "__interpreter__"; - - public static final String PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME = "__function__"; - public static final Map classNameToSharedInstanceCount = new HashMap<>(); - - private static final Logger LOGGER = LoggerFactory.getLogger(PythonBytecodeToJavaBytecodeTranslator.class); - public static Path classOutputRootPath = InterpreterStartupOptions.classOutputRootPath; - - static { - BuiltinTypes.load(); - } - - public static void writeClassOutput(Map classNameToBytecode, String className, byte[] classByteCode) { - classNameToBytecode.put(className, classByteCode); - - if (classOutputRootPath == null) { - return; - } - - String[] parts = (className.replace('.', '/') + ".class").split("/"); - Path classFileLocation = classOutputRootPath.resolve(Path.of(".", parts)); - - try { - Files.createDirectories(classFileLocation.getParent()); - Files.write(classFileLocation, classByteCode); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static Method getFunctionalInterfaceMethod(Class interfaceClass) { - List candidateList = new ArrayList<>(); - for (Method method : interfaceClass.getMethods()) { - if (Modifier.isAbstract(method.getModifiers())) { - candidateList.add(method); - } - } - - if (candidateList.isEmpty()) { - throw new IllegalArgumentException("Class (" + interfaceClass.getName() + ") is not a functional interface: " + - "it has no abstract methods."); - } - - if (candidateList.size() > 1) { - throw new IllegalArgumentException("Class (" + interfaceClass.getName() + ") is not a functional interface: " + - "it has multiple abstract methods (" + candidateList + ")."); - } - - return candidateList.get(0); - } - - public static T createInstance(Class functionClass, PythonInterpreter pythonInterpreter) { - return FunctionImplementor.createInstance(new PythonLikeTuple(), new PythonLikeDict(), - new PythonLikeTuple(), new PythonLikeTuple(), - PythonString.valueOf(functionClass.getName()), - functionClass, pythonInterpreter); - } - - public static T translatePythonBytecode(PythonCompiledFunction pythonCompiledFunction, - Class javaFunctionalInterfaceType) { - Class compiledClass = translatePythonBytecodeToClass(pythonCompiledFunction, javaFunctionalInterfaceType); - PythonLikeTuple annotationTuple = pythonCompiledFunction.typeAnnotations.entrySet() - .stream() - .map(entry -> PythonLikeTuple.fromItems(PythonString.valueOf(entry.getKey()), - entry.getValue() != null ? entry.getValue().type() : BuiltinTypes.BASE_TYPE)) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return FunctionImplementor.createInstance(pythonCompiledFunction.defaultPositionalArguments, - pythonCompiledFunction.defaultKeywordArguments, - annotationTuple, pythonCompiledFunction.closure, - PythonString.valueOf(compiledClass.getName()), - compiledClass, PythonInterpreter.DEFAULT); - } - - public static T translatePythonBytecode(PythonCompiledFunction pythonCompiledFunction, - Class javaFunctionalInterfaceType, List> genericTypeArgumentList) { - Class compiledClass = - translatePythonBytecodeToClass(pythonCompiledFunction, javaFunctionalInterfaceType, genericTypeArgumentList); - PythonLikeTuple annotationTuple = pythonCompiledFunction.typeAnnotations.entrySet() - .stream() - .map(entry -> PythonLikeTuple.fromItems(PythonString.valueOf(entry.getKey()), entry.getValue().type())) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return FunctionImplementor.createInstance(pythonCompiledFunction.defaultPositionalArguments, - pythonCompiledFunction.defaultKeywordArguments, - annotationTuple, pythonCompiledFunction.closure, - PythonString.valueOf(compiledClass.getName()), - compiledClass, PythonInterpreter.DEFAULT); - } - - public static T forceTranslatePythonBytecodeToGenerator(PythonCompiledFunction pythonCompiledFunction, - Class javaFunctionalInterfaceType) { - Method methodWithoutGenerics = getFunctionalInterfaceMethod(javaFunctionalInterfaceType); - MethodDescriptor methodDescriptor = new MethodDescriptor(javaFunctionalInterfaceType, - methodWithoutGenerics, - Collections.emptyList()); - Class compiledClass = forceTranslatePythonBytecodeToGeneratorClass(pythonCompiledFunction, methodDescriptor, - methodWithoutGenerics, false); - return FunctionImplementor.createInstance(new PythonLikeTuple(), new PythonLikeDict(), - new PythonLikeTuple(), pythonCompiledFunction.closure, - PythonString.valueOf(compiledClass.getName()), - compiledClass, PythonInterpreter.DEFAULT); - } - - public static Class translatePythonBytecodeToClass(PythonCompiledFunction pythonCompiledFunction, - Class javaFunctionalInterfaceType) { - MethodDescriptor methodDescriptor = new MethodDescriptor(getFunctionalInterfaceMethod(javaFunctionalInterfaceType)); - return translatePythonBytecodeToClass(pythonCompiledFunction, methodDescriptor); - } - - public static Class translatePythonBytecodeToClass(PythonCompiledFunction pythonCompiledFunction, - Class javaFunctionalInterfaceType, - List> genericTypeArgumentList) { - Method methodWithoutGenerics = getFunctionalInterfaceMethod(javaFunctionalInterfaceType); - MethodDescriptor methodDescriptor = new MethodDescriptor(javaFunctionalInterfaceType, - methodWithoutGenerics, - genericTypeArgumentList); - return translatePythonBytecodeToClass(pythonCompiledFunction, methodDescriptor, methodWithoutGenerics, false); - } - - public static Class translatePythonBytecodeToClass(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor) { - return translatePythonBytecodeToClass(pythonCompiledFunction, methodDescriptor, false); - } - - public static T translatePythonBytecodeToInstance(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor) { - return translatePythonBytecodeToInstance(pythonCompiledFunction, methodDescriptor, false); - } - - public static T translatePythonBytecodeToInstance(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor, - boolean isVirtual) { - Class compiledClass = translatePythonBytecodeToClass(pythonCompiledFunction, methodDescriptor, isVirtual); - PythonLikeTuple annotationTuple = pythonCompiledFunction.typeAnnotations.entrySet() - .stream() - .map(entry -> PythonLikeTuple.fromItems(PythonString.valueOf(entry.getKey()), entry.getValue().type())) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return FunctionImplementor.createInstance(pythonCompiledFunction.defaultPositionalArguments, - pythonCompiledFunction.defaultKeywordArguments, - annotationTuple, pythonCompiledFunction.closure, - PythonString.valueOf(compiledClass.getName()), - compiledClass, PythonInterpreter.DEFAULT); - } - - @SuppressWarnings("unchecked") - public static Class translatePythonBytecodeToClass(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor, boolean isVirtual) { - String maybeClassName = USER_PACKAGE_BASE + pythonCompiledFunction.getGeneratedClassBaseName(); - int numberOfInstances = classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), - new String[] { methodDescriptor.getDeclaringClassInternalName() }); - - final boolean isPythonLikeFunction = - methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class)); - - classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null); - - createFields(classWriter); - createConstructor(classWriter, internalClassName); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, - methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), - null, - null); - - translatePythonBytecodeToMethod(methodDescriptor, internalClassName, methodVisitor, pythonCompiledFunction, - isPythonLikeFunction, isVirtual); - - classWriter.visitEnd(); - - writeClassOutput(BuiltinTypes.classNameToBytecode, className, classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - setStaticFields(compiledClass, pythonCompiledFunction); - return compiledClass; - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } - } - - @SuppressWarnings("unchecked") - public static Class translatePythonBytecodeToClass(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor, Method methodWithoutGenerics, - boolean isVirtual) { - String maybeClassName = USER_PACKAGE_BASE + pythonCompiledFunction.getGeneratedClassBaseName(); - int numberOfInstances = classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), - new String[] { methodDescriptor.getDeclaringClassInternalName() }); - - final boolean isPythonLikeFunction = - methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class)); - - classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null); - - createFields(classWriter); - createConstructor(classWriter, internalClassName); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, - methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), - null, - null); - - translatePythonBytecodeToMethod(methodDescriptor, internalClassName, methodVisitor, pythonCompiledFunction, - isPythonLikeFunction, isVirtual); - - String withoutGenericsSignature = Type.getMethodDescriptor(methodWithoutGenerics); - if (!withoutGenericsSignature.equals(methodDescriptor.getMethodDescriptor())) { - methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, methodDescriptor.getMethodName(), withoutGenericsSignature, null, - null); - - methodVisitor.visitCode(); - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - for (int i = 0; i < methodWithoutGenerics.getParameterCount(); i++) { - Type parameterType = Type.getType(methodWithoutGenerics.getParameterTypes()[i]); - methodVisitor.visitVarInsn(parameterType.getOpcode(Opcodes.ILOAD), i + 1); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getParameterTypes()[i].getInternalName()); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), false); - methodVisitor.visitInsn(methodDescriptor.getReturnType().getOpcode(Opcodes.IRETURN)); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - classWriter.visitEnd(); - - writeClassOutput(BuiltinTypes.classNameToBytecode, className, classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - setStaticFields(compiledClass, pythonCompiledFunction); - return compiledClass; - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } - } - - @SuppressWarnings("unchecked") - public static Class translatePythonBytecodeToPythonWrapperClass(PythonCompiledFunction pythonCompiledFunction, - OpaquePythonReference codeReference) { - String maybeClassName = USER_PACKAGE_BASE + pythonCompiledFunction.getGeneratedClassBaseName(); - int numberOfInstances = classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), - new String[] { Type.getInternalName(PythonLikeFunction.class) }); - - classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null); - - createFields(classWriter); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME, - Type.getDescriptor(OpaquePythonReference.class), - null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.FINAL, PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonObjectWrapper.class), - null, null); - createPythonWrapperConstructor(classWriter, internalClassName); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, - "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - null, - null); - - methodVisitor.visitCode(); - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonObjectWrapper.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonObjectWrapper.class), "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitInsn(Opcodes.ARETURN); - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - - classWriter.visitEnd(); - - writeClassOutput(BuiltinTypes.classNameToBytecode, className, classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - setStaticFields(compiledClass, pythonCompiledFunction); - compiledClass.getField(PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME).set(null, codeReference); - return compiledClass; - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } - } - - /** - * Used for testing; force translate the python to a generator, even if it is not a generator - */ - @SuppressWarnings("unchecked") - public static Class forceTranslatePythonBytecodeToGeneratorClass(PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor methodDescriptor, Method methodWithoutGenerics, - boolean isVirtual) { - String maybeClassName = USER_PACKAGE_BASE + pythonCompiledFunction.getGeneratedClassBaseName(); - int numberOfInstances = classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), - new String[] { methodDescriptor.getDeclaringClassInternalName() }); - - classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null); - - final boolean isPythonLikeFunction = - methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class)); - - createFields(classWriter); - createConstructor(classWriter, internalClassName); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, - methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), - null, - null); - - LocalVariableHelper localVariableHelper = - new LocalVariableHelper(methodDescriptor.getParameterTypes(), pythonCompiledFunction); - - if (!isPythonLikeFunction) { - // Need to convert Java parameters - for (int i = 0; i < localVariableHelper.parameters.length; i++) { - JavaPythonTypeConversionImplementor.copyParameter(methodVisitor, localVariableHelper, i); - } - } else { - // Need to move Python parameters from the argument list + keyword list to their variable slots - movePythonParametersToSlots(methodVisitor, internalClassName, pythonCompiledFunction, localVariableHelper); - } - - for (int i = 0; i < localVariableHelper.getNumberOfBoundCells(); i++) { - VariableImplementor.createCell(methodVisitor, localVariableHelper, i); - } - - for (int i = 0; i < localVariableHelper.getNumberOfFreeCells(); i++) { - VariableImplementor.setupFreeVariableCell(methodVisitor, internalClassName, localVariableHelper, i); - } - - translateGeneratorBytecode(methodVisitor, methodDescriptor, internalClassName, localVariableHelper, - pythonCompiledFunction); // TODO: Use actual python version - - String withoutGenericsSignature = Type.getMethodDescriptor(methodWithoutGenerics); - if (!withoutGenericsSignature.equals(methodDescriptor.getMethodDescriptor())) { - methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, methodDescriptor.getMethodName(), withoutGenericsSignature, null, - null); - - methodVisitor.visitCode(); - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - for (int i = 0; i < methodWithoutGenerics.getParameterCount(); i++) { - Type parameterType = Type.getType(methodWithoutGenerics.getParameterTypes()[i]); - methodVisitor.visitVarInsn(parameterType.getOpcode(Opcodes.ILOAD), i + 1); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getParameterTypes()[i].getInternalName()); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, methodDescriptor.getMethodName(), - methodDescriptor.getMethodDescriptor(), false); - methodVisitor.visitInsn(methodDescriptor.getReturnType().getOpcode(Opcodes.IRETURN)); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - classWriter.visitEnd(); - - writeClassOutput(BuiltinTypes.classNameToBytecode, className, classWriter.toByteArray()); - - try { - Class compiledClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - setStaticFields(compiledClass, pythonCompiledFunction); - return compiledClass; - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } - } - - private static void createConstructor(ClassWriter classWriter, String className) { - // Empty constructor, for java code - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor.visitCode(); - - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - "()V", false); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(className.replace('/', '.')); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), - "valueOf", Type.getMethodDescriptor(Type.getType(PythonString.class), Type.getType(String.class)), - false); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - // Spec - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME, - Type.getDescriptor(BiFunction.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(BiFunction.class), "apply", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ArgumentSpec.class)); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - // Interpreter - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInterpreter.class), "DEFAULT", - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - // Full constructor, for MAKE_FUNCTION - methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonInterpreter.class)), - null, null); - methodVisitor.visitCode(); - - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - "()V", false); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 5); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - // Spec - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME, - Type.getDescriptor(BiFunction.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(BiFunction.class), "apply", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ArgumentSpec.class)); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - // Interpreter - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - private static void createPythonWrapperConstructor(ClassWriter classWriter, String className) { - // Empty constructor, for java code - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor.visitCode(); - - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - "()V", false); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(className.replace('/', '.')); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), - "valueOf", Type.getMethodDescriptor(Type.getType(PythonString.class), Type.getType(String.class)), - false); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - // Spec - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME, - Type.getDescriptor(BiFunction.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(BiFunction.class), "apply", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ArgumentSpec.class)); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - // Interpreter - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInterpreter.class), "DEFAULT", - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - // Function object - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME, - Type.getDescriptor(OpaquePythonReference.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(CPythonBackedPythonInterpreter.class), - "createPythonFunctionWrapper", - Type.getMethodDescriptor(Type.getType(PythonObjectWrapper.class), - Type.getType(OpaquePythonReference.class), - Type.getType(Map.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class)), - false); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonObjectWrapper.class)); - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - // Full constructor, for MAKE_FUNCTION - methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonInterpreter.class)), - null, null); - methodVisitor.visitCode(); - - visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - "()V", false); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 5); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - // Spec - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME, - Type.getDescriptor(BiFunction.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(BiFunction.class), "apply", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ArgumentSpec.class)); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - // Interpreter - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - // Function object - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME, - Type.getDescriptor(OpaquePythonReference.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(CPythonBackedPythonInterpreter.class), - "createPythonFunctionWrapper", - Type.getMethodDescriptor(Type.getType(PythonObjectWrapper.class), - Type.getType(OpaquePythonReference.class), - Type.getType(Map.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class)), - false); - - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonObjectWrapper.class)); - - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - public static void createFields(ClassWriter classWriter) { - // Static fields - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - CONSTANTS_STATIC_FIELD_NAME, Type.getDescriptor(List.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - NAMES_STATIC_FIELD_NAME, Type.getDescriptor(List.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - VARIABLE_NAMES_STATIC_FIELD_NAME, Type.getDescriptor(List.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - GLOBALS_MAP_STATIC_FIELD_NAME, Type.getDescriptor(Map.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - CLASS_CELL_STATIC_FIELD_NAME, Type.getDescriptor(PythonLikeType.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, - ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME, Type.getDescriptor(BiFunction.class), null, null); - - // Instance fields - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - INTERPRETER_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonInterpreter.class), null, null); - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonLikeTuple.class), null, null); - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonLikeDict.class), null, null); - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonLikeDict.class), null, null); - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - QUALIFIED_NAME_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonString.class), null, null); - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, - CELLS_INSTANCE_FIELD_NAME, Type.getDescriptor(PythonLikeTuple.class), null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.FINAL, - ARGUMENT_SPEC_INSTANCE_FIELD_NAME, Type.getDescriptor(ArgumentSpec.class), null, null); - } - - static void setStaticFields(Class compiledClass, PythonCompiledFunction pythonCompiledFunction) { - try { - compiledClass.getField(CONSTANTS_STATIC_FIELD_NAME).set(null, pythonCompiledFunction.co_constants); - compiledClass.getField(GLOBALS_MAP_STATIC_FIELD_NAME).set(null, pythonCompiledFunction.globalsMap); - compiledClass.getField(ARGUMENT_SPEC_GETTER_STATIC_FIELD_NAME).set(null, - pythonCompiledFunction.getArgumentSpecMapper()); - - // Need to convert co_names to python strings (used in __getattribute__) - List pythonNameList = new ArrayList<>(pythonCompiledFunction.co_names.size()); - for (String name : pythonCompiledFunction.co_names) { - pythonNameList.add(PythonString.valueOf(name)); - } - compiledClass.getField(NAMES_STATIC_FIELD_NAME).set(null, pythonNameList); - - List pythonVariableNameList = new ArrayList<>(pythonCompiledFunction.co_varnames.size()); - for (String name : pythonCompiledFunction.co_varnames) { - pythonVariableNameList.add(PythonString.valueOf(name)); - } - compiledClass.getField(VARIABLE_NAMES_STATIC_FIELD_NAME).set(null, pythonVariableNameList); - // Class cell is set by PythonClassTranslator - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new IllegalStateException("Impossible state: generated class (" + compiledClass + - ") does not have static field \"" + CONSTANTS_STATIC_FIELD_NAME + "\"", e); - } - } - - public static List getOpcodeList(PythonCompiledFunction pythonCompiledFunction) { - List opcodeList = new ArrayList<>(pythonCompiledFunction.instructionList.size()); - for (PythonBytecodeInstruction instruction : pythonCompiledFunction.instructionList) { - opcodeList.add(Opcode.lookupOpcodeForInstruction(instruction, pythonCompiledFunction.pythonVersion)); - } - return opcodeList; - } - - public static StackMetadata getInitialStackMetadata(LocalVariableHelper localVariableHelper, MethodDescriptor method, - boolean isVirtual) { - StackMetadata initialStackMetadata = new StackMetadata(localVariableHelper); - - if (Type.getInternalName(PythonLikeFunction.class).equals(method.getDeclaringClassInternalName())) { - return getPythonLikeFunctionInitialStackMetadata(localVariableHelper, initialStackMetadata); - } - - for (int i = 0; i < method.getParameterTypes().length; i++) { - Type type = method.getParameterTypes()[i]; - try { - Class typeClass = Class.forName(type.getClassName(), false, BuiltinTypes.asmClassLoader); - initialStackMetadata = - initialStackMetadata.setLocalVariableValueSource(i, ValueSourceInfo.of(new OpcodeWithoutSource(), - JavaPythonTypeConversionImplementor.getPythonLikeType(typeClass))); - } catch (ClassNotFoundException e) { - initialStackMetadata = initialStackMetadata.setLocalVariableValueSource(i, - ValueSourceInfo.of(new OpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)); - } - } - - if (isVirtual && method.getParameterTypes().length > 0) { - try { - Class typeClass = - Class.forName(method.getParameterTypes()[0].getClassName(), false, BuiltinTypes.asmClassLoader); - initialStackMetadata = - initialStackMetadata.setLocalVariableValueSource(0, ValueSourceInfo.of(new SelfOpcodeWithoutSource(), - JavaPythonTypeConversionImplementor.getPythonLikeType(typeClass))); - } catch (ClassNotFoundException e) { - initialStackMetadata = initialStackMetadata.setLocalVariableValueSource(0, - ValueSourceInfo.of(new SelfOpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)); - } - } - - return initialStackMetadata; - } - - private static StackMetadata getPythonLikeFunctionInitialStackMetadata(LocalVariableHelper localVariableHelper, - StackMetadata initialStackMetadata) { - for (int i = 0; i < localVariableHelper.getNumberOfLocalVariables(); i++) { - initialStackMetadata = initialStackMetadata.setLocalVariableValueSource(i, - ValueSourceInfo.of(new OpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)); - } - - for (int i = 0; i < localVariableHelper.getNumberOfCells(); i++) { - initialStackMetadata = - initialStackMetadata.setCellVariableValueSource(i, ValueSourceInfo.of(new OpcodeWithoutSource(), - BuiltinTypes.BASE_TYPE)); - } - - return initialStackMetadata; - } - - public static PythonFunctionType getFunctionType(PythonCompiledFunction pythonCompiledFunction) { - for (PythonBytecodeInstruction instruction : pythonCompiledFunction.instructionList) { - var opcode = AbstractOpcode.lookupInstruction(instruction.opname()); - if (opcode instanceof GeneratorOpDescriptor generatorInstruction) - switch (generatorInstruction) { - case GEN_START: - case RETURN_GENERATOR: - case YIELD_VALUE: - case YIELD_FROM: - return PythonFunctionType.GENERATOR; - - default: - break; // Do nothing - } - } - return PythonFunctionType.FUNCTION; - } - - private static void translatePythonBytecodeToMethod(MethodDescriptor method, String className, MethodVisitor methodVisitor, - 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); - - for (int i = 0; i < method.getParameterTypes().length; i++) { - if (!isPythonLikeFunction) { - methodVisitor.visitParameter(pythonCompiledFunction.co_varnames.get(i), 0); - } else { - methodVisitor.visitParameter(null, 0); - } - } - methodVisitor.visitCode(); - - visitGeneratedLineNumber(methodVisitor); - Label start = new Label(); - Label end = new Label(); - - methodVisitor.visitLabel(start); - - Map bytecodeCounterToLabelMap = new HashMap<>(); - LocalVariableHelper localVariableHelper = new LocalVariableHelper(method.getParameterTypes(), pythonCompiledFunction); - - localVariableHelper.resetCallKeywords(methodVisitor); - - // The bytecode checker will see an empty slot in finally blocks without this (in particular, - // when a try block finally handler is inside another try block). - localVariableHelper.setupInitialStoredExceptionStacks(methodVisitor); - if (!isPythonLikeFunction) { - // Need to convert Java parameters - for (int i = 0; i < localVariableHelper.parameters.length; i++) { - JavaPythonTypeConversionImplementor.copyParameter(methodVisitor, localVariableHelper, i); - } - } else { - // Need to move Python parameters from the argument list + keyword list to their variable slots - movePythonParametersToSlots(methodVisitor, className, pythonCompiledFunction, localVariableHelper); - } - - for (int i = 0; i < localVariableHelper.getNumberOfBoundCells(); i++) { - VariableImplementor.createCell(methodVisitor, localVariableHelper, i); - } - - for (int i = 0; i < localVariableHelper.getNumberOfFreeCells(); i++) { - VariableImplementor.setupFreeVariableCell(methodVisitor, className, localVariableHelper, i); - } - - Map> bytecodeIndexToArgumentorsMap = new HashMap<>(); - - FunctionMetadata functionMetadata = new FunctionMetadata(); - functionMetadata.functionType = getFunctionType(pythonCompiledFunction); - functionMetadata.method = method; - functionMetadata.bytecodeCounterToCodeArgumenterList = bytecodeIndexToArgumentorsMap; - functionMetadata.bytecodeCounterToLabelMap = bytecodeCounterToLabelMap; - functionMetadata.methodVisitor = methodVisitor; - functionMetadata.pythonCompiledFunction = pythonCompiledFunction; - functionMetadata.className = className; - - if (functionMetadata.functionType == PythonFunctionType.GENERATOR) { - translateGeneratorBytecode(methodVisitor, method, className, localVariableHelper, pythonCompiledFunction); - return; - } - - StackMetadata initialStackMetadata = getInitialStackMetadata(localVariableHelper, method, isVirtual); - - List opcodeList = getOpcodeList(pythonCompiledFunction); - - FlowGraph flowGraph = FlowGraph.createFlowGraph(functionMetadata, initialStackMetadata, opcodeList); - List stackMetadataForOpcodeIndex = flowGraph.getStackMetadataForOperations(); - - writeInstructionsForOpcodes(functionMetadata, stackMetadataForOpcodeIndex, opcodeList); - - methodVisitor.visitLabel(end); - - for (int i = method.getParameterTypes().length; i < localVariableHelper.getNumberOfLocalVariables(); i++) { - methodVisitor.visitLocalVariable(pythonCompiledFunction.co_varnames.get(i), - Type.getDescriptor(PythonLikeObject.class), - null, - start, - end, - localVariableHelper.getPythonLocalVariableSlot(i)); - } - - try { - methodVisitor.visitMaxs(0, 0); - } catch (Exception e) { - throw new IllegalStateException("Invalid Java bytecode generated (this is a bug):\n" + - pythonCompiledFunction.instructionList.stream() - .map(PythonBytecodeInstruction::toString) - .collect(Collectors.joining("\n")), - e); - } - - methodVisitor.visitEnd(); - } - - public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata, - List stackMetadataForOpcodeIndex, List opcodeList) { - writeInstructionsForOpcodes(functionMetadata, stackMetadataForOpcodeIndex, opcodeList, ignored -> { - }); - } - - public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata, - List stackMetadataForOpcodeIndex, List opcodeList, - Consumer runAfterLabelAndBeforeArgumentors) { - PythonCompiledFunction pythonCompiledFunction = functionMetadata.pythonCompiledFunction; - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - Map> bytecodeIndexToArgumentorsMap = functionMetadata.bytecodeCounterToCodeArgumenterList; - - Map> exceptionTableTryBlockMap = new HashMap<>(); - Map exceptionTableStartLabelMap = new HashMap<>(); - Map exceptionTableTargetLabelMap = new HashMap<>(); - Set tryBlockStartInstructionSet = new HashSet<>(); - - for (ExceptionBlock exceptionBlock : pythonCompiledFunction.co_exceptiontable.getEntries()) { - if (exceptionBlock.blockStartInstructionInclusive == exceptionBlock.blockEndInstructionExclusive) { - continue; // Empty try block range - } - tryBlockStartInstructionSet.add(exceptionBlock.blockStartInstructionInclusive); - StackMetadata stackMetadata = stackMetadataForOpcodeIndex.get(exceptionBlock.blockStartInstructionInclusive); - - exceptionTableTryBlockMap.computeIfAbsent(exceptionBlock.blockStartInstructionInclusive, index -> new ArrayList<>()) - .add(() -> { - Label startLabel = - exceptionTableStartLabelMap.computeIfAbsent(exceptionBlock.blockStartInstructionInclusive, - offset -> new Label()); - Label endLabel = bytecodeCounterToLabelMap.computeIfAbsent(exceptionBlock.blockEndInstructionExclusive, - offset -> new Label()); - Label targetLabel = exceptionTableTargetLabelMap.computeIfAbsent(exceptionBlock.targetInstruction, - offset -> new Label()); - - if (exceptionBlock.blockStartInstructionInclusive > exceptionBlock.targetInstruction) { - return; - } - functionMetadata.methodVisitor.visitTryCatchBlock(startLabel, endLabel, targetLabel, - Type.getInternalName(PythonBaseException.class)); - }); - - bytecodeIndexToArgumentorsMap.computeIfAbsent(exceptionBlock.targetInstruction, index -> new ArrayList<>()) - .add(() -> ExceptionImplementor.startExceptBlock(functionMetadata, stackMetadata, exceptionBlock)); - } - - // Do this after so the startExceptBlock code is before the code to store the stack - for (Integer tryBlockStart : tryBlockStartInstructionSet) { - StackMetadata stackMetadata = stackMetadataForOpcodeIndex.get(tryBlockStart); - pythonCompiledFunction.co_exceptiontable.getEntries().stream() - .filter(block -> block.blockStartInstructionInclusive == tryBlockStart) - .forEach(exceptionBlock -> { - bytecodeIndexToArgumentorsMap.computeIfAbsent(tryBlockStart, index -> new ArrayList<>()) - .add(() -> StackManipulationImplementor.storeExceptionTableStack(functionMetadata, - stackMetadata, - exceptionBlock)); - }); - } - - 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); - - if (exceptionTableTargetLabelMap.containsKey(instruction.offset())) { - Label label = exceptionTableTargetLabelMap.get(instruction.offset()); - methodVisitor.visitLabel(label); - } - 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()); - methodVisitor.visitLabel(label); - } - - if (instruction.startsLine().isPresent()) { - Label label = new Label(); - methodVisitor.visitLabel(label); - methodVisitor.visitLineNumber(instruction.startsLine().getAsInt(), label); - } - - runAfterLabelAndBeforeArgumentors.accept(instruction); - - bytecodeIndexToArgumentorsMap.getOrDefault(instruction.offset(), Collections.emptyList()).forEach(Runnable::run); - - if (exceptionTableStartLabelMap.containsKey(instruction.offset())) { - Label label = exceptionTableStartLabelMap.get(instruction.offset()); - methodVisitor.visitLabel(label); - } - - if (stackMetadata.isDeadCode()) { - continue; - } - - opcodeList.get(i).implement(functionMetadata, stackMetadata); - } - } - - private static void translateGeneratorBytecode(MethodVisitor methodVisitor, MethodDescriptor method, - String internalClassName, LocalVariableHelper localVariableHelper, PythonCompiledFunction pythonCompiledFunction) { - Class generatorClass = PythonGeneratorTranslator.translateGeneratorFunction(pythonCompiledFunction); - - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(generatorClass)); - methodVisitor.visitInsn(Opcodes.DUP); - - Type[] javaParameterTypes = Stream.concat(Stream.of(Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonInterpreter.class)), - pythonCompiledFunction.getParameterTypes().stream() - .map(type -> Type.getType(type.getJavaTypeDescriptor()))) - .toArray(Type[]::new); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Interpreter - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - for (int i = 0; i < pythonCompiledFunction.totalArgCount(); i++) { - localVariableHelper.readLocal(methodVisitor, i); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, javaParameterTypes[i + 6].getInternalName()); - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(generatorClass), - "", Type.getMethodDescriptor(Type.VOID_TYPE, javaParameterTypes), - false); - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void movePythonParametersToSlots(MethodVisitor methodVisitor, - String internalClassName, - PythonCompiledFunction pythonCompiledFunction, - LocalVariableHelper localVariableHelper) { - // Call {@link ArgumentSpec#extractArgumentList} to extract argument into a list - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ArgumentSpec.class), - "extractArgumentList", - Type.getMethodDescriptor(Type.getType(List.class), Type.getType(List.class), Type.getType(Map.class)), - false); - - for (int i = 0; i < pythonCompiledFunction.totalArgCount(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - methodVisitor.visitVarInsn(Opcodes.ASTORE, localVariableHelper.getPythonLocalVariableSlot(i)); - } - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * Used for debugging; prints the instruction when it is executed - */ - @SuppressWarnings("unused") - private static void trace(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), - "out", Type.getDescriptor(PrintStream.class)); - methodVisitor.visitLdcInsn(instruction.toString()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), - "println", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), - false); - } - - /** - * Used for debugging; prints TOS - */ - @SuppressWarnings("unused") - public static void print(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), - "out", Type.getDescriptor(PrintStream.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), - "println", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), - false); - } - - /** - * Used for debugging; prints the entire stack - */ - @SuppressWarnings("unused") - public static void printStack(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - int[] stackLocals = new int[stackMetadata.getStackSize()]; - - for (int i = stackLocals.length - 1; i >= 0; i--) { - stackLocals[i] = localVariableHelper.newLocal(); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackLocals[i]); - } - methodVisitor.visitLdcInsn(stackLocals.length); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(PythonLikeObject.class)); - - for (int i = 0; i < stackLocals.length; i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackLocals[i]); - methodVisitor.visitInsn(Opcodes.AASTORE); - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Arrays.class), "toString", - Type.getMethodDescriptor(Type.getType(String.class), Type.getType(Object[].class)), - false); - print(methodVisitor); - methodVisitor.visitInsn(Opcodes.POP); - - for (int i = 0; i < stackLocals.length; i++) { - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackLocals[i]); - } - - for (int i = 0; i < stackLocals.length; i++) { - localVariableHelper.freeLocal(); - } - } - - public static String getPythonBytecodeListing(PythonCompiledFunction pythonCompiledFunction) { - StringBuilder out = new StringBuilder(); - out.append("qualified_name = ").append(pythonCompiledFunction.qualifiedName).append("\n"); - out.append("co_varnames = ").append(pythonCompiledFunction.co_varnames).append("\n"); - out.append("co_cellvars = ").append(pythonCompiledFunction.co_cellvars).append("\n"); - out.append("co_freevars = ").append(pythonCompiledFunction.co_freevars).append("\n"); - - out.append(pythonCompiledFunction.instructionList.stream() - .map(PythonBytecodeInstruction::toString) - .collect(Collectors.joining("\n"))); - out.append("\nco_exceptiontable = ").append(pythonCompiledFunction.co_exceptiontable).append("\n"); - return out.toString(); - } - - public static void visitGeneratedLineNumber(MethodVisitor methodVisitor) { - Label label = new Label(); - methodVisitor.visitLabel(label); - methodVisitor.visitLineNumber(0, label); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java deleted file mode 100644 index 3b988c16..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java +++ /dev/null @@ -1,1705 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import static ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator.ARGUMENT_SPEC_INSTANCE_FIELD_NAME; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.dag.FlowGraph; -import ai.timefold.jpyinterpreter.implementors.DelegatingInterfaceImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaComparableImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaEqualsImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaHashCodeImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaInterfaceImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.implementors.PythonConstantsImplementor; -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; -import ai.timefold.jpyinterpreter.opcodes.object.StoreAttrOpcode; -import ai.timefold.jpyinterpreter.opcodes.variable.LoadFastOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; -import ai.timefold.jpyinterpreter.types.GeneratedFunctionMethodReference; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.wrappers.JavaObjectWrapper; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; -import ai.timefold.jpyinterpreter.util.JavaPythonClassWriter; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.signature.SignatureVisitor; -import org.objectweb.asm.signature.SignatureWriter; - -public class PythonClassTranslator { - static Map functionSignatureToInterfaceName = new HashMap<>(); - - // $ is illegal in variables/methods in Python - public static final String TYPE_FIELD_NAME = "$TYPE"; - public static final String CPYTHON_TYPE_FIELD_NAME = "$CPYTHON_TYPE"; - public static final String JAVA_METHOD_PREFIX = "$method$"; - public static final String PYTHON_JAVA_TYPE_MAPPING_PREFIX = "$pythonJavaTypeMapping"; - - public record PreparedClassInfo(PythonLikeType type, String className, String classInternalName) { - } - - public static PreparedClassInfo getPreparedClassInfo(String pythonClassName, - String module, String qualifiedName) { - String maybeClassName = - PythonBytecodeToJavaBytecodeTranslator.USER_PACKAGE_BASE - + PythonCompiledClass.getGeneratedClassBaseName(module, qualifiedName); - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - return new PreparedClassInfo(PythonLikeType.getTypeForNewClass(pythonClassName, internalClassName), - className, internalClassName); - } - - public static PythonLikeType translatePythonClass(PythonCompiledClass pythonCompiledClass) { - return translatePythonClass(pythonCompiledClass, getPreparedClassInfo(pythonCompiledClass.className, - pythonCompiledClass.module, - pythonCompiledClass.qualifiedName)); - } - - public static PythonLikeType translatePythonClass(PythonCompiledClass pythonCompiledClass, - PreparedClassInfo preparedClassInfo) { - var className = preparedClassInfo.className; - var internalClassName = preparedClassInfo.classInternalName; - - Map instanceMethodNameToMethodDescriptor = new HashMap<>(); - Set superTypeSet; - Set javaInterfaceImplementorSet = new HashSet<>(); - - for (Map.Entry instanceMethodEntry : pythonCompiledClass.instanceFunctionNameToPythonBytecode - .entrySet()) { - switch (instanceMethodEntry.getKey()) { - case "__eq__": - javaInterfaceImplementorSet.add(new JavaEqualsImplementor(internalClassName)); - break; - - case "__hash__": - javaInterfaceImplementorSet.add(new JavaHashCodeImplementor(internalClassName)); - break; - - case "__lt__": - case "__le__": - case "__gt__": - case "__ge__": - javaInterfaceImplementorSet - .add(new JavaComparableImplementor(internalClassName, instanceMethodEntry.getKey())); - break; - } - } - - for (Class javaInterface : pythonCompiledClass.javaInterfaces) { - javaInterfaceImplementorSet.add( - new DelegatingInterfaceImplementor(internalClassName, javaInterface, - instanceMethodNameToMethodDescriptor)); - } - - if (pythonCompiledClass.superclassList.isEmpty()) { - superTypeSet = Set.of(CPythonBackedPythonLikeObject.CPYTHON_BACKED_OBJECT_TYPE); - } else { - superTypeSet = new LinkedHashSet<>(pythonCompiledClass.superclassList.size()); - for (int i = 0; i < pythonCompiledClass.superclassList.size(); i++) { - PythonLikeType superType = pythonCompiledClass.superclassList.get(i); - if (superType == BuiltinTypes.BASE_TYPE || superType == null) { - superTypeSet.add(CPythonBackedPythonLikeObject.CPYTHON_BACKED_OBJECT_TYPE); - } else { - superTypeSet.add(superType); - } - } - } - - List superTypeList = new ArrayList<>(superTypeSet); - PythonLikeType pythonLikeType = preparedClassInfo.type; - pythonLikeType.initializeNewType(superTypeList); - PythonLikeType superClassType = superTypeList.get(0); - Set instanceAttributeSet = new HashSet<>(); - pythonCompiledClass.instanceFunctionNameToPythonBytecode.values().forEach(instanceMethod -> { - try { - instanceAttributeSet.addAll(getReferencedSelfAttributes(instanceMethod)); - } catch (UnsupportedOperationException e) { - // silently ignore unsupported operations - } catch (Exception e) { - e.printStackTrace(); - System.out.println("WARNING: Ignoring exception"); - //e.printStackTrace(); - //System.out.println("globals: " + instanceMethod.globalsMap); - //System.out.println("co_constants: " + instanceMethod.co_constants); - //System.out.println("co_names: " + instanceMethod.co_names); - //System.out.println("co_varnames: " + instanceMethod.co_varnames); - System.out.println("Instructions:"); - System.out.println(instanceMethod.instructionList.stream() - .map(PythonBytecodeInstruction::toString) - .collect(Collectors.joining("\n"))); - } - }); - - List nonObjectInterfaceImplementors = javaInterfaceImplementorSet.stream() - .filter(implementor -> !Object.class.equals(implementor.getInterfaceClass())) - .toList(); - - String[] interfaces = new String[nonObjectInterfaceImplementors.size()]; - for (int i = 0; i < nonObjectInterfaceImplementors.size(); i++) { - interfaces[i] = Type.getInternalName(nonObjectInterfaceImplementors.get(i).getInterfaceClass()); - } - - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - superClassType.getJavaTypeInternalName(), interfaces); - - classWriter.visitSource(pythonCompiledClass.moduleFilePath, null); - - for (var annotation : AnnotationMetadata.getAnnotationListWithoutRepeatable(pythonCompiledClass.annotations)) { - annotation.addAnnotationTo(classWriter); - } - - pythonCompiledClass.staticAttributeNameToObject.forEach(pythonLikeType::$setAttribute); - - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, TYPE_FIELD_NAME, Type.getDescriptor(PythonLikeType.class), - null, null); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, CPYTHON_TYPE_FIELD_NAME, - Type.getDescriptor(PythonLikeType.class), - null, null); - - for (Map.Entry staticAttributeEntry : pythonCompiledClass.staticAttributeNameToObject - .entrySet()) { - pythonLikeType.$setAttribute(staticAttributeEntry.getKey(), staticAttributeEntry.getValue()); - } - - for (var attributeName : pythonCompiledClass.typeAnnotations.keySet()) { - if (pythonLikeType.$getAttributeOrNull(attributeName) == null) { - instanceAttributeSet.add(attributeName); - } - } - - for (int i = 0; i < pythonCompiledClass.pythonJavaTypeMappings.size(); i++) { - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, PYTHON_JAVA_TYPE_MAPPING_PREFIX + i, - Type.getDescriptor(PythonJavaTypeMapping.class), null, null); - } - - Map attributeNameToTypeMap = new HashMap<>(); - instanceAttributeSet.removeAll(pythonCompiledClass.staticAttributeDescriptorNames); - try { - var parentClass = superClassType.getJavaClass(); - while (parentClass != Object.class) { - for (Field field : parentClass.getFields()) { - instanceAttributeSet.remove(field.getName()); - } - parentClass = parentClass.getSuperclass(); - } - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e); - } - - for (String attributeName : instanceAttributeSet) { - var typeHint = pythonCompiledClass.typeAnnotations.getOrDefault(attributeName, - TypeHint.withoutAnnotations(BuiltinTypes.BASE_TYPE)); - PythonLikeType type = typeHint.type(); - PythonLikeType javaGetterType = typeHint.javaGetterType(); - if (type == null) { // null might be in __annotations__ - type = BuiltinTypes.BASE_TYPE; - } - - attributeNameToTypeMap.put(attributeName, type); - FieldVisitor fieldVisitor; - String javaFieldTypeDescriptor; - String getterTypeDescriptor; - String signature = null; - boolean isJavaType; - if (type.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - javaFieldTypeDescriptor = Type.getDescriptor(type.getJavaObjectWrapperType()); - getterTypeDescriptor = javaFieldTypeDescriptor; - fieldVisitor = - classWriter.visitField(Modifier.PUBLIC, getJavaFieldName(attributeName), javaFieldTypeDescriptor, - null, null); - isJavaType = true; - } else { - if (typeHint.genericArgs() != null) { - var signatureWriter = new SignatureWriter(); - visitSignature(typeHint, signatureWriter); - signature = signatureWriter.toString(); - } - javaFieldTypeDescriptor = 'L' + type.getJavaTypeInternalName() + ';'; - getterTypeDescriptor = javaGetterType.getJavaTypeDescriptor(); - fieldVisitor = - classWriter.visitField(Modifier.PUBLIC, getJavaFieldName(attributeName), javaFieldTypeDescriptor, - signature, null); - isJavaType = false; - } - fieldVisitor.visitEnd(); - - createJavaGetterSetter(classWriter, pythonCompiledClass, - preparedClassInfo, attributeName, - Type.getType(javaFieldTypeDescriptor), - Type.getType(getterTypeDescriptor), - signature, - typeHint); - - FieldDescriptor fieldDescriptor = - new FieldDescriptor(attributeName, getJavaFieldName(attributeName), internalClassName, - javaFieldTypeDescriptor, type, true, isJavaType); - pythonLikeType.addInstanceField(fieldDescriptor); - } - - // No args constructor for creating instance of this class - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInterpreter.class), "DEFAULT", - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, TYPE_FIELD_NAME, - Type.getDescriptor(PythonLikeType.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassType.getJavaTypeInternalName(), "", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonInterpreter.class), - Type.getType(PythonLikeType.class)), - false); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - // interpreter + type arg constructor for creating subclass of this class - methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonInterpreter.class), Type.getType(PythonLikeType.class)), - null, null); - methodVisitor.visitParameter("subclassType", 0); - - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassType.getJavaTypeInternalName(), "", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonInterpreter.class), - Type.getType(PythonLikeType.class)), - false); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - createGetAttribute(classWriter, internalClassName, superClassType.getJavaTypeInternalName(), - instanceAttributeSet, - attributeNameToTypeMap); - createSetAttribute(classWriter, internalClassName, superClassType.getJavaTypeInternalName(), - instanceAttributeSet, - attributeNameToTypeMap); - createDeleteAttribute(classWriter, internalClassName, superClassType.getJavaTypeInternalName(), - instanceAttributeSet, - attributeNameToTypeMap); - - for (Map.Entry instanceMethodEntry : pythonCompiledClass.instanceFunctionNameToPythonBytecode - .entrySet()) { - instanceMethodEntry.getValue().methodKind = PythonMethodKind.VIRTUAL_METHOD; - createInstanceMethod(pythonLikeType, classWriter, internalClassName, instanceMethodEntry.getKey(), - instanceMethodEntry.getValue(), instanceMethodNameToMethodDescriptor); - } - - for (Map.Entry staticMethodEntry : pythonCompiledClass.staticFunctionNameToPythonBytecode - .entrySet()) { - staticMethodEntry.getValue().methodKind = PythonMethodKind.STATIC_METHOD; - createStaticMethod(pythonLikeType, classWriter, internalClassName, staticMethodEntry.getKey(), - staticMethodEntry.getValue()); - } - - for (Map.Entry classMethodEntry : pythonCompiledClass.classFunctionNameToPythonBytecode - .entrySet()) { - classMethodEntry.getValue().methodKind = PythonMethodKind.CLASS_METHOD; - createClassMethod(pythonLikeType, classWriter, internalClassName, classMethodEntry.getKey(), - classMethodEntry.getValue()); - } - - boolean isCpythonBacked; - - try { - isCpythonBacked = CPythonBackedPythonLikeObject.class.isAssignableFrom(superClassType.getJavaClass()); - } catch (ClassNotFoundException e) { - isCpythonBacked = false; - } - - if (isCpythonBacked) { - createCPythonOperationMethods(classWriter, internalClassName, superClassType.getJavaTypeInternalName(), - attributeNameToTypeMap); - } - - javaInterfaceImplementorSet.forEach(implementor -> implementor.implement(classWriter, pythonCompiledClass)); - - classWriter.visitEnd(); - - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - pythonLikeType.$setAttribute("__name__", PythonString.valueOf(pythonCompiledClass.className)); - pythonLikeType.$setAttribute("__qualname__", PythonString.valueOf(pythonCompiledClass.qualifiedName)); - pythonLikeType.$setAttribute("__module__", PythonString.valueOf(pythonCompiledClass.module)); - - PythonLikeDict annotations = new PythonLikeDict(); - pythonCompiledClass.typeAnnotations - .forEach((name, type) -> annotations.put(PythonString.valueOf(name), type.type())); - pythonLikeType.$setAttribute("__annotations__", annotations); - - PythonLikeTuple mro = new PythonLikeTuple(); - mro.addAll(superTypeList); - pythonLikeType.$setAttribute("__mro__", mro); - - Class generatedClass; - try { - generatedClass = (Class) BuiltinTypes.asmClassLoader.loadClass(className); - generatedClass.getField(TYPE_FIELD_NAME).set(null, pythonLikeType); - generatedClass.getField(CPYTHON_TYPE_FIELD_NAME).set(null, pythonCompiledClass.binaryType); - for (int i = 0; i < pythonCompiledClass.pythonJavaTypeMappings.size(); i++) { - generatedClass.getField(PYTHON_JAVA_TYPE_MAPPING_PREFIX + i) - .set(null, pythonCompiledClass.pythonJavaTypeMappings.get(i)); - } - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException("Impossible State: could not access type static field for generated class (" - + className + ").", e); - } - - Class initFunctionClass = null; - for (Map.Entry instanceMethodEntry : pythonCompiledClass.instanceFunctionNameToPythonBytecode - .entrySet()) { - InterfaceDeclaration interfaceDeclaration = - getInterfaceForInstancePythonFunction(internalClassName, instanceMethodEntry.getValue()); - Class createdFunctionClass = createBytecodeForMethodAndSetOnClass(className, pythonLikeType, - pythonCompiledClass.binaryType, generatedClass, - instanceMethodEntry, - interfaceDeclaration, PythonMethodKind.VIRTUAL_METHOD); - if (instanceMethodEntry.getKey().equals("__init__")) { - initFunctionClass = createdFunctionClass; - } - } - - for (Map.Entry staticMethodEntry : pythonCompiledClass.staticFunctionNameToPythonBytecode - .entrySet()) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForPythonFunction(staticMethodEntry.getValue()); - createBytecodeForMethodAndSetOnClass(className, pythonLikeType, pythonCompiledClass.binaryType, generatedClass, - staticMethodEntry, - interfaceDeclaration, PythonMethodKind.STATIC_METHOD); - } - - for (Map.Entry classMethodEntry : pythonCompiledClass.classFunctionNameToPythonBytecode - .entrySet()) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForClassPythonFunction(classMethodEntry.getValue()); - createBytecodeForMethodAndSetOnClass(className, pythonLikeType, pythonCompiledClass.binaryType, generatedClass, - classMethodEntry, - interfaceDeclaration, PythonMethodKind.CLASS_METHOD); - } - - pythonLikeType.setConstructor(createConstructor(internalClassName, - pythonCompiledClass.instanceFunctionNameToPythonBytecode.get("__init__"), - generatedClass)); - - PythonOverloadImplementor.createDispatchesFor(pythonLikeType); - return pythonLikeType; - } - - private static void visitSignature(TypeHint typeHint, SignatureVisitor signatureVisitor) { - signatureVisitor.visitClassType(typeHint.type().getJavaTypeInternalName()); - if (typeHint.genericArgs() != null) { - for (TypeHint genericArg : typeHint.genericArgs()) { - visitSignature(genericArg, signatureVisitor.visitTypeArgument('=')); - } - } - signatureVisitor.visitEnd(); - } - - public static void setSelfStaticInstances(PythonCompiledClass pythonCompiledClass, Class generatedClass, - PythonLikeType pythonLikeType, - Map instanceMap) { - if (!CPythonBackedPythonLikeObject.class.isAssignableFrom(generatedClass)) { - return; - } - - pythonCompiledClass.staticAttributeNameToClassInstance.forEach((attributeName, instancePointer) -> { - try { - CPythonBackedPythonLikeObject objectInstance = - (CPythonBackedPythonLikeObject) generatedClass.getConstructor().newInstance(); - Number pythonReferenceId = CPythonBackedPythonInterpreter.getPythonReferenceId(instancePointer); - instanceMap.put(pythonReferenceId, objectInstance); - objectInstance.$setCPythonReference(instancePointer); - objectInstance.$setInstanceMap(instanceMap); - objectInstance.$readFieldsFromCPythonReference(); - pythonLikeType.$setAttribute(attributeName, objectInstance); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException("Unable to construct instance of class (" + generatedClass + ")", e); - } - }); - } - - public static String getJavaFieldName(String pythonFieldName) { - return pythonFieldName; - } - - public static String getPythonFieldName(String javaFieldName) { - return javaFieldName; - } - - public static String getJavaMethodName(String pythonMethodName) { - return JAVA_METHOD_PREFIX + pythonMethodName; - } - - public static String getPythonMethodName(String javaMethodName) { - return javaMethodName.substring(JAVA_METHOD_PREFIX.length()); - } - - private static Class createBytecodeForMethodAndSetOnClass(String className, PythonLikeType pythonLikeType, - PythonLikeType cPythonType, - Class generatedClass, - Map.Entry methodEntry, - InterfaceDeclaration interfaceDeclaration, - PythonMethodKind pythonMethodKind) { - Class functionClass; - Object functionInstance; - - try { - functionInstance = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToInstance(methodEntry.getValue(), - new MethodDescriptor(interfaceDeclaration.interfaceName, - MethodDescriptor.MethodType.INTERFACE, "invoke", - interfaceDeclaration.methodDescriptor), - pythonMethodKind == PythonMethodKind.VIRTUAL_METHOD); - functionClass = functionInstance.getClass(); - functionClass.getField(PythonBytecodeToJavaBytecodeTranslator.CLASS_CELL_STATIC_FIELD_NAME).set(null, - pythonLikeType); - } catch (Exception e) { - functionClass = createPythonWrapperMethod(methodEntry.getKey(), methodEntry.getValue(), - interfaceDeclaration, pythonMethodKind == PythonMethodKind.VIRTUAL_METHOD); - try { - functionInstance = functionClass.getConstructor(PythonLikeType.class).newInstance(cPythonType); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { - throw new IllegalStateException( - "Cannot create instance of Python native wrapper despite it being just generated", ex); - } - } - - try { - PythonLikeObject translatedPythonMethodWrapper; - PythonCompiledFunction function = methodEntry.getValue(); - pythonLikeType.clearMethod(methodEntry.getKey()); - String javaMethodDescriptor = Arrays.stream(generatedClass.getDeclaredMethods()) - .filter(method -> method.getName().equals(getJavaMethodName(methodEntry.getKey()))) - .map(Type::getMethodDescriptor) - .findFirst().orElseThrow(); - ArgumentSpec argumentSpec = function.getArgumentSpecMapper() - .apply(function.defaultPositionalArguments, function.defaultKeywordArguments); - - switch (pythonMethodKind) { - case VIRTUAL_METHOD: - translatedPythonMethodWrapper = new GeneratedFunctionMethodReference(functionInstance, - functionClass.getMethods()[0], - Map.of(), - PythonLikeFunction.getFunctionType()); - pythonLikeType.addMethod(methodEntry.getKey(), - argumentSpec.asPythonFunctionSignature(className.replace('.', '/'), - getJavaMethodName(methodEntry.getKey()), - javaMethodDescriptor)); - break; - - case STATIC_METHOD: - translatedPythonMethodWrapper = new GeneratedFunctionMethodReference(functionInstance, - functionClass.getMethods()[0], - Map.of(), - PythonLikeFunction.getStaticFunctionType()); - pythonLikeType.addMethod(methodEntry.getKey(), - argumentSpec.asStaticPythonFunctionSignature(className.replace('.', '/'), - getJavaMethodName(methodEntry.getKey()), - javaMethodDescriptor)); - break; - - case CLASS_METHOD: - translatedPythonMethodWrapper = new GeneratedFunctionMethodReference(functionInstance, - functionClass.getMethods()[0], - Map.of(), - PythonLikeFunction.getClassFunctionType()); - pythonLikeType.addMethod(methodEntry.getKey(), - argumentSpec.asClassPythonFunctionSignature(className.replace('.', '/'), - getJavaMethodName(methodEntry.getKey()), - javaMethodDescriptor)); - break; - - default: - throw new IllegalStateException("Unhandled case: " + pythonMethodKind); - } - - generatedClass.getField(getJavaMethodName(methodEntry.getKey())) - .set(null, functionInstance); - pythonLikeType.$setAttribute(methodEntry.getKey(), translatedPythonMethodWrapper); - return functionClass; - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new IllegalStateException("Impossible State: could not access method (" + methodEntry.getKey() - + ") static field for generated class (" - + className + ").", e); - } - } - - private static Class createPythonWrapperMethod(String methodName, PythonCompiledFunction pythonCompiledFunction, - InterfaceDeclaration interfaceDeclaration, boolean isVirtual) { - String maybeClassName = PythonBytecodeToJavaBytecodeTranslator.GENERATED_PACKAGE_BASE - + pythonCompiledFunction.getGeneratedClassBaseName() + "$$Wrapper"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(Object.class), new String[] { interfaceDeclaration.interfaceName }); - - classWriter.visitSource("", null); - - classWriter.visitField(Modifier.PUBLIC | Modifier.FINAL, "$binaryType", Type.getDescriptor(PythonLikeType.class), - null, null); - classWriter.visitField(Modifier.STATIC | Modifier.PUBLIC, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class), - null, null); - - // Constructor that takes a PythonLikeType - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonLikeType.class)), - null, null); - - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, "$binaryType", - Type.getDescriptor(PythonLikeType.class)); - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - // Interface method - methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "invoke", interfaceDeclaration.methodDescriptor, - null, null); - - for (int i = 0; i < pythonCompiledFunction.totalArgCount(); i++) { - methodVisitor.visitParameter("parameter" + i, 0); - } - - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, "$binaryType", - Type.getDescriptor(PythonLikeType.class)); - methodVisitor.visitLdcInsn(methodName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); - - methodVisitor.visitLdcInsn(pythonCompiledFunction.totalArgCount()); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Object.class)); - for (int i = 0; i < pythonCompiledFunction.totalArgCount(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitVarInsn(Opcodes.ALOAD, i + 1); - methodVisitor.visitInsn(Opcodes.AASTORE); - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Arrays.class), "asList", - Type.getMethodDescriptor(Type.getType(List.class), Type.getType(Object[].class)), - false); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(List.class), - Type.getType(Map.class), Type.getType(PythonLikeObject.class)), - true); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - Type.getReturnType(interfaceDeclaration.methodDescriptor).getInternalName()); - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - classWriter.visitEnd(); - - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - try { - return BuiltinTypes.asmClassLoader.loadClass(className); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Cannot load class " + className + " despite it being just generated", e); - } - } - - private static PythonLikeFunction createConstructor(String classInternalName, - PythonCompiledFunction initFunction, - Class typeGeneratedClass) { - String maybeClassName = PythonBytecodeToJavaBytecodeTranslator.GENERATED_PACKAGE_BASE - + classInternalName.replace('/', '.') + "$$Constructor"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String constructorClassName = maybeClassName; - String constructorInternalClassName = constructorClassName.replace('.', '/'); - - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, constructorInternalClassName, null, Type.getInternalName(Object.class), - new String[] { - Type.getInternalName(PythonLikeFunction.class) - }); - - classWriter.visitSource(initFunction != null ? initFunction.moduleFilePath : "", null); - - classWriter.visitField(Modifier.STATIC | Modifier.PUBLIC, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class), - null, null); - - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - Type generatedClassType = Type.getType('L' + classInternalName + ';'); - methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), Type.getType(Map.class), Type.getType(PythonLikeObject.class)), - null, null); - - methodVisitor.visitCode(); - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - methodVisitor.visitTypeInsn(Opcodes.NEW, classInternalName); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, classInternalName, "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - if (initFunction != null) { - Label start = new Label(); - methodVisitor.visitLabel(start); - methodVisitor.visitLineNumber(initFunction.getFirstLine(), start); - methodVisitor.visitInsn(Opcodes.DUP); - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, constructorInternalClassName, ARGUMENT_SPEC_INSTANCE_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ArgumentSpec.class), - "extractArgumentList", - Type.getMethodDescriptor(Type.getType(List.class), Type.getType(List.class), Type.getType(Map.class)), - false); - - List initParameterTypes = initFunction.getParameterTypes(); - for (int i = 1; i < initParameterTypes.size(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i - 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, initParameterTypes.get(i).getJavaTypeInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - - Type[] parameterTypes = new Type[initFunction.totalArgCount() - 1]; - List parameterTypeAnnotations = initFunction.getParameterTypes(); - for (int i = 1; i < parameterTypeAnnotations.size(); i++) { - parameterTypes[i - 1] = Type.getType('L' + parameterTypeAnnotations.get(i).getJavaTypeInternalName() + ';'); - } - - Type returnType = getVirtualFunctionReturnType(initFunction); - - String initMethodDescriptor = Type.getMethodDescriptor(returnType, parameterTypes); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, classInternalName, getJavaMethodName("__init__"), - initMethodDescriptor, false); - - methodVisitor.visitInsn(Opcodes.POP); - } - - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(-1, -1); - - methodVisitor.visitEnd(); - - classWriter.visitEnd(); - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, constructorClassName, - classWriter.toByteArray()); - - try { - @SuppressWarnings("unchecked") - Class generatedClass = - (Class) BuiltinTypes.asmClassLoader.loadClass(constructorClassName); - if (initFunction != null) { - Object method = typeGeneratedClass.getField(getJavaMethodName("__init__")).get(null); - ArgumentSpec spec = - (ArgumentSpec) method.getClass().getField(ARGUMENT_SPEC_INSTANCE_FIELD_NAME).get(method); - generatedClass.getField(ARGUMENT_SPEC_INSTANCE_FIELD_NAME).set(null, spec); - } - return generatedClass.getConstructor().newInstance(); - } catch (ClassNotFoundException | RuntimeException | InstantiationException | NoSuchMethodException - | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - constructorClassName + ") despite it being just generated.", e); - } - } - - private record MatchedMapping(int index, PythonJavaTypeMapping pythonJavaTypeMapping) { - } - - private static void createJavaGetterSetter(ClassWriter classWriter, - PythonCompiledClass pythonCompiledClass, - PreparedClassInfo preparedClassInfo, - String attributeName, Type attributeType, Type getterType, - String signature, - TypeHint typeHint) { - MatchedMapping matchedMapping = null; - for (int i = 0; i < pythonCompiledClass.pythonJavaTypeMappings.size(); i++) { - var mapping = pythonCompiledClass.pythonJavaTypeMappings.get(i); - if (mapping.getPythonType().equals(typeHint.javaGetterType())) { - matchedMapping = new MatchedMapping(i, mapping); - getterType = Type.getType(mapping.getJavaType()); - } - } - createJavaGetter(classWriter, preparedClassInfo, matchedMapping, attributeName, attributeType, getterType, signature, - typeHint); - createJavaSetter(classWriter, preparedClassInfo, matchedMapping, attributeName, attributeType, getterType, signature, - typeHint); - } - - private static void createJavaGetter(ClassWriter classWriter, PreparedClassInfo preparedClassInfo, - MatchedMapping matchedMapping, String attributeName, - Type attributeType, Type getterType, String signature, TypeHint typeHint) { - var getterName = "get" + attributeName.substring(0, 1).toUpperCase() + attributeName.substring(1); - if (signature != null && Objects.equals(attributeType, getterType)) { - signature = "()" + signature; - } - var getterVisitor = classWriter.visitMethod(Modifier.PUBLIC, getterName, Type.getMethodDescriptor(getterType), - signature, null); - var maxStack = 1; - - for (var annotation : AnnotationMetadata.getAnnotationListWithoutRepeatable(typeHint.annotationList())) { - annotation.addAnnotationTo(getterVisitor); - } - - getterVisitor.visitCode(); - getterVisitor.visitVarInsn(Opcodes.ALOAD, 0); - getterVisitor.visitFieldInsn(Opcodes.GETFIELD, preparedClassInfo.classInternalName, - attributeName, attributeType.getDescriptor()); - if (typeHint.type().isInstance(PythonNone.INSTANCE)) { - maxStack = 3; - getterVisitor.visitInsn(Opcodes.DUP); - PythonConstantsImplementor.loadNone(getterVisitor); - Label returnLabel = new Label(); - getterVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, returnLabel); - // field is None, so we want Java to see it as null - getterVisitor.visitInsn(Opcodes.POP); - getterVisitor.visitInsn(Opcodes.ACONST_NULL); - getterVisitor.visitLabel(returnLabel); - // If branch is taken, stack is field - // If branch is not taken, stack is null - } - if (!Objects.equals(attributeType, getterType)) { - if (matchedMapping != null) { - getterVisitor.visitInsn(Opcodes.DUP); - getterVisitor.visitInsn(Opcodes.ACONST_NULL); - Label skipMapping = new Label(); - getterVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, skipMapping); - getterVisitor.visitFieldInsn(Opcodes.GETSTATIC, preparedClassInfo.classInternalName, - PYTHON_JAVA_TYPE_MAPPING_PREFIX + matchedMapping.index, - Type.getDescriptor(PythonJavaTypeMapping.class)); - getterVisitor.visitInsn(Opcodes.SWAP); - getterVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonJavaTypeMapping.class), "toJavaObject", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), - true); - getterVisitor.visitLabel(skipMapping); - } - getterVisitor.visitTypeInsn(Opcodes.CHECKCAST, getterType.getInternalName()); - } - getterVisitor.visitInsn(Opcodes.ARETURN); - getterVisitor.visitMaxs(maxStack, 0); - getterVisitor.visitEnd(); - } - - private static void createJavaSetter(ClassWriter classWriter, PreparedClassInfo preparedClassInfo, - MatchedMapping matchedMapping, String attributeName, - Type attributeType, Type setterType, String signature, TypeHint typeHint) { - var setterName = "set" + attributeName.substring(0, 1).toUpperCase() + attributeName.substring(1); - if (signature != null && Objects.equals(attributeType, setterType)) { - signature = "(" + signature + ")V"; - } - var setterVisitor = classWriter.visitMethod(Modifier.PUBLIC, setterName, Type.getMethodDescriptor(Type.VOID_TYPE, - setterType), - signature, null); - var maxStack = 2; - setterVisitor.visitCode(); - setterVisitor.visitVarInsn(Opcodes.ALOAD, 0); - setterVisitor.visitVarInsn(Opcodes.ALOAD, 1); - if (typeHint.type().isInstance(PythonNone.INSTANCE)) { - maxStack = 4; - // We want to replace null with None - setterVisitor.visitInsn(Opcodes.DUP); - setterVisitor.visitInsn(Opcodes.ACONST_NULL); - Label setFieldLabel = new Label(); - setterVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, setFieldLabel); - // set value is null, so we want Python to see it as None - setterVisitor.visitInsn(Opcodes.POP); - PythonConstantsImplementor.loadNone(setterVisitor); - setterVisitor.visitLabel(setFieldLabel); - // If branch is taken, stack is (non-null instance) - // If branch is not taken, stack is None - } - if (!Objects.equals(attributeType, setterType)) { - if (matchedMapping != null) { - setterVisitor.visitVarInsn(Opcodes.ALOAD, 1); - setterVisitor.visitInsn(Opcodes.ACONST_NULL); - Label skipMapping = new Label(); - setterVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, skipMapping); - setterVisitor.visitFieldInsn(Opcodes.GETSTATIC, preparedClassInfo.classInternalName, - PYTHON_JAVA_TYPE_MAPPING_PREFIX + matchedMapping.index, - Type.getDescriptor(PythonJavaTypeMapping.class)); - setterVisitor.visitInsn(Opcodes.SWAP); - setterVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonJavaTypeMapping.class), "toPythonObject", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), - true); - setterVisitor.visitLabel(skipMapping); - } - setterVisitor.visitTypeInsn(Opcodes.CHECKCAST, attributeType.getInternalName()); - } - setterVisitor.visitFieldInsn(Opcodes.PUTFIELD, preparedClassInfo.classInternalName, - attributeName, attributeType.getDescriptor()); - setterVisitor.visitInsn(Opcodes.RETURN); - setterVisitor.visitMaxs(maxStack, 0); - setterVisitor.visitEnd(); - } - - private static void addAnnotationsToMethod(PythonCompiledFunction function, MethodVisitor methodVisitor) { - var returnTypeHint = function.typeAnnotations.get("return"); - if (returnTypeHint != null) { - for (var annotation : AnnotationMetadata.getAnnotationListWithoutRepeatable(returnTypeHint.annotationList())) { - annotation.addAnnotationTo(methodVisitor); - } - } - } - - private static void createInstanceMethod(PythonLikeType pythonLikeType, ClassWriter classWriter, String internalClassName, - String methodName, PythonCompiledFunction function, - Map instanceMethodNameToMethodDescriptor) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForInstancePythonFunction(internalClassName, function); - String interfaceDescriptor = interfaceDeclaration.descriptor(); - String javaMethodName = getJavaMethodName(methodName); - - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, javaMethodName, interfaceDescriptor, - null, null); - instanceMethodNameToMethodDescriptor.put(methodName, interfaceDeclaration); - Type returnType = getVirtualFunctionReturnType(function); - - List parameterPythonTypeList = function.getParameterTypes(); - Type[] javaParameterTypes = new Type[Math.max(0, function.totalArgCount() - 1)]; - - for (int i = 1; i < function.totalArgCount(); i++) { - javaParameterTypes[i - 1] = Type.getType(parameterPythonTypeList.get(i).getJavaTypeDescriptor()); - } - String javaMethodDescriptor = Type.getMethodDescriptor(returnType, javaParameterTypes); - String signature = getFunctionSignature(function, javaMethodDescriptor); - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, javaMethodName, javaMethodDescriptor, signature, null); - - createInstanceOrStaticMethodBody(internalClassName, javaMethodName, javaParameterTypes, - interfaceDeclaration.methodDescriptor, function, - interfaceDeclaration.interfaceName, interfaceDescriptor, methodVisitor); - - pythonLikeType.addMethod(methodName, - new PythonFunctionSignature(new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.VIRTUAL, - javaMethodName, javaMethodDescriptor), - function.getReturnType().orElse(BuiltinTypes.BASE_TYPE), - function.totalArgCount() > 0 - ? function.getParameterTypes().subList(1, function.getParameterTypes().size()) - : Collections.emptyList())); - } - - private static void createStaticMethod(PythonLikeType pythonLikeType, ClassWriter classWriter, String internalClassName, - String methodName, PythonCompiledFunction function) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForPythonFunction(function); - String interfaceDescriptor = 'L' + interfaceDeclaration.interfaceName + ';'; - String javaMethodName = getJavaMethodName(methodName); - String signature = getFunctionSignature(function, function.getAsmMethodDescriptorString()); - - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, javaMethodName, interfaceDescriptor, - null, null); - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC | Modifier.STATIC, javaMethodName, - function.getAsmMethodDescriptorString(), signature, null); - - List parameterPythonTypeList = function.getParameterTypes(); - Type[] javaParameterTypes = new Type[function.totalArgCount()]; - - for (int i = 0; i < function.totalArgCount(); i++) { - javaParameterTypes[i] = Type.getType('L' + parameterPythonTypeList.get(i).getJavaTypeInternalName() + ';'); - } - - createInstanceOrStaticMethodBody(internalClassName, javaMethodName, javaParameterTypes, - interfaceDeclaration.methodDescriptor, function, - interfaceDeclaration.interfaceName, interfaceDescriptor, methodVisitor); - - pythonLikeType.addMethod(methodName, - new PythonFunctionSignature(new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.STATIC, - javaMethodName, function.getAsmMethodDescriptorString()), - function.getReturnType().orElse(BuiltinTypes.BASE_TYPE), - function.getParameterTypes())); - } - - private static void createClassMethod(PythonLikeType pythonLikeType, ClassWriter classWriter, String internalClassName, - String methodName, PythonCompiledFunction function) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForClassPythonFunction(function); - String interfaceDescriptor = 'L' + interfaceDeclaration.interfaceName + ';'; - String javaMethodName = getJavaMethodName(methodName); - - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, javaMethodName, interfaceDescriptor, - null, null); - - String javaMethodDescriptor = interfaceDeclaration.methodDescriptor; - String signature = getFunctionSignature(function, javaMethodDescriptor); - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC | Modifier.STATIC, javaMethodName, javaMethodDescriptor, signature, - null); - - for (int i = 0; i < function.getParameterTypes().size(); i++) { - methodVisitor.visitParameter(function.co_varnames.get(i), 0); - } - - addAnnotationsToMethod(function, methodVisitor); - methodVisitor.visitCode(); - - Label start = new Label(); - methodVisitor.visitLabel(start); - methodVisitor.visitLineNumber(function.getFirstLine(), start); - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, javaMethodName, interfaceDescriptor); - - for (int i = 0; i < function.totalArgCount(); i++) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, i); - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfaceDeclaration.interfaceName, "invoke", - interfaceDeclaration.methodDescriptor, true); - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(-1, -1); - - methodVisitor.visitEnd(); - - List parameterTypes = new ArrayList<>(function.getParameterTypes().size()); - parameterTypes.add(BuiltinTypes.TYPE_TYPE); - parameterTypes.addAll(function.getParameterTypes().subList(1, function.getParameterTypes().size())); - - pythonLikeType.addMethod(methodName, - new PythonFunctionSignature(new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.CLASS, - javaMethodName, interfaceDeclaration.methodDescriptor), - function.getReturnType().orElse(BuiltinTypes.BASE_TYPE), - parameterTypes)); - } - - private static void createInstanceOrStaticMethodBody(String internalClassName, String javaMethodName, - Type[] javaParameterTypes, - String methodDescriptorString, - PythonCompiledFunction function, String interfaceInternalName, String interfaceDescriptor, - MethodVisitor methodVisitor) { - for (int i = 0; i < javaParameterTypes.length; i++) { - methodVisitor.visitParameter(function.co_varnames.get(i), 0); - } - addAnnotationsToMethod(function, methodVisitor); - methodVisitor.visitCode(); - - Label start = new Label(); - methodVisitor.visitLabel(start); - methodVisitor.visitLineNumber(function.getFirstLine(), start); - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, javaMethodName, interfaceDescriptor); - for (int i = 0; i < function.totalArgCount(); i++) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, i); - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfaceInternalName, "invoke", methodDescriptorString, true); - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(-1, -1); - - methodVisitor.visitEnd(); - } - - public static Type getVirtualFunctionReturnType(PythonCompiledFunction function) { - return Type.getType('L' + function.getReturnType().map(PythonLikeType::getJavaTypeInternalName) - .orElseGet(() -> getPythonReturnTypeOfFunction(function, true).getJavaTypeInternalName()) + ';'); - } - - public static String getFunctionSignature(PythonCompiledFunction function, - String asmMethodDescriptor) { - var maybeReturnTypeHint = function.getReturnTypeHint(); - if (maybeReturnTypeHint.isPresent()) { - var returnTypeHint = maybeReturnTypeHint.get(); - var signatureWriter = new SignatureWriter(); - Type methodType = Type.getMethodType(asmMethodDescriptor); - for (int i = 0; i < methodType.getArgumentCount(); i++) { - var parameterVisitor = signatureWriter.visitParameterType(); - parameterVisitor.visitClassType(methodType.getArgumentTypes()[i].getInternalName()); - parameterVisitor.visitEnd(); - } - visitSignature(returnTypeHint, signatureWriter.visitReturnType()); - signatureWriter.visitEnd(); - return signatureWriter.toString(); - } - return null; - } - - public static void createGetAttribute(ClassWriter classWriter, String classInternalName, String superInternalName, - Collection instanceAttributes, - Map fieldToType) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$getAttributeOrNull", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - null, null); - - methodVisitor.visitParameter("attribute", 0); - - methodVisitor.visitCode(); - - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 2, field -> { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - var type = fieldToType.get(field); - if (type.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, getJavaFieldName(field), - Type.getDescriptor(type.getJavaObjectWrapperType())); - getWrappedJavaObject(methodVisitor); - } else { - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, getJavaFieldName(field), - 'L' + type.getJavaTypeInternalName() + ';'); - } - methodVisitor.visitInsn(Opcodes.ARETURN); - }, () -> { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superInternalName, "$getAttributeOrNull", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ARETURN); - }, true); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - private static void getWrappedJavaObject(MethodVisitor methodVisitor) { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(JavaObjectWrapper.class)); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(JavaObjectWrapper.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), false); - } - - public static void createSetAttribute(ClassWriter classWriter, String classInternalName, String superInternalName, - Collection instanceAttributes, - Map fieldToType) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$setAttribute", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(String.class), - Type.getType(PythonLikeObject.class)), - null, null); - - methodVisitor.visitParameter("attribute", 0); - methodVisitor.visitParameter("value", 0); - - methodVisitor.visitCode(); - - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 3, field -> { - var type = fieldToType.get(field); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - String typeDescriptor = type.getJavaTypeDescriptor(); - if (type.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - // Need to unwrap the object - getUnwrappedJavaObject(methodVisitor, type); - typeDescriptor = Type.getDescriptor(type.getJavaObjectWrapperType()); - } else { - methodVisitor.visitLdcInsn(Type.getType(type.getJavaTypeDescriptor())); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, type.getJavaTypeInternalName()); - } - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, getJavaFieldName(field), - typeDescriptor); - methodVisitor.visitInsn(Opcodes.RETURN); - }, () -> { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superInternalName, "$setAttribute", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(String.class), - Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitInsn(Opcodes.RETURN); - }, true); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - private static void getUnwrappedJavaObject(MethodVisitor methodVisitor, PythonLikeType type) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(JavaObjectWrapper.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(JavaObjectWrapper.class), - "getWrappedObject", Type.getMethodDescriptor(Type.getType(Object.class)), false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getType(type.getJavaObjectWrapperType()).getInternalName()); - } - - public static void createDeleteAttribute(ClassWriter classWriter, String classInternalName, String superInternalName, - Collection instanceAttributes, - Map fieldToType) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$deleteAttribute", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(String.class)), - null, null); - - methodVisitor.visitParameter("attribute", 0); - - methodVisitor.visitCode(); - - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 2, field -> { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - var fieldType = fieldToType.get(field); - if (fieldType.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, getJavaFieldName(field), - Type.getDescriptor(fieldType.getJavaObjectWrapperType())); - } else { - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, getJavaFieldName(field), - fieldType.getJavaTypeDescriptor()); - } - methodVisitor.visitInsn(Opcodes.RETURN); - }, () -> { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superInternalName, "$deleteAttribute", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.RETURN); - }, true); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - public static void createCPythonOperationMethods(ClassWriter classWriter, String internalClassName, - String superClassInternalName, Map attributeNameToType) { - createReadFromCPythonReference(classWriter, internalClassName, superClassInternalName, attributeNameToType); - createWriteToCPythonReference(classWriter, internalClassName, superClassInternalName, attributeNameToType); - } - - public static void createReadFromCPythonReference(ClassWriter classWriter, String internalClassName, - String superClassInternalName, Map attributeNameToType) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$readFieldsFromCPythonReference", - Type.getMethodDescriptor(Type.VOID_TYPE), null, - null); - methodVisitor.visitCode(); - - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName, - "$readFieldsFromCPythonReference", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(CPythonBackedPythonLikeObject.class), - "$cpythonReference", Type.getDescriptor(OpaquePythonReference.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - Label ifReferenceIsNotNull = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifReferenceIsNotNull); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitLabel(ifReferenceIsNotNull); - for (String field : attributeNameToType.keySet()) { - methodVisitor.visitInsn(Opcodes.DUP2); - methodVisitor.visitLdcInsn(field); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(CPythonBackedPythonLikeObject.class), - "$instanceMap", Type.getDescriptor(Map.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(CPythonBackedPythonInterpreter.class), - "lookupAttributeOnPythonReference", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(OpaquePythonReference.class), - Type.getType(String.class), - Type.getType(Map.class)), - false); - - boolean isAssignableFromNone = false; - - try { - isAssignableFromNone = attributeNameToType.get(field).getJavaClass().isAssignableFrom(PythonNone.class); - } catch (ClassNotFoundException e) { - // do nothing - } - - Label ifFieldIsNone = new Label(); - Label doneSettingField = new Label(); - - if (!isAssignableFromNone) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonNone.class), - "INSTANCE", Type.getDescriptor(PythonNone.class)); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, ifFieldIsNone); - } - - var attributeType = attributeNameToType.get(field); - methodVisitor.visitLdcInsn(Type.getType(attributeType.getJavaTypeDescriptor())); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, attributeNameToType.get(field).getJavaTypeInternalName()); - - if (attributeType.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - Class wrappedJavaType = attributeType.getJavaObjectWrapperType(); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(JavaObjectWrapper.class), - "getWrappedObject", Type.getMethodDescriptor(Type.getType(Object.class)), false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getType(wrappedJavaType).getInternalName()); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, getJavaFieldName(field), - Type.getDescriptor(wrappedJavaType)); - } else { - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, getJavaFieldName(field), - attributeType.getJavaTypeDescriptor()); - } - - if (!isAssignableFromNone) { - methodVisitor.visitJumpInsn(Opcodes.GOTO, doneSettingField); - - methodVisitor.visitLabel(ifFieldIsNone); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - if (attributeType.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, getJavaFieldName(field), - Type.getDescriptor(attributeType.getJavaObjectWrapperType())); - } else { - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, getJavaFieldName(field), - attributeType.getJavaTypeDescriptor()); - } - methodVisitor.visitLabel(doneSettingField); - } - } - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - public static void createWriteToCPythonReference(ClassWriter classWriter, String internalClassName, - String superClassInternalName, Map attributeNameToType) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$writeFieldsToCPythonReference", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpaquePythonReference.class)), null, - null); - methodVisitor.visitCode(); - - PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName, - "$writeFieldsToCPythonReference", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpaquePythonReference.class)), false); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(CPythonBackedPythonLikeObject.class), - "$cpythonReference", Type.getDescriptor(OpaquePythonReference.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - Label ifReferenceIsNotNull = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifReferenceIsNotNull); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitLabel(ifReferenceIsNotNull); - - methodVisitor.visitInsn(Opcodes.SWAP); - for (String field : attributeNameToType.keySet()) { - methodVisitor.visitInsn(Opcodes.DUP2); - var attributeType = attributeNameToType.get(field); - if (attributeType.getJavaTypeInternalName().equals(Type.getInternalName(JavaObjectWrapper.class))) { - var wrappedJavaType = attributeType.getJavaObjectWrapperType(); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, getJavaFieldName(field), - Type.getDescriptor(wrappedJavaType)); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(JavaObjectWrapper.class)); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(JavaObjectWrapper.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), false); - } else { - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, getJavaFieldName(field), - attributeType.getJavaTypeDescriptor()); - } - methodVisitor.visitLdcInsn(field); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(CPythonBackedPythonInterpreter.class), - "setAttributeOnPythonReference", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(OpaquePythonReference.class), - Type.getType(OpaquePythonReference.class), - Type.getType(String.class), - Type.getType(Object.class)), - false); - } - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - public static InterfaceDeclaration getInterfaceForFunctionSignature(FunctionSignature functionSignature) { - return functionSignatureToInterfaceName.computeIfAbsent(functionSignature, - PythonClassTranslator::createInterfaceForFunctionSignature); - } - - public static InterfaceDeclaration getInterfaceForPythonFunction(PythonCompiledFunction pythonCompiledFunction) { - String[] parameterTypes = new String[pythonCompiledFunction.totalArgCount()]; - List parameterTypeAnnotations = pythonCompiledFunction.getParameterTypes(); - for (int i = 0; i < parameterTypeAnnotations.size(); i++) { - parameterTypes[i] = 'L' + parameterTypeAnnotations.get(i).getJavaTypeInternalName() + ';'; - } - - String returnType = 'L' + pythonCompiledFunction.getReturnType() - .orElseGet(() -> getPythonReturnTypeOfFunction(pythonCompiledFunction, false)).getJavaTypeInternalName() + ';'; - - FunctionSignature functionSignature = new FunctionSignature(returnType, parameterTypes); - return functionSignatureToInterfaceName.computeIfAbsent(functionSignature, - PythonClassTranslator::createInterfaceForFunctionSignature); - } - - public static InterfaceDeclaration - getInterfaceForPythonFunctionIgnoringReturn(PythonCompiledFunction pythonCompiledFunction) { - String[] parameterTypes = new String[pythonCompiledFunction.totalArgCount()]; - List parameterTypeAnnotations = pythonCompiledFunction.getParameterTypes(); - - for (int i = 0; i < parameterTypeAnnotations.size(); i++) { - var parameterType = parameterTypeAnnotations.get(i); - parameterTypes[i] = parameterType.getJavaTypeDescriptor(); - } - - String returnType = pythonCompiledFunction.getReturnType() - .orElse(BuiltinTypes.BASE_TYPE).getJavaTypeDescriptor(); - - FunctionSignature functionSignature = new FunctionSignature(returnType, parameterTypes); - return functionSignatureToInterfaceName.computeIfAbsent(functionSignature, - PythonClassTranslator::createInterfaceForFunctionSignature); - } - - public static InterfaceDeclaration getInterfaceForInstancePythonFunction(String instanceInternalClassName, - PythonCompiledFunction pythonCompiledFunction) { - List parameterPythonTypeList = pythonCompiledFunction.getParameterTypes(); - String[] pythonParameterTypes = new String[pythonCompiledFunction.totalArgCount()]; - - if (pythonParameterTypes.length > 0) { - pythonParameterTypes[0] = 'L' + instanceInternalClassName + ';'; - } - - for (int i = 1; i < pythonCompiledFunction.totalArgCount(); i++) { - pythonParameterTypes[i] = 'L' + parameterPythonTypeList.get(i).getJavaTypeInternalName() + ';'; - } - String returnType = 'L' + pythonCompiledFunction.getReturnType().map(PythonLikeType::getJavaTypeInternalName) - .orElseGet(() -> getPythonReturnTypeOfFunction(pythonCompiledFunction, true).getJavaTypeInternalName()) + ';'; - FunctionSignature functionSignature = new FunctionSignature(returnType, pythonParameterTypes); - return functionSignatureToInterfaceName.computeIfAbsent(functionSignature, - PythonClassTranslator::createInterfaceForFunctionSignature); - } - - public static InterfaceDeclaration getInterfaceForClassPythonFunction(PythonCompiledFunction pythonCompiledFunction) { - List parameterPythonTypeList = pythonCompiledFunction.getParameterTypes(); - String[] pythonParameterTypes = new String[pythonCompiledFunction.totalArgCount()]; - - if (pythonParameterTypes.length > 0) { - pythonParameterTypes[0] = Type.getDescriptor(PythonLikeType.class); - } - - for (int i = 1; i < pythonCompiledFunction.totalArgCount(); i++) { - pythonParameterTypes[i] = 'L' + parameterPythonTypeList.get(i).getJavaTypeInternalName() + ';'; - } - String returnType = 'L' + pythonCompiledFunction.getReturnType().map(PythonLikeType::getJavaTypeInternalName) - .orElseGet(() -> getPythonReturnTypeOfFunction(pythonCompiledFunction, true).getJavaTypeInternalName()) + ';'; - FunctionSignature functionSignature = new FunctionSignature(returnType, pythonParameterTypes); - return functionSignatureToInterfaceName.computeIfAbsent(functionSignature, - PythonClassTranslator::createInterfaceForFunctionSignature); - } - - public static InterfaceDeclaration createInterfaceForFunctionSignature(FunctionSignature functionSignature) { - String maybeClassName = functionSignature.getClassName(); - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - - String internalClassName = className.replace('.', '/'); - - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(Opcodes.V11, Modifier.PUBLIC | Modifier.INTERFACE | Modifier.ABSTRACT, internalClassName, null, - Type.getInternalName(Object.class), null); - - classWriter.visitSource("", null); - - Type returnType = Type.getType(functionSignature.returnType); - Type[] parameterTypes = new Type[functionSignature.parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - parameterTypes[i] = Type.getType(functionSignature.parameterTypes[i]); - } - - classWriter.visitMethod(Modifier.PUBLIC | Modifier.ABSTRACT, "invoke", - Type.getMethodDescriptor(returnType, parameterTypes), null, null); - classWriter.visitEnd(); - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - return new InterfaceDeclaration(internalClassName, Type.getMethodDescriptor(returnType, parameterTypes)); - } - - public static Class getInterfaceClassForDeclaration(InterfaceDeclaration interfaceDeclaration) { - try { - return BuiltinTypes.asmClassLoader.loadClass(interfaceDeclaration.interfaceName.replaceAll("/", ".")); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot load " + interfaceDeclaration.interfaceName + - " from the classloader; maybe it was not created?", e); - } - } - - private static FlowGraph createFlowGraph(PythonCompiledFunction pythonCompiledFunction, boolean isVirtual) { - InterfaceDeclaration interfaceDeclaration = getInterfaceForPythonFunctionIgnoringReturn(pythonCompiledFunction); - MethodDescriptor methodDescriptor = new MethodDescriptor(interfaceDeclaration.interfaceName.replace('.', '/'), - MethodDescriptor.MethodType.INTERFACE, - "invoke", - interfaceDeclaration.methodDescriptor); - LocalVariableHelper localVariableHelper = - new LocalVariableHelper(methodDescriptor.getParameterTypes(), pythonCompiledFunction); - List opcodeList = PythonBytecodeToJavaBytecodeTranslator.getOpcodeList(pythonCompiledFunction); - StackMetadata initialStackMetadata = PythonBytecodeToJavaBytecodeTranslator.getInitialStackMetadata(localVariableHelper, - methodDescriptor, isVirtual); - FunctionMetadata functionMetadata = new FunctionMetadata(); - functionMetadata.functionType = PythonBytecodeToJavaBytecodeTranslator.getFunctionType(pythonCompiledFunction); - functionMetadata.bytecodeCounterToLabelMap = new HashMap<>(); - functionMetadata.bytecodeCounterToCodeArgumenterList = new HashMap<>(); - functionMetadata.method = methodDescriptor; - functionMetadata.pythonCompiledFunction = pythonCompiledFunction; - functionMetadata.className = ""; - functionMetadata.methodVisitor = null; - - return FlowGraph.createFlowGraph(functionMetadata, initialStackMetadata, opcodeList); - } - - public static Set getReferencedSelfAttributes(PythonCompiledFunction pythonCompiledFunction) { - FlowGraph flowGraph = createFlowGraph(pythonCompiledFunction, true); - - Set referencedSelfAttributeSet = new HashSet<>(); - - BiConsumer attributeVisitor = (attributeOpcode, stackMetadata) -> { - Set possibleSourceOpcodeSet = stackMetadata.getTOSValueSource().getPossibleSourceOpcodeSet(); - if (possibleSourceOpcodeSet.stream().anyMatch(opcode -> { - if (opcode instanceof LoadFastOpcode || opcode instanceof StoreAttrOpcode - || opcode instanceof DeleteAttrOpcode) { - AbstractOpcode instructionOpcode = (AbstractOpcode) opcode; - return instructionOpcode.getInstruction().arg() == 0; - } - if (opcode instanceof SelfOpcodeWithoutSource) { - return true; - } - return false; - })) { - referencedSelfAttributeSet.add(pythonCompiledFunction.co_names.get(attributeOpcode.getInstruction().arg())); - } - }; - - flowGraph.visitOperations(LoadAttrOpcode.class, attributeVisitor); - flowGraph.visitOperations(StoreAttrOpcode.class, attributeVisitor); - flowGraph.visitOperations(DeleteAttrOpcode.class, attributeVisitor); - - return referencedSelfAttributeSet; - } - - public static PythonLikeType getPythonReturnTypeOfFunction(PythonCompiledFunction pythonCompiledFunction, - boolean isVirtual) { - try { - if (PythonBytecodeToJavaBytecodeTranslator - .getFunctionType(pythonCompiledFunction) == PythonFunctionType.GENERATOR) { - return BuiltinTypes.GENERATOR_TYPE; - } - - FlowGraph flowGraph = createFlowGraph(pythonCompiledFunction, isVirtual); - - List possibleReturnTypeList = new ArrayList<>(); - 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) - .orElse(BuiltinTypes.NONE_TYPE); - } catch (UnsupportedOperationException e) { - // Return the base type if we encounter any unsupported operations - return BuiltinTypes.BASE_TYPE; - } catch (Exception e) { - System.out.println("WARNING: Ignoring exception"); - //System.out.println("globals: " + pythonCompiledFunction.globalsMap); - //System.out.println("co_constants: " + pythonCompiledFunction.co_constants); - //System.out.println("co_names: " + pythonCompiledFunction.co_names); - //System.out.println("co_varnames: " + pythonCompiledFunction.co_varnames); - System.out.println("Instructions:"); - System.out.println(pythonCompiledFunction.instructionList.stream() - .map(PythonBytecodeInstruction::toString) - .collect(Collectors.joining("\n"))); - e.printStackTrace(); - return BuiltinTypes.BASE_TYPE; - } - } - - public record InterfaceDeclaration(String interfaceName, String methodDescriptor) { - public String descriptor() { - return "L" + interfaceName + ";"; - } - - public Type methodType() { - return Type.getMethodType(methodDescriptor); - } - } - - public static class FunctionSignature { - final String returnType; - final String[] parameterTypes; - - public FunctionSignature(String returnType, String... parameterTypes) { - this.returnType = returnType; - this.parameterTypes = parameterTypes; - } - - public String getClassName() { - return PythonBytecodeToJavaBytecodeTranslator.GENERATED_PACKAGE_BASE + "signature.InterfaceSignature"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FunctionSignature that = (FunctionSignature) o; - return returnType.equals(that.returnType) && Arrays.equals(parameterTypes, that.parameterTypes); - } - - @Override - public int hashCode() { - int result = Objects.hash(returnType); - result = 31 * result + Arrays.hashCode(parameterTypes); - return result; - } - } - - public enum PythonMethodKind { - VIRTUAL_METHOD, - STATIC_METHOD, - CLASS_METHOD; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledClass.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledClass.java deleted file mode 100644 index 10cb7213..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledClass.java +++ /dev/null @@ -1,88 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.wrappers.CPythonType; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; -import ai.timefold.jpyinterpreter.util.JavaIdentifierUtils; - -public class PythonCompiledClass { - /** - * The module where the class was defined. - */ - public String module; - - /** - * The path to the file that defines the module. - */ - public String moduleFilePath; - - /** - * The qualified name of the class. Does not include module. - */ - public String qualifiedName; - - public String className; - - /** - * The annotations on the type - */ - public List annotations; - - /** - * Type annotations for fields - */ - public Map typeAnnotations; - - /** - * Java interfaces the class implement - */ - public List> javaInterfaces; - - /** - * Mapping from Python types to Java types - */ - public List> pythonJavaTypeMappings; - - /** - * The binary type of this PythonCompiledClass; - * typically {@link CPythonType}. Used when methods - * cannot be generated. - */ - public PythonLikeType binaryType; - public List superclassList; - public Map instanceFunctionNameToPythonBytecode; - public Map staticFunctionNameToPythonBytecode; - public Map classFunctionNameToPythonBytecode; - - /** - * Contains static attributes that are not instances of this class - */ - public Map staticAttributeNameToObject; - - /** - * Contains static attributes that are instances of this class - */ - public Map staticAttributeNameToClassInstance; - - /** - * Contains static attributes that have get/set descriptors - */ - public Set staticAttributeDescriptorNames; - - public String getGeneratedClassBaseName() { - return getGeneratedClassBaseName(module, qualifiedName); - } - - public static String getGeneratedClassBaseName(String module, String qualifiedName) { - if (module == null || module.isEmpty()) { - return JavaIdentifierUtils.sanitizeClassName((qualifiedName != null) ? qualifiedName : "PythonClass"); - } - return JavaIdentifierUtils - .sanitizeClassName((qualifiedName != null) ? module + "." + qualifiedName : module + "." + "PythonClass"); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledFunction.java deleted file mode 100644 index f06a6bb3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledFunction.java +++ /dev/null @@ -1,304 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; - -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.util.JavaIdentifierUtils; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.Type; - -public class PythonCompiledFunction { - /** - * The module where the function was defined. - */ - public String module; - - /** - * The path to the file that defines the module. - */ - public String moduleFilePath; - - /** - * The qualified name of the function. Does not include module. - */ - public String qualifiedName; - - /** - * List of bytecode instructions in the function - */ - public List instructionList; - - /** - * The closure of the function - */ - public PythonLikeTuple closure; - - /** - * The globals of the function - */ - public Map globalsMap; - - /** - * Type annotations for the parameters and return. - * (return is stored under the "return" key). - */ - public Map typeAnnotations; - - /** - * Default positional arguments - */ - public PythonLikeTuple defaultPositionalArguments = new PythonLikeTuple(); - - /** - * Default keyword arguments - */ - public PythonLikeDict defaultKeywordArguments = new PythonLikeDict(); - - /** - * List of all names used in the function - */ - public List co_names; - - /** - * List of names used by local variables in the function - */ - public List co_varnames; - - /** - * List of names used by cell variables - */ - public List co_cellvars; - - /** - * List of names used by free variables - */ - public List co_freevars; - - /** - * List of constants used in bytecode - */ - public List co_constants; - - /** - * The exception table; only populated in Python 3.11 and above (in Python 3.10 and below, - * the table will be empty, since those use explict block instructions) - */ - public PythonExceptionTable co_exceptiontable; - - /** - * The number of not keyword only arguments the function takes - */ - public int co_argcount; - - /** - * The number of keyword only arguments the function takes - */ - public int co_kwonlyargcount; - - /** - * The number of positional only arguments the function takes - */ - public int co_posonlyargcount; - - /** - * True if the python function can take extra positional arguments that were not specified in its arguments - */ - public boolean supportExtraPositionalArgs = false; - - /** - * True if the python function can take extra keyword arguments that were not specified in its arguments - */ - public boolean supportExtraKeywordsArgs = false; - - /** - * The python version this function was compiled in (see sys.hexversion) - */ - public PythonVersion pythonVersion; - - public PythonClassTranslator.PythonMethodKind methodKind = PythonClassTranslator.PythonMethodKind.STATIC_METHOD; - - public PythonCompiledFunction() { - } - - public PythonCompiledFunction copy() { - PythonCompiledFunction out = new PythonCompiledFunction(); - - out.module = module; - out.moduleFilePath = moduleFilePath; - out.qualifiedName = qualifiedName; - out.instructionList = List.copyOf(instructionList); - out.closure = closure; - out.globalsMap = globalsMap; - out.typeAnnotations = typeAnnotations; - out.defaultPositionalArguments = defaultPositionalArguments; - out.defaultKeywordArguments = defaultKeywordArguments; - out.co_exceptiontable = this.co_exceptiontable; - out.co_names = List.copyOf(co_names); - out.co_varnames = List.copyOf(co_varnames); - out.co_cellvars = List.copyOf(co_cellvars); - out.co_freevars = List.copyOf(co_freevars); - out.co_constants = List.copyOf(co_constants); - out.co_argcount = co_argcount; - out.co_kwonlyargcount = co_kwonlyargcount; - out.pythonVersion = pythonVersion; - out.methodKind = methodKind; - - return out; - } - - public List getParameterTypes() { - List out = new ArrayList<>(totalArgCount()); - PythonLikeType defaultType = BuiltinTypes.BASE_TYPE; - - for (int i = 0; i < totalArgCount(); i++) { - String parameterName = co_varnames.get(i); - var parameterTypeHint = typeAnnotations.get(parameterName); - PythonLikeType parameterType = defaultType; - if (parameterTypeHint != null) { - parameterType = parameterTypeHint.type(); - } - out.add(parameterType); - } - return out; - } - - public Optional getReturnType() { - var returnTypeHint = typeAnnotations.get("return"); - if (returnTypeHint == null) { - return Optional.empty(); - } - return Optional.of(returnTypeHint.type()); - } - - public Optional getReturnTypeHint() { - return Optional.ofNullable(typeAnnotations.get("return")); - } - - public String getAsmMethodDescriptorString() { - Type returnType = Type.getType('L' + getReturnType().map(PythonLikeType::getJavaTypeInternalName) - .orElseGet(BuiltinTypes.BASE_TYPE::getJavaTypeInternalName) + ';'); - List parameterPythonTypeList = getParameterTypes(); - Type[] parameterTypes = new Type[totalArgCount()]; - - for (int i = 0; i < totalArgCount(); i++) { - parameterTypes[i] = Type.getType('L' + parameterPythonTypeList.get(i).getJavaTypeInternalName() + ';'); - } - return Type.getMethodDescriptor(returnType, parameterTypes); - } - - public String getGeneratedClassBaseName() { - if (module == null || module.isEmpty()) { - return JavaIdentifierUtils.sanitizeClassName((qualifiedName != null) ? qualifiedName : "PythonFunction"); - } - return JavaIdentifierUtils - .sanitizeClassName((qualifiedName != null) ? module + "." + qualifiedName : module + "." + "PythonFunction"); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Class getParameterJavaClass(List parameterTypeList, int variableIndex) { - return (Class) parameterTypeList.get(variableIndex).getJavaClassOrDefault(PythonLikeObject.class); - } - - private static String getParameterJavaClassName(List parameterTypeList, int variableIndex) { - return parameterTypeList.get(variableIndex).getJavaTypeInternalName(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public BiFunction> getArgumentSpecMapper() { - return (defaultPositionalArguments, defaultKeywordArguments) -> { - ArgumentSpec out = ArgumentSpec.forFunctionReturning(qualifiedName, getReturnType() - .map(PythonLikeType::getJavaTypeInternalName) - .orElse(PythonLikeObject.class.getName())); - - int variableIndex = 0; - int defaultPositionalStartIndex = co_argcount - defaultPositionalArguments.size(); - - if (methodKind == PythonClassTranslator.PythonMethodKind.VIRTUAL_METHOD) { - variableIndex = 1; - } - - List parameterTypeList = getParameterTypes(); - for (; variableIndex < co_posonlyargcount; variableIndex++) { - if (variableIndex >= defaultPositionalStartIndex) { - out = out.addPositionalOnlyArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex), - defaultPositionalArguments.get( - variableIndex - defaultPositionalStartIndex)); - } else { - out = out.addPositionalOnlyArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex)); - } - } - - for (; variableIndex < co_argcount; variableIndex++) { - if (variableIndex >= defaultPositionalStartIndex) { - out = out.addArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex), - defaultPositionalArguments.get(variableIndex - defaultPositionalStartIndex)); - } else { - out = out.addArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex)); - } - } - - for (int i = 0; i < co_kwonlyargcount; i++) { - PythonLikeObject maybeDefault = - defaultKeywordArguments.get(PythonString.valueOf(co_varnames.get(variableIndex))); - if (maybeDefault != null) { - out = out.addKeywordOnlyArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex), - maybeDefault); - } else { - out = out.addKeywordOnlyArgument(co_varnames.get(variableIndex), - getParameterJavaClassName(parameterTypeList, variableIndex)); - } - variableIndex++; - } - - // vargs and kwargs are always last, despite position in signature - if (supportExtraPositionalArgs) { - out = out.addExtraPositionalVarArgument(co_varnames.get(variableIndex)); - variableIndex++; - } - - if (supportExtraKeywordsArgs) { - out = out.addExtraKeywordVarArgument(co_varnames.get(variableIndex)); - } - - return out; - }; - } - - /** - * The total number of arguments the function takes - */ - public int totalArgCount() { - int extraArgs = 0; - if (supportExtraPositionalArgs) { - extraArgs++; - } - if (supportExtraKeywordsArgs) { - extraArgs++; - } - - return co_argcount + co_kwonlyargcount + extraArgs; - } - - public int getFirstLine() { - for (var instruction : instructionList) { - if (instruction.startsLine().isPresent()) { - return instruction.startsLine().getAsInt(); - } - } - return -1; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonDefaultArgumentImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonDefaultArgumentImplementor.java deleted file mode 100644 index 5f8416b1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonDefaultArgumentImplementor.java +++ /dev/null @@ -1,471 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Modifier; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implement classes that hold static constants used for default arguments when calling - */ -public class PythonDefaultArgumentImplementor { - public static final String ARGUMENT_PREFIX = "argument_"; - public static final String CONSTANT_PREFIX = "DEFAULT_VALUE_"; - - public static final String ARGUMENT_SPEC_STATIC_FIELD_NAME = "argumentSpec"; - - public static final String KEY_TUPLE_FIELD_NAME = "keyword_args"; - - public static final String REMAINING_KEY_ARGUMENTS_FIELD_NAME = "remaining_keys"; - - public static final String POSITIONAL_INDEX = "positional_index"; - - public static String getArgumentName(int argumentIndex) { - return ARGUMENT_PREFIX + argumentIndex; - } - - public static String getConstantName(int defaultIndex) { - return CONSTANT_PREFIX + defaultIndex; - } - - public static String createDefaultArgumentFor(MethodDescriptor methodDescriptor, - List defaultArgumentList, - Map argumentNameToIndexMap, Optional extraPositionalArgumentsVariableIndex, - Optional extraKeywordArgumentsVariableIndex, - ArgumentSpec argumentSpec) { - String maybeClassName = PythonBytecodeToJavaBytecodeTranslator.GENERATED_PACKAGE_BASE + - methodDescriptor.getDeclaringClassInternalName().replace('/', '.') + - "." - + methodDescriptor.getMethodName() + "$$Defaults"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(Object.class), new String[] { - Type.getInternalName(PythonLikeFunction.class) - }); - - // static constants - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, ARGUMENT_SPEC_STATIC_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class), null, null); - - final int defaultStart = methodDescriptor.getParameterTypes().length - defaultArgumentList.size(); - for (int i = 0; i < defaultArgumentList.size(); i++) { - String fieldName = getConstantName(i); - classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, fieldName, - methodDescriptor.getParameterTypes()[defaultStart + i].getDescriptor(), - null, - null); - } - - // instance fields (representing actual arguments) - classWriter.visitField(Modifier.PRIVATE | Modifier.FINAL, KEY_TUPLE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class), null, null); - classWriter.visitField(Modifier.PRIVATE, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class), null, null); - classWriter.visitField(Modifier.PRIVATE, POSITIONAL_INDEX, - Type.getDescriptor(int.class), null, null); - for (int i = 0; i < methodDescriptor.getParameterTypes().length; i++) { - String fieldName = getArgumentName(i); - - if (extraPositionalArgumentsVariableIndex.isPresent() && extraPositionalArgumentsVariableIndex.get() == i) { - classWriter.visitField(Modifier.PUBLIC, fieldName, - Type.getDescriptor(PythonLikeTuple.class), - null, - null); - } else if (extraKeywordArgumentsVariableIndex.isPresent() && extraKeywordArgumentsVariableIndex.get() == i) { - classWriter.visitField(Modifier.PUBLIC, fieldName, - Type.getDescriptor(PythonLikeDict.class), - null, - null); - } else { - classWriter.visitField(Modifier.PUBLIC, fieldName, - methodDescriptor.getParameterTypes()[i].getDescriptor(), - null, - null); - } - } - - // public constructor; an instance is created for keyword function calls, since we need consistent stack frames - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonLikeTuple.class), Type.INT_TYPE), - null, null); - methodVisitor.visitCode(); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, KEY_TUPLE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), "size", - Type.getMethodDescriptor(Type.INT_TYPE), true); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 2); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, POSITIONAL_INDEX, Type.getDescriptor(int.class)); - - for (int i = 0; i < defaultArgumentList.size(); i++) { - int argumentIndex = i + (methodDescriptor.getParameterTypes().length - defaultArgumentList.size()); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, - getConstantName(i), - methodDescriptor.getParameterTypes()[defaultStart + i].getDescriptor()); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - getArgumentName(argumentIndex), - methodDescriptor.getParameterTypes()[argumentIndex].getDescriptor()); - } - - if (extraPositionalArgumentsVariableIndex.isPresent()) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - getArgumentName(extraPositionalArgumentsVariableIndex.get()), - Type.getDescriptor(PythonLikeTuple.class)); - } - - if (extraKeywordArgumentsVariableIndex.isPresent()) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - getArgumentName(extraKeywordArgumentsVariableIndex.get()), - Type.getDescriptor(PythonLikeDict.class)); - } - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - createAddArgumentMethod(classWriter, internalClassName, methodDescriptor, argumentNameToIndexMap, - extraPositionalArgumentsVariableIndex, extraKeywordArgumentsVariableIndex, argumentSpec); - - // clinit to set ArgumentSpec, as class cannot be loaded if it contains - // yet to be compiled forward references - methodVisitor = classWriter.visitMethod(Modifier.PUBLIC | Modifier.STATIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), null, null); - methodVisitor.visitCode(); - - argumentSpec.loadArgumentSpec(methodVisitor); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, internalClassName, - ARGUMENT_SPEC_STATIC_FIELD_NAME, Type.getDescriptor(ArgumentSpec.class)); - - for (int i = 0; i < defaultArgumentList.size(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(defaultStart + i); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ArgumentSpec.class), - "getDefaultValue", Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[defaultStart + i].getInternalName()); - String fieldName = getConstantName(i); - methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, internalClassName, - fieldName, methodDescriptor.getParameterTypes()[defaultStart + i].getDescriptor()); - } - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - classWriter.visitEnd(); - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - return internalClassName; - } - - /** - * Create code that look like this: - * - *
-     * void addArgument(PythonLikeObject argument) {
-     *     if (remainingKeywords > 0) {
-     *         String keyword = keywordTuple.get(remainingKeywords - 1).getValue();
-     *         switch (keyword) {
-     *             case "key1":
-     *                 argument_0 = (Argument0Type) argument;
-     *                 break;
-     *             case "key2":
-     *                 argument_1 = (Argument1Type) argument;
-     *                 break;
-     *             ...
-     *             default:
-     *                 #ifdef EXTRA_KEYWORD_VAR
-     *                 EXTRA_KEYWORD_VAR.put(keyword, argument);
-     *                 #endif
-     *
-     *                 #ifndef EXTRA_KEYWORD_VAR
-     *                 throw new TypeError();
-     *                 #endif
-     *         }
-     *         remainingKeywords--;
-     *         return;
-     *     } else {
-     *         switch (positionalIndex) {
-     *             case 0:
-     *                 argument_0 = (Argument0Type) argument;
-     *                 break;
-     *             case 1:
-     *                 argument_1 = (Argument1Type) argument;
-     *                 break;
-     *             ...
-     *             default:
-     *                 #ifdef EXTRA_POSITIONAL_VAR
-     *                 EXTRA_POSITIONAL_VAR.add(0, argument);
-     *                 #endif
-     *
-     *                 #ifndef EXTRA_POSITIONAL_VAR
-     *                 throw new TypeError();
-     *                 #endif
-     *         }
-     *         positionalIndex--;
-     *         return;
-     *     }
-     * }
-     * 
- * - * @param classVisitor - * @param classInternalName - * @param methodDescriptor - * @param argumentNameToIndexMap - */ - private static void createAddArgumentMethod(ClassVisitor classVisitor, String classInternalName, - MethodDescriptor methodDescriptor, - Map argumentNameToIndexMap, Optional extraPositionalArgumentsVariableIndex, - Optional extraKeywordArgumentsVariableIndex, - ArgumentSpec argumentSpec) { - MethodVisitor methodVisitor = classVisitor.visitMethod(Modifier.PUBLIC, "addArgument", - Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(PythonLikeObject.class)), - null, null); - - methodVisitor.visitParameter("argument", 0); - - methodVisitor.visitCode(); - - Label noMoreKeywordArguments = new Label(); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, noMoreKeywordArguments); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, KEY_TUPLE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonString.class), "getValue", - Type.getMethodDescriptor(Type.getType(String.class)), - false); - - BytecodeSwitchImplementor.createStringSwitch(methodVisitor, argumentNameToIndexMap.keySet(), - 2, key -> { - int index = argumentNameToIndexMap.get(key); - Type parameterType = methodDescriptor.getParameterTypes()[index]; - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, parameterType.getInternalName()); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, getArgumentName(index), - parameterType.getDescriptor()); - }, - () -> { - if (extraKeywordArgumentsVariableIndex.isPresent()) { - // Extra keys dict - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, - getArgumentName(extraKeywordArgumentsVariableIndex.get()), - Type.getDescriptor(PythonLikeDict.class)); - - // Key - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, KEY_TUPLE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); - - // Value - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeDict.class), - "put", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitInsn(Opcodes.POP); - } else { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(TypeError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, KEY_TUPLE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonString.class), - "getValue", - Type.getMethodDescriptor(Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(PythonDefaultArgumentImplementor.class), - "getUnknownKeyArgument", Type.getMethodDescriptor(Type.getType(String.class), - Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(TypeError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - }, - false); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, REMAINING_KEY_ARGUMENTS_FIELD_NAME, - Type.getDescriptor(int.class)); - methodVisitor.visitInsn(Opcodes.RETURN); - - // No more keyword arguments - methodVisitor.visitLabel(noMoreKeywordArguments); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, POSITIONAL_INDEX, Type.getDescriptor(int.class)); - - BytecodeSwitchImplementor.createIntSwitch(methodVisitor, - IntStream.range(0, argumentSpec.getAllowPositionalArgumentCount()) - .boxed().collect(Collectors.toList()), - index -> { - Type parameterType = methodDescriptor.getParameterTypes()[index]; - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, parameterType.getInternalName()); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, getArgumentName(index), - parameterType.getDescriptor()); - }, - () -> { - if (extraPositionalArgumentsVariableIndex.isPresent()) { - // Extra argument tuple - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, - getArgumentName(extraPositionalArgumentsVariableIndex.get()), - Type.getDescriptor(PythonLikeTuple.class)); - - // Index (need to insert in front of list since positional arguments are read in reverse) - methodVisitor.visitInsn(Opcodes.ICONST_0); - - // Item - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - - // Insert at front of list (since positional arguments are read in reverse) - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeTuple.class), "add", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(PythonLikeObject.class)), - false); - - } else { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(TypeError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, POSITIONAL_INDEX, - Type.getDescriptor(int.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(PythonDefaultArgumentImplementor.class), - "getTooManyPositionalArguments", Type.getMethodDescriptor(Type.getType(String.class), - Type.INT_TYPE), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(TypeError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - }, - false); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classInternalName, POSITIONAL_INDEX, Type.getDescriptor(int.class)); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classInternalName, POSITIONAL_INDEX, Type.getDescriptor(int.class)); - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - } - - public static String getUnknownKeyArgument(String keyArgument) { - return "got an unexpected keyword argument '" + keyArgument + "'"; - } - - public static String getTooManyPositionalArguments(int numOfArguments) { - return "Got too many positional arguments (" + numOfArguments + ")"; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonExceptionTable.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonExceptionTable.java deleted file mode 100644 index fcc2d04a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonExceptionTable.java +++ /dev/null @@ -1,79 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.util.JumpUtils; - -public class PythonExceptionTable { - private final List blockList; - - public PythonExceptionTable() { - this.blockList = new ArrayList<>(); - } - - public void addEntry(PythonVersion pythonVersion, int blockStartInstructionInclusive, int blockEndInstructionInclusive, - int targetByteOffset, int stackDepth, boolean pushLastIndex) { - blockList.add( - new ExceptionBlock(JumpUtils.getInstructionIndexForByteOffset(blockStartInstructionInclusive, pythonVersion), - JumpUtils.getInstructionIndexForByteOffset(blockEndInstructionInclusive, pythonVersion) + 1, - JumpUtils.getInstructionIndexForByteOffset(targetByteOffset, pythonVersion), - stackDepth, pushLastIndex)); - } - - public List getEntries() { - return blockList; - } - - public boolean containsJumpTarget(int target) { - return blockList.stream().anyMatch(block -> block.targetInstruction == target); - } - - public Set getJumpTargetSet() { - return blockList.stream().map(ExceptionBlock::getTargetInstruction).collect(Collectors.toSet()); - } - - public Set getJumpTargetForStartSet(int start) { - return blockList.stream() - .filter(exceptionBlock -> exceptionBlock.getBlockStartInstructionInclusive() == start) - .map(ExceptionBlock::getTargetInstruction) - .collect(Collectors.toSet()); - } - - public List getInnerExceptionBlockList(int start, Set possibleJumpTargetSet) { - return blockList.stream() - .filter(exceptionBlock -> exceptionBlock.getBlockStartInstructionInclusive() == start || - exceptionBlock.containsAnyTargetInSet(possibleJumpTargetSet)) - .collect(Collectors.toList()); - } - - public Set getStartPositionSet() { - return blockList.stream().map(ExceptionBlock::getBlockStartInstructionInclusive).collect(Collectors.toSet()); - } - - @Override - public String toString() { - return blockList.stream().map(ExceptionBlock::toString) - .collect(Collectors.joining("\n ", "ExceptionTable:\n ", "")); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonExceptionTable that = (PythonExceptionTable) o; - return Objects.equals(blockList, that.blockList); - } - - @Override - public int hashCode() { - return Objects.hash(blockList); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java deleted file mode 100644 index 8fab72e0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java +++ /dev/null @@ -1,368 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.Type; - -public class PythonFunctionSignature { - private final PythonLikeType returnType; - private final PythonLikeType[] parameterTypes; - - private final MethodDescriptor methodDescriptor; - - private final List defaultArgumentList; - private final Map keywordToArgumentIndexMap; - - private final Optional extraPositionalArgumentsVariableIndex; - private final Optional extraKeywordArgumentsVariableIndex; - - private final String defaultArgumentHolderClassInternalName; - private final ArgumentSpec argumentSpec; - private final boolean isFromArgumentSpec; - - private static Map extractKeywordArgument(MethodDescriptor methodDescriptor) { - Map out = new HashMap<>(); - - int index = 0; - for (Type parameterType : methodDescriptor.getParameterTypes()) { - out.put("arg" + index, index); - } - return out; - } - - public static PythonFunctionSignature forMethod(Method method) { - MethodDescriptor methodDescriptor = new MethodDescriptor(method); - PythonLikeType returnType = JavaPythonTypeConversionImplementor.getPythonLikeType(method.getReturnType()); - PythonLikeType[] parameterTypes = new PythonLikeType[method.getParameterCount()]; - for (int i = 0; i < parameterTypes.length; i++) { - parameterTypes[i] = JavaPythonTypeConversionImplementor.getPythonLikeType(method.getParameterTypes()[i]); - } - return new PythonFunctionSignature(methodDescriptor, returnType, parameterTypes); - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - PythonLikeType returnType, PythonLikeType... parameterTypes) { - this(methodDescriptor, Collections.emptyList(), extractKeywordArgument(methodDescriptor), returnType, parameterTypes); - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - PythonLikeType returnType, List parameterTypeList) { - this(methodDescriptor, Collections.emptyList(), extractKeywordArgument(methodDescriptor), returnType, - parameterTypeList); - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - List defaultArgumentList, - Map keywordToArgumentIndexMap, - PythonLikeType returnType, PythonLikeType... parameterTypes) { - this.returnType = returnType; - this.parameterTypes = parameterTypes; - this.methodDescriptor = methodDescriptor; - this.defaultArgumentList = defaultArgumentList; - this.keywordToArgumentIndexMap = keywordToArgumentIndexMap; - this.extraPositionalArgumentsVariableIndex = Optional.empty(); - this.extraKeywordArgumentsVariableIndex = Optional.empty(); - isFromArgumentSpec = false; - argumentSpec = computeArgumentSpec(); - defaultArgumentHolderClassInternalName = PythonDefaultArgumentImplementor.createDefaultArgumentFor(methodDescriptor, - defaultArgumentList, keywordToArgumentIndexMap, getExtraPositionalArgumentsVariableIndex(), - getExtraKeywordArgumentsVariableIndex(), getArgumentSpec()); - - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - List defaultArgumentList, - Map keywordToArgumentIndexMap, - PythonLikeType returnType, List parameterTypesList) { - this.returnType = returnType; - this.parameterTypes = parameterTypesList.toArray(new PythonLikeType[0]); - this.methodDescriptor = methodDescriptor; - this.defaultArgumentList = defaultArgumentList; - this.keywordToArgumentIndexMap = keywordToArgumentIndexMap; - this.extraPositionalArgumentsVariableIndex = Optional.empty(); - this.extraKeywordArgumentsVariableIndex = Optional.empty(); - isFromArgumentSpec = false; - argumentSpec = computeArgumentSpec(); - defaultArgumentHolderClassInternalName = PythonDefaultArgumentImplementor.createDefaultArgumentFor(methodDescriptor, - defaultArgumentList, keywordToArgumentIndexMap, getExtraPositionalArgumentsVariableIndex(), - getExtraKeywordArgumentsVariableIndex(), getArgumentSpec()); - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - List defaultArgumentList, - Map keywordToArgumentIndexMap, - PythonLikeType returnType, List parameterTypesList, - Optional extraPositionalArgumentsVariableIndex, - Optional extraKeywordArgumentsVariableIndex) { - this.returnType = returnType; - this.parameterTypes = parameterTypesList.toArray(new PythonLikeType[0]); - this.methodDescriptor = methodDescriptor; - this.defaultArgumentList = defaultArgumentList; - this.keywordToArgumentIndexMap = keywordToArgumentIndexMap; - this.extraPositionalArgumentsVariableIndex = extraPositionalArgumentsVariableIndex; - this.extraKeywordArgumentsVariableIndex = extraKeywordArgumentsVariableIndex; - isFromArgumentSpec = false; - argumentSpec = computeArgumentSpec(); - defaultArgumentHolderClassInternalName = PythonDefaultArgumentImplementor.createDefaultArgumentFor(methodDescriptor, - defaultArgumentList, keywordToArgumentIndexMap, extraPositionalArgumentsVariableIndex, - extraKeywordArgumentsVariableIndex, getArgumentSpec()); - } - - public PythonFunctionSignature(MethodDescriptor methodDescriptor, - List defaultArgumentList, - Map keywordToArgumentIndexMap, - PythonLikeType returnType, List parameterTypesList, - Optional extraPositionalArgumentsVariableIndex, - Optional extraKeywordArgumentsVariableIndex, - ArgumentSpec argumentSpec) { - this.returnType = returnType; - this.parameterTypes = parameterTypesList.toArray(new PythonLikeType[0]); - this.methodDescriptor = methodDescriptor; - this.defaultArgumentList = defaultArgumentList; - this.keywordToArgumentIndexMap = keywordToArgumentIndexMap; - this.extraPositionalArgumentsVariableIndex = extraPositionalArgumentsVariableIndex; - this.extraKeywordArgumentsVariableIndex = extraKeywordArgumentsVariableIndex; - this.argumentSpec = argumentSpec; - isFromArgumentSpec = true; - defaultArgumentHolderClassInternalName = PythonDefaultArgumentImplementor.createDefaultArgumentFor(methodDescriptor, - defaultArgumentList, keywordToArgumentIndexMap, extraPositionalArgumentsVariableIndex, - extraKeywordArgumentsVariableIndex, argumentSpec); - } - - private ArgumentSpec computeArgumentSpec() { - ArgumentSpec argumentSpec = ArgumentSpec.forFunctionReturning(getMethodDescriptor().getMethodName(), - getReturnType().getJavaTypeInternalName()); - for (int i = 0; i < getParameterTypes().length - getDefaultArgumentList().size(); i++) { - if (getExtraPositionalArgumentsVariableIndex().isPresent() - && getExtraPositionalArgumentsVariableIndex().get() == i) { - continue; - } - - if (getExtraKeywordArgumentsVariableIndex().isPresent() && getExtraKeywordArgumentsVariableIndex().get() == i) { - continue; - } - - final int argIndex = i; - Optional argumentName = getKeywordToArgumentIndexMap().entrySet() - .stream().filter(e -> e.getValue().equals(argIndex)) - .map(Map.Entry::getKey) - .findAny(); - - if (argumentName.isEmpty()) { - argumentSpec = argumentSpec.addArgument("$arg" + i, - getParameterTypes()[i].getJavaTypeInternalName()); - } else { - argumentSpec = argumentSpec.addArgument(argumentName.get(), - getParameterTypes()[i].getJavaTypeInternalName()); - } - } - - for (int i = getParameterTypes().length - getDefaultArgumentList().size(); i < getParameterTypes().length; i++) { - if (getExtraPositionalArgumentsVariableIndex().isPresent() - && getExtraPositionalArgumentsVariableIndex().get() == i) { - continue; - } - - if (getExtraKeywordArgumentsVariableIndex().isPresent() && getExtraKeywordArgumentsVariableIndex().get() == i) { - continue; - } - - PythonLikeObject defaultValue = - getDefaultArgumentList().get(getDefaultArgumentList().size() - (getParameterTypes().length - i)); - - final int argIndex = i; - Optional argumentName = getKeywordToArgumentIndexMap().entrySet() - .stream().filter(e -> e.getValue().equals(argIndex)) - .map(Map.Entry::getKey) - .findAny(); - - if (argumentName.isEmpty()) { - argumentSpec = argumentSpec.addArgument("$arg" + i, - getParameterTypes()[i].getJavaTypeInternalName(), - defaultValue); - } else { - argumentSpec = argumentSpec.addArgument(argumentName.get(), - getParameterTypes()[i].getJavaTypeInternalName(), - defaultValue); - } - } - - if (getExtraPositionalArgumentsVariableIndex().isPresent()) { - argumentSpec = argumentSpec.addExtraPositionalVarArgument("*vargs"); - } - - if (getExtraKeywordArgumentsVariableIndex().isPresent()) { - argumentSpec = argumentSpec.addExtraKeywordVarArgument("**kwargs"); - } - return argumentSpec; - } - - public ArgumentSpec getArgumentSpec() { - return argumentSpec; - } - - public PythonLikeType getReturnType() { - return returnType; - } - - public PythonLikeType[] getParameterTypes() { - return parameterTypes; - } - - public MethodDescriptor getMethodDescriptor() { - return methodDescriptor; - } - - public boolean isFromArgumentSpec() { - return isFromArgumentSpec; - } - - public List getDefaultArgumentList() { - return defaultArgumentList; - } - - public Map getKeywordToArgumentIndexMap() { - return keywordToArgumentIndexMap; - } - - public Optional getExtraPositionalArgumentsVariableIndex() { - return extraPositionalArgumentsVariableIndex; - } - - public Optional getExtraKeywordArgumentsVariableIndex() { - return extraKeywordArgumentsVariableIndex; - } - - public String getDefaultArgumentHolderClassInternalName() { - return defaultArgumentHolderClassInternalName; - } - - public boolean isVirtualMethod() { - switch (getMethodDescriptor().getMethodType()) { - case VIRTUAL: - case INTERFACE: - case CONSTRUCTOR: - return true; - default: - return false; - - } - } - - public boolean isClassMethod() { - return getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.CLASS; - } - - public boolean isStaticMethod() { - return getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.STATIC; - } - - private int getPositionalParameterCount(int originalPositionalParameterCount) { - if (getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.CLASS) { - return originalPositionalParameterCount + 1; - } else { - return originalPositionalParameterCount; - } - } - - private List getCallParameterList(List callStackTypeList) { - if (getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.CLASS) { - List actualCallParameters = new ArrayList<>(); - actualCallParameters.add(BuiltinTypes.TYPE_TYPE); - actualCallParameters.addAll(callStackTypeList); - return actualCallParameters; - } else { - return callStackTypeList; - } - } - - public boolean matchesParameters(PythonLikeType... callParameters) { - return getArgumentSpec().verifyMatchesCallSignature(getPositionalParameterCount(callParameters.length), - Collections.emptyList(), - getCallParameterList(List.of(callParameters))); - } - - public boolean matchesParameters(int positionalArgumentCount, List keywordArgumentNameList, - List callStackTypeList) { - - return getArgumentSpec().verifyMatchesCallSignature(getPositionalParameterCount(positionalArgumentCount), - keywordArgumentNameList, - getCallParameterList(callStackTypeList)); - } - - public boolean moreSpecificThan(PythonFunctionSignature other) { - if (other.getParameterTypes().length < getParameterTypes().length && - (other.getExtraPositionalArgumentsVariableIndex().isPresent() || - other.getExtraKeywordArgumentsVariableIndex().isPresent())) { - return true; - } - - if (other.getParameterTypes().length > getParameterTypes().length && - (getExtraPositionalArgumentsVariableIndex().isPresent() || - getExtraKeywordArgumentsVariableIndex().isPresent())) { - return false; - } - - if (other.getParameterTypes().length != getParameterTypes().length) { - return false; - } - - for (int i = 0; i < getParameterTypes().length; i++) { - PythonLikeType overloadParameterType = getParameterTypes()[i]; - PythonLikeType otherParameterType = other.getParameterTypes()[i]; - - if (otherParameterType.equals(overloadParameterType)) { - continue; - } - - if (otherParameterType.isSubclassOf(overloadParameterType)) { - return false; - } - } - return true; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonFunctionSignature that = (PythonFunctionSignature) o; - return getReturnType().equals(that.getReturnType()) && Arrays.equals(getParameterTypes(), that.getParameterTypes()) && - getExtraPositionalArgumentsVariableIndex().equals(that.getExtraPositionalArgumentsVariableIndex()) && - getExtraKeywordArgumentsVariableIndex().equals(that.getExtraKeywordArgumentsVariableIndex()); - } - - @Override - public int hashCode() { - int result = Objects.hash(getReturnType(), getExtraPositionalArgumentsVariableIndex(), - getExtraKeywordArgumentsVariableIndex()); - result = 31 * result + Arrays.hashCode(getParameterTypes()); - return result; - } - - @Override - public String toString() { - return getMethodDescriptor().getMethodName() + - Arrays.stream(getParameterTypes()).map(PythonLikeType::toString).collect(Collectors.joining(", ", "(", ") -> ")) - + - getReturnType(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionType.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionType.java deleted file mode 100644 index f86ff611..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionType.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.jpyinterpreter; - -public enum PythonFunctionType { - /** - * A normal function that corresponds to a typical Java Function. - */ - FUNCTION, - - /** - * A generator function that corresponds to a Java Iterable (has yield opcodes) - */ - GENERATOR -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslator.java deleted file mode 100644 index 1f66efd6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslator.java +++ /dev/null @@ -1,847 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import ai.timefold.jpyinterpreter.dag.FlowGraph; -import ai.timefold.jpyinterpreter.implementors.DunderOperatorImplementor; -import ai.timefold.jpyinterpreter.implementors.FunctionImplementor; -import ai.timefold.jpyinterpreter.implementors.GeneratorImplementor; -import ai.timefold.jpyinterpreter.implementors.PythonConstantsImplementor; -import ai.timefold.jpyinterpreter.implementors.VariableImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.descriptor.GeneratorOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.generator.GeneratorStartOpcode; -import ai.timefold.jpyinterpreter.opcodes.generator.ResumeOpcode; -import ai.timefold.jpyinterpreter.opcodes.generator.YieldFromOpcode; -import ai.timefold.jpyinterpreter.opcodes.generator.YieldValueOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonCell; -import ai.timefold.jpyinterpreter.types.PythonGenerator; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; -import ai.timefold.jpyinterpreter.util.JavaPythonClassWriter; -import ai.timefold.jpyinterpreter.util.MethodVisitorAdapters; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class PythonGeneratorTranslator { - // Needed since value from return is used for StopIteration, meaning to check if a generator has more values - // we need to progress it to the next yield/return to determine if it has a next value - private static final String SHOULD_PROGRESS_GENERATOR = "$shouldProgressGenerator"; - - // Remembers where the generator was last yielded at - // -1 if the generator hits a return. 0 if generator.__next__() has not been called yet - public static final String GENERATOR_STATE = "$generatorState"; - - // Stack of the generator after it yield a value - public static final String GENERATOR_STACK = "$generatorStack"; - - // The last value yielded by the generator - public static final String YIELDED_VALUE = "$yieldedValue"; - - // The iterator to yield values from, as well as to delegate "send" and "next" calls to - public static final String YIELD_FROM_ITERATOR = "$yieldFromIterator"; - - // The last exception catch by the generator - public static final String CURRENT_EXCEPTION = "$currentException"; - - // The stored stack for an exception handler prefix - private static final String EXCEPTION_STACK_PREFIX = "$exceptionHandlerStack"; - - // Called to advance the generator - private static final String PROGRESS_GENERATOR = "progressGenerator"; - - public static String exceptionHandlerTargetStackLocal(int target) { - return EXCEPTION_STACK_PREFIX + target; - } - - public static Class translateGeneratorFunction(PythonCompiledFunction pythonCompiledFunction) { - String maybeClassName = PythonBytecodeToJavaBytecodeTranslator.USER_PACKAGE_BASE - + pythonCompiledFunction.getGeneratedClassBaseName() + "$Generator"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - ClassWriter classWriter = new JavaPythonClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(PythonGenerator.class), null); - - // Create fields for generator state - classWriter.visitField(Modifier.PRIVATE, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor(), - null, null); - classWriter.visitField(Modifier.PRIVATE, GENERATOR_STATE, - Type.INT_TYPE.getDescriptor(), - null, null); - classWriter.visitField(Modifier.PRIVATE, GENERATOR_STACK, - Type.getDescriptor(List.class), - null, null); - classWriter.visitField(Modifier.PRIVATE, YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class), - null, null); - classWriter.visitField(Modifier.PRIVATE, YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class), - null, null); - classWriter.visitField(Modifier.PRIVATE, CURRENT_EXCEPTION, - Type.getDescriptor(Throwable.class), - null, null); - - // Create fields for translated functions - PythonBytecodeToJavaBytecodeTranslator.createFields(classWriter); - - // Create fields for parameters, cells and local variables - { - // Cannot use parameter types as the type descriptor, since the variables assigned to the - // Python parameter can change types in the middle of code - for (int variable = 0; variable < pythonCompiledFunction.co_varnames.size(); variable++) { - classWriter.visitField(Modifier.PRIVATE, pythonCompiledFunction.co_varnames.get(variable), - Type.getDescriptor(PythonLikeObject.class), - null, null); - } - for (int i = 0; i < pythonCompiledFunction.co_cellvars.size(); i++) { - classWriter.visitField(Modifier.PRIVATE, pythonCompiledFunction.co_cellvars.get(i), - Type.getDescriptor(PythonCell.class), - null, null); - } - for (int i = 0; i < pythonCompiledFunction.co_freevars.size(); i++) { - classWriter.visitField(Modifier.PRIVATE, pythonCompiledFunction.co_freevars.get(i), - Type.getDescriptor(PythonCell.class), - null, null); - } - for (int target : pythonCompiledFunction.co_exceptiontable.getJumpTargetSet()) { - classWriter.visitField(Modifier.PRIVATE, exceptionHandlerTargetStackLocal(target), - Type.getDescriptor(PythonLikeObject[].class), - null, null); - } - } - - Type[] javaParameterTypes = Stream.concat(Stream.of(Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonInterpreter.class)), - pythonCompiledFunction.getParameterTypes().stream() - .map(type -> Type.getType(type.getJavaTypeDescriptor()))) - .toArray(Type[]::new); - // Create constructor that sets parameters and initial generator state - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE, javaParameterTypes), - null, null); - methodVisitor.visitCode(); - - // Call super - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonGenerator.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), - false); - - // Positional only and Positional/Keyword default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.DEFAULT_POSITIONAL_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Keyword only default arguments - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.DEFAULT_KEYWORD_ARGS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Annotation Directory as key/value tuple - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.ANNOTATION_DIRECTORY_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeDict.class)); - - // Free variable cells - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - - // Function name - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 5); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.QUALIFIED_NAME_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonString.class)); - - // Interpreter - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - // Set initial generator state - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(true); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, GENERATOR_STATE, - Type.INT_TYPE.getDescriptor()); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, GENERATOR_STACK, - Type.getDescriptor(List.class)); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, CURRENT_EXCEPTION, - Type.getDescriptor(Throwable.class)); - - // Set parameters - { - for (int parameter = 0; parameter < pythonCompiledFunction.getParameterTypes().size(); parameter++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, parameter + 7); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - pythonCompiledFunction.co_varnames.get(parameter), - Type.getDescriptor(PythonLikeObject.class)); - } - } - - GeneratorLocalVariableHelper localVariableHelper = - new GeneratorLocalVariableHelper(classWriter, internalClassName, new Type[] {}, pythonCompiledFunction); - - localVariableHelper.resetCallKeywords(methodVisitor); - // Load cells - for (int i = 0; i < localVariableHelper.getNumberOfBoundCells(); i++) { - VariableImplementor.createCell(methodVisitor, localVariableHelper, i); - } - - for (int i = 0; i < localVariableHelper.getNumberOfFreeCells(); i++) { - VariableImplementor.setupFreeVariableCell(methodVisitor, internalClassName, localVariableHelper, i); - } - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - generateHasNext(classWriter, internalClassName, pythonCompiledFunction); - generateNext(classWriter, internalClassName, pythonCompiledFunction); - - Map generatorStateToMethodPart = - createGeneratorStateToMethod(classWriter, internalClassName, pythonCompiledFunction); - generateProgressGenerator(classWriter, internalClassName, generatorStateToMethodPart); - generateAdvanceGeneratorMethods(classWriter, internalClassName, generatorStateToMethodPart); - - classWriter.visitEnd(); - - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - try { - Class out = BuiltinTypes.asmClassLoader.loadClass(className); - PythonBytecodeToJavaBytecodeTranslator.setStaticFields(out, pythonCompiledFunction); - return out; - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Cannot load class " + className + " despite it being just generated.", e); - } - } - - private static void generateHasNext(ClassWriter classWriter, String internalClassName, - PythonCompiledFunction pythonCompiledFunction) { - MethodVisitor methodVisitor = MethodVisitorAdapters - .adapt(classWriter.visitMethod(Modifier.PUBLIC, "hasNext", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), - null, null), "hasNext", Type.getMethodDescriptor(Type.BOOLEAN_TYPE)); - methodVisitor.visitCode(); - - Label checkIfGeneratorEnded = new Label(); - - // Check if we need to progress generator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, checkIfGeneratorEnded); - - // We need to progress generator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, PROGRESS_GENERATOR, - Type.getMethodDescriptor(Type.VOID_TYPE), false); - // generator been progressed, so future hasNext calls (until next is called) should not progress generator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - - methodVisitor.visitLabel(checkIfGeneratorEnded); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, GENERATOR_STATE, - Type.INT_TYPE.getDescriptor()); - methodVisitor.visitLdcInsn(-1); - - Label generatorEnded = new Label(); - - methodVisitor.visitJumpInsn(Opcodes.IF_ICMPEQ, generatorEnded); - // generator state is not -1, so there are more values - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitLabel(generatorEnded); - // generator state is -1, so there are no more values - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void generateNext(ClassWriter classWriter, String internalClassName, - PythonCompiledFunction pythonCompiledFunction) { - MethodVisitor methodVisitor = MethodVisitorAdapters - .adapt(classWriter.visitMethod(Modifier.PUBLIC, "next", Type.getMethodDescriptor(Type.getType(Object.class)), - null, null), "next", Type.getMethodDescriptor(Type.getType(Object.class))); - methodVisitor.visitCode(); - - Label returnYieldedValue = new Label(); - - // Check if we need to progress generator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, returnYieldedValue); - - // We need to progress generator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, PROGRESS_GENERATOR, - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - methodVisitor.visitLabel(returnYieldedValue); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, GENERATOR_STATE, - Type.INT_TYPE.getDescriptor()); - methodVisitor.visitLdcInsn(-1); - - Label generatorEnded = new Label(); - - methodVisitor.visitJumpInsn(Opcodes.IF_ICMPEQ, generatorEnded); - // generator state is not -1, so there are more values - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - - // next call should progress generator - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - - // get the yielded value and return it - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitLabel(generatorEnded); - - // generator state is -1, so there are no more values - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - - // next call should not progress generator, since it ended - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, SHOULD_PROGRESS_GENERATOR, - Type.BOOLEAN_TYPE.getDescriptor()); - - // We need to throw StopIteration with the return value, which is stored in YIELDED_VALUE - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StopIteration.class)); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StopIteration.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void generateProgressGenerator(ClassWriter classWriter, String internalClassName, - Map generatorStateToMethodPartMap) { - MethodVisitor methodVisitor = MethodVisitorAdapters - .adapt(classWriter.visitMethod(Modifier.PRIVATE, PROGRESS_GENERATOR, Type.getMethodDescriptor(Type.VOID_TYPE), - null, null), PROGRESS_GENERATOR, Type.getMethodDescriptor(Type.VOID_TYPE)); - - methodVisitor.visitCode(); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, GENERATOR_STATE, Type.INT_TYPE.getDescriptor()); - - BytecodeSwitchImplementor.createIntSwitch(methodVisitor, generatorStateToMethodPartMap.keySet(), generatorState -> { - GeneratorMethodPart generatorMethodPart = generatorStateToMethodPartMap.get(generatorState); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - generatorMethodPart.functionMetadata.method.callMethod(methodVisitor); - }, () -> { - }, false); - - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void generateAdvanceGeneratorMethods(ClassWriter classWriter, String internalClassName, - Map generatorStateToMethodPartMap) { - generatorStateToMethodPartMap.values().forEach( - generatorMethodPart -> generateAdvanceGeneratorMethod(classWriter, internalClassName, generatorMethodPart)); - } - - private static void generateAdvanceGeneratorMethod(ClassWriter classWriter, String internalClassName, - GeneratorMethodPart generatorMethodPart) { - var instruction = AbstractOpcode.lookupInstruction(generatorMethodPart.instruction.opname()); - if (!(instruction instanceof GeneratorOpDescriptor generatorInstruction)) { - throw new IllegalArgumentException( - "Invalid opcode for instruction: %s".formatted(generatorMethodPart.instruction.opname())); - } - switch (generatorInstruction) { - case YIELD_VALUE -> - generateAdvanceGeneratorMethodForYieldValue(classWriter, internalClassName, generatorMethodPart); - case YIELD_FROM -> generateAdvanceGeneratorMethodForYieldFrom(classWriter, internalClassName, generatorMethodPart); - default -> throw new IllegalArgumentException( - "Invalid opcode for instruction: " + generatorMethodPart.instruction.opname()); - } - } - - private static void generateAdvanceGeneratorMethodForYieldValue(ClassWriter classWriter, String internalClassName, - GeneratorMethodPart generatorMethodPart) { - MethodVisitor methodVisitor = generatorMethodPart.functionMetadata.methodVisitor; - List opcodeList = PythonBytecodeToJavaBytecodeTranslator - .getOpcodeList(generatorMethodPart.functionMetadata.pythonCompiledFunction); - - // First, restore stack - methodVisitor.visitCode(); - - GeneratorImplementor.restoreGeneratorState(generatorMethodPart.functionMetadata, - generatorMethodPart.initialStackMetadata); - - if (generatorMethodPart.afterYield != 0 - || (opcodeList.size() > 0 && opcodeList.get(0) instanceof GeneratorStartOpcode)) { - // Push the sent value to the stack - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonGenerator.class), "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - - // Set the sent value to None - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonGenerator.class), "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - } - - // Now generate bytecode for method - StackMetadata initialStackMetadata = - PythonBytecodeToJavaBytecodeTranslator.getInitialStackMetadata( - generatorMethodPart.initialStackMetadata.localVariableHelper, - generatorMethodPart.originalMethodDescriptor, false); - FlowGraph flowGraph = FlowGraph.createFlowGraph(generatorMethodPart.functionMetadata, initialStackMetadata, opcodeList); - List stackMetadataForOpcodeIndex = flowGraph.getStackMetadataForOperations(); - - initialStackMetadata.localVariableHelper.resetCallKeywords(methodVisitor); - if (generatorMethodPart.afterYield != 0) { - Label afterYieldLabel = new Label(); - generatorMethodPart.functionMetadata.bytecodeCounterToLabelMap.put(generatorMethodPart.afterYield, afterYieldLabel); - methodVisitor.visitJumpInsn(Opcodes.GOTO, afterYieldLabel); - } - - ResumeOpcode.ResumeType resumeType = ResumeOpcode.ResumeType.YIELD; - if (opcodeList.get(generatorMethodPart.afterYield) instanceof ResumeOpcode) { - resumeType = ((ResumeOpcode) opcodeList.get(generatorMethodPart.afterYield)).getResumeType(); - } - - final ResumeOpcode.ResumeType actualResumeType = resumeType; - PythonBytecodeToJavaBytecodeTranslator.writeInstructionsForOpcodes(generatorMethodPart.functionMetadata, - stackMetadataForOpcodeIndex, opcodeList, - instruction -> { - if (actualResumeType == ResumeOpcode.ResumeType.YIELD) { - if (instruction.offset() == generatorMethodPart.afterYield) { - // Put thrownValue on TOS - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - - // Set thrownValue to null - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - - // Duplicate top - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - Label doNotThrowException = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, doNotThrowException); // If thrownValue is null, continue - - // else, raise thrownValue - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(doNotThrowException); // continue as normal - methodVisitor.visitInsn(Opcodes.POP); // Pop top, since it was not an exception - } - } else { - switch (actualResumeType) { - case YIELD_FROM: - case AWAIT: - break; - default: - throw new IllegalArgumentException( - "Invalid resume type after YIELD_VALUE: " + actualResumeType); - } - } - }); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void generateAdvanceGeneratorMethodForYieldFrom(ClassWriter classWriter, String internalClassName, - GeneratorMethodPart generatorMethodPart) { - MethodVisitor methodVisitor = generatorMethodPart.functionMetadata.methodVisitor; - - methodVisitor.visitCode(); - - // Generate bytecode for method - StackMetadata initialStackMetadata = - PythonBytecodeToJavaBytecodeTranslator.getInitialStackMetadata( - generatorMethodPart.initialStackMetadata.localVariableHelper, - generatorMethodPart.originalMethodDescriptor, false); - List opcodeList = PythonBytecodeToJavaBytecodeTranslator - .getOpcodeList(generatorMethodPart.functionMetadata.pythonCompiledFunction); - FlowGraph flowGraph = FlowGraph.createFlowGraph(generatorMethodPart.functionMetadata, initialStackMetadata, opcodeList); - List stackMetadataForOpcodeIndex = flowGraph.getStackMetadataForOperations(); - - initialStackMetadata.localVariableHelper.resetCallKeywords(methodVisitor); - if (generatorMethodPart.afterYield != 0) { - Label afterYieldLabel = new Label(); - generatorMethodPart.functionMetadata.bytecodeCounterToLabelMap.put(generatorMethodPart.afterYield, afterYieldLabel); - methodVisitor.visitJumpInsn(Opcodes.GOTO, afterYieldLabel); - } - - PythonBytecodeToJavaBytecodeTranslator.writeInstructionsForOpcodes(generatorMethodPart.functionMetadata, - stackMetadataForOpcodeIndex, opcodeList, - instruction -> { - if (instruction.offset() == generatorMethodPart.afterYield) { - // 0 = next, 1 = send, 2 = throw - - Label wasNotSentValue = new Label(); - Label wasNotThrownValue = new Label(); - Label iterateSubiterator = new Label(); - - // Push subiterator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - // Check if sent a value - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonGenerator.class), "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - PythonConstantsImplementor.loadNone(methodVisitor); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, wasNotSentValue); - - methodVisitor.visitLdcInsn(1); - methodVisitor.visitJumpInsn(Opcodes.GOTO, iterateSubiterator); - - methodVisitor.visitLabel(wasNotSentValue); - - // Check if thrown a value - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, wasNotThrownValue); - - methodVisitor.visitLdcInsn(2); - methodVisitor.visitJumpInsn(Opcodes.GOTO, iterateSubiterator); - - methodVisitor.visitLabel(wasNotThrownValue); - - methodVisitor.visitLdcInsn(0); - - methodVisitor.visitLabel(iterateSubiterator); - - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label catchStartLabel = new Label(); - Label catchEndLabel = new Label(); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, catchStartLabel, - Type.getInternalName(StopIteration.class)); - - methodVisitor.visitLabel(tryStartLabel); - BytecodeSwitchImplementor.createIntSwitch(methodVisitor, List.of(0, 1, 2), - key -> { - Label generatorOperationDone = new Label(); - switch (key) { - case 0: { // next - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - break; - } - case 1: { // send - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, - Type.getInternalName(PythonGenerator.class), - "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, - Type.getInternalName(PythonGenerator.class), - "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - FunctionImplementor.callBinaryMethod(methodVisitor, - PythonBinaryOperator.SEND.dunderMethod); - break; - } - case 2: { // throw - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, - Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, - Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - // Stack is now Throwable, Generator - - // Check if the subgenerator has a "throw" method - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn("throw"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - - // Stack is now Throwable, Generator, maybeMethod - Label ifThrowMethodPresent = new Label(); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifThrowMethodPresent); - - // does not have a throw method - // Set yieldFromIterator to null since it is finished - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(ifThrowMethodPresent); - - // Swap so it Generator, Throwable instead of Throwable, Generator - methodVisitor.visitInsn(Opcodes.SWAP); - FunctionImplementor.callBinaryMethod(methodVisitor, - PythonBinaryOperator.THROW.dunderMethod); - break; - } - } - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - methodVisitor.visitInsn(Opcodes.RETURN); // subiterator yielded something; return control to caller - }, () -> { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalStateException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, - Type.getInternalName(IllegalStateException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitInsn(Opcodes.ATHROW); - }, true); - - methodVisitor.visitLabel(tryEndLabel); - - methodVisitor.visitLabel(catchStartLabel); - methodVisitor.visitInsn(Opcodes.POP); // pop the StopIteration exception - methodVisitor.visitLabel(catchEndLabel); - - // Set yieldFromIterator to null since it is finished - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - // Restore the stack - GeneratorImplementor.restoreGeneratorState(generatorMethodPart.functionMetadata, - generatorMethodPart.initialStackMetadata); - - // Push the last yielded value to TOS - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, - PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - - // Set yielded value to null - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, - PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - - // Resume execution - } - }); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static Map createGeneratorStateToMethod(ClassWriter classWriter, - String internalClassName, - PythonCompiledFunction pythonCompiledFunction) { - Map generatorStateToMethod = new HashMap<>(); - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PRIVATE, "advance", Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor = MethodVisitorAdapters.adapt(methodVisitor, "advance", Type.getMethodDescriptor(Type.VOID_TYPE)); - Map> bytecodeIndexToArgumentorsMap = new HashMap<>(); - Map bytecodeCounterToLabelMap = new HashMap<>(); - - FunctionMetadata functionMetadata = new FunctionMetadata(); - functionMetadata.functionType = PythonFunctionType.GENERATOR; - functionMetadata.method = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.VIRTUAL, - "advance", Type.getMethodDescriptor(Type.VOID_TYPE)); - functionMetadata.bytecodeCounterToCodeArgumenterList = bytecodeIndexToArgumentorsMap; - functionMetadata.bytecodeCounterToLabelMap = bytecodeCounterToLabelMap; - functionMetadata.methodVisitor = methodVisitor; - functionMetadata.pythonCompiledFunction = pythonCompiledFunction; - functionMetadata.className = internalClassName; - - GeneratorLocalVariableHelper localVariableHelper = - new GeneratorLocalVariableHelper(classWriter, internalClassName, new Type[] {}, pythonCompiledFunction); - - MethodDescriptor stackMetadataMethod = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.STATIC, - pythonCompiledFunction.qualifiedName, pythonCompiledFunction.getAsmMethodDescriptorString()); - StackMetadata initialStackMetadata = - PythonBytecodeToJavaBytecodeTranslator.getInitialStackMetadata(localVariableHelper, stackMetadataMethod, false); - - List opcodeList = PythonBytecodeToJavaBytecodeTranslator.getOpcodeList(pythonCompiledFunction); - - FlowGraph flowGraph = FlowGraph.createFlowGraph(functionMetadata, initialStackMetadata, opcodeList); - flowGraph.visitOperations(YieldValueOpcode.class, (yieldValueOpcode, priorStackMetadata) -> { - generatorStateToMethod.put(yieldValueOpcode.getBytecodeIndex() + 1, - getGeneratorMethodPartForYield(internalClassName, classWriter, pythonCompiledFunction, - stackMetadataMethod, - priorStackMetadata.pop(), - yieldValueOpcode)); - }); - - flowGraph.visitOperations(YieldFromOpcode.class, (yieldFromOpcode, priorStackMetadata) -> { - generatorStateToMethod.put(yieldFromOpcode.getBytecodeIndex() + 1, - getGeneratorMethodPartForYield(internalClassName, classWriter, pythonCompiledFunction, - stackMetadataMethod, - priorStackMetadata.pop(2), - yieldFromOpcode)); - }); - - GeneratorMethodPart start = new GeneratorMethodPart(); - start.initialStackMetadata = initialStackMetadata; - start.functionMetadata = functionMetadata; - start.afterYield = 0; - start.originalMethodDescriptor = stackMetadataMethod; - start.instruction = PythonBytecodeInstruction.atOffset(GeneratorOpDescriptor.YIELD_VALUE, 0); - generatorStateToMethod.put(0, start); - - return generatorStateToMethod; - } - - private static GeneratorMethodPart getGeneratorMethodPartForYield(String internalClassName, - ClassWriter classWriter, - PythonCompiledFunction pythonCompiledFunction, - MethodDescriptor originalMethodDescriptor, - StackMetadata stackMetadata, - AbstractOpcode opcode) { - final String methodName = "advance" + (opcode.getBytecodeIndex() + 1); - - GeneratorMethodPart out = new GeneratorMethodPart(); - out.initialStackMetadata = stackMetadata; - out.afterYield = opcode.getBytecodeIndex() + 1; - out.instruction = opcode.getInstruction(); - out.originalMethodDescriptor = originalMethodDescriptor; - - FunctionMetadata functionMetadata = new FunctionMetadata(); - out.functionMetadata = functionMetadata; - functionMetadata.functionType = PythonFunctionType.GENERATOR; - functionMetadata.method = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.VIRTUAL, - methodName, Type.getMethodDescriptor(Type.VOID_TYPE)); - functionMetadata.bytecodeCounterToLabelMap = new HashMap<>(); - functionMetadata.bytecodeCounterToCodeArgumenterList = new HashMap<>(); - functionMetadata.className = internalClassName; - functionMetadata.methodVisitor = - classWriter.visitMethod(Modifier.PRIVATE, methodName, Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - functionMetadata.methodVisitor = MethodVisitorAdapters.adapt(functionMetadata.methodVisitor, methodName, - Type.getMethodDescriptor(Type.VOID_TYPE)); - functionMetadata.pythonCompiledFunction = pythonCompiledFunction.copy(); - return out; - } - - private static class GeneratorMethodPart { - FunctionMetadata functionMetadata; - StackMetadata initialStackMetadata; - MethodDescriptor originalMethodDescriptor; - int afterYield; - PythonBytecodeInstruction instruction; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonInterpreter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonInterpreter.java deleted file mode 100644 index e6962e8a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonInterpreter.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.types.PythonModule; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.PythonTraceback; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; - -public interface PythonInterpreter { - PythonInterpreter DEFAULT = new CPythonBackedPythonInterpreter(); - - boolean hasValidPythonReference(PythonLikeObject instance); - - void setPythonReference(PythonLikeObject instance, OpaquePythonReference reference); - - PythonLikeObject getGlobal(Map globalsMap, String name); - - void setGlobal(Map globalsMap, String name, PythonLikeObject value); - - void deleteGlobal(Map globalsMap, String name); - - PythonModule importModule(PythonInteger level, List fromList, Map globalsMap, - Map localsMap, String moduleName); - - /** - * Writes output without a trailing newline to standard output - * - * @param output the text to write - */ - void write(String output); - - /** - * Reads a line from standard input - * - * @return A line read from standard input - */ - String readLine(); - - PythonTraceback getTraceback(); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonLikeObject.java deleted file mode 100644 index eb27c209..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonLikeObject.java +++ /dev/null @@ -1,158 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Objects; - -import ai.timefold.jpyinterpreter.builtins.TernaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -/** - * Represents an Object that can be interacted with like a Python Object. - * A PythonLikeObject can refer to a Java Object, a CPython Object, or - * be an Object constructed by the Java Python Interpreter. - */ -public interface PythonLikeObject { - - /** - * Gets an attribute by name. - * - * @param attributeName Name of the attribute to get - * @return The attribute of the object that corresponds with attributeName - * @throws AttributeError if the attribute does not exist - */ - PythonLikeObject $getAttributeOrNull(String attributeName); - - /** - * Gets an attribute by name. - * - * @param attributeName Name of the attribute to get - * @return The attribute of the object that corresponds with attributeName - * @throws AttributeError if the attribute does not exist - */ - default PythonLikeObject $getAttributeOrError(String attributeName) { - PythonLikeObject out = this.$getAttributeOrNull(attributeName); - if (out == null) { - throw new AttributeError("object '" + this + "' does not have attribute '" + attributeName + "'"); - } - return out; - } - - /** - * Sets an attribute by name. - * - * @param attributeName Name of the attribute to set - * @param value Value to set the attribute to - */ - void $setAttribute(String attributeName, PythonLikeObject value); - - /** - * Delete an attribute by name. - * - * @param attributeName Name of the attribute to delete - */ - void $deleteAttribute(String attributeName); - - /** - * Returns the type describing the object - * - * @return the type describing the object - */ - PythonLikeType $getType(); - - /** - * Return a generic version of {@link PythonLikeObject#$getType()}. This is used in bytecode - * generation and not at runtime. For example, for a list of integers, this return - * list[int], while getType returns list. Both methods are needed so type([1,2,3]) is type(['a', 'b', 'c']) - * return True. - * - * @return the generic version of this object's type. Must not be used in identity checks. - */ - default PythonLikeType $getGenericType() { - return $getType(); - } - - default PythonLikeObject $method$__getattribute__(PythonString pythonName) { - String name = pythonName.value; - PythonLikeObject objectResult = $getAttributeOrNull(name); - if (objectResult != null) { - return objectResult; - } - - PythonLikeType type = $getType(); - PythonLikeObject typeResult = type.$getAttributeOrNull(name); - if (typeResult != null) { - PythonLikeObject maybeDescriptor = typeResult.$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - if (maybeDescriptor == null) { - maybeDescriptor = typeResult.$getType().$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - } - - if (maybeDescriptor != null) { - if (!(maybeDescriptor instanceof PythonLikeFunction)) { - throw new UnsupportedOperationException("'" + maybeDescriptor.$getType() + "' is not callable"); - } - return TernaryDunderBuiltin.GET_DESCRIPTOR.invoke(typeResult, this, type); - } - return typeResult; - } - - throw new AttributeError("object '" + this + "' does not have attribute '" + name + "'"); - } - - default PythonLikeObject $method$__setattr__(PythonString pythonName, PythonLikeObject value) { - String name = pythonName.value; - $setAttribute(name, value); - return PythonNone.INSTANCE; - } - - default PythonLikeObject $method$__delattr__(PythonString pythonName) { - String name = pythonName.value; - $deleteAttribute(name); - return PythonNone.INSTANCE; - } - - default PythonLikeObject $method$__eq__(PythonLikeObject other) { - return PythonBoolean.valueOf(Objects.equals(this, other)); - } - - default PythonLikeObject $method$__ne__(PythonLikeObject other) { - return PythonBoolean.valueOf(!Objects.equals(this, other)); - } - - default PythonString $method$__str__() { - return PythonString.valueOf(this.getClass().getSimpleName() + "@" + System.identityHashCode(this)); - } - - default PythonLikeObject $method$__repr__() { - String position; - if (this instanceof CPythonBackedPythonLikeObject) { - PythonInteger id = ((CPythonBackedPythonLikeObject) this).$cpythonId; - if (id != null) { - position = id.toString(); - } else { - position = String.valueOf(System.identityHashCode(this)); - } - } else { - position = String.valueOf(System.identityHashCode(this)); - } - return PythonString.valueOf("<" + $getType().getTypeName() + " object at " + position + ">"); - } - - default PythonLikeObject $method$__format__() { - return $method$__format__(PythonNone.INSTANCE); - } - - default PythonLikeObject $method$__format__(PythonLikeObject formatString) { - return $method$__str__().$method$__format__(formatString); - } - - default PythonLikeObject $method$__hash__() { - throw new TypeError("unhashable type: '" + $getType().getTypeName() + "'"); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonOverloadImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonOverloadImplementor.java deleted file mode 100644 index 297bcf1b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonOverloadImplementor.java +++ /dev/null @@ -1,500 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.implementors.KnownCallImplementor; -import ai.timefold.jpyinterpreter.types.BoundPythonLikeFunction; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.util.MethodVisitorAdapters; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class PythonOverloadImplementor { - public static Comparator TYPE_DEPTH_COMPARATOR = Comparator.comparingInt(PythonLikeType::getDepth) - .thenComparing(PythonLikeType::getTypeName) - .thenComparing(PythonLikeType::getJavaTypeInternalName) - .reversed(); - - private final static List deferredRunnerList = new ArrayList<>(); - - public interface DeferredRunner { - PythonLikeType run() throws NoSuchMethodException; - } - - public static void deferDispatchesFor(DeferredRunner runner) { - try { - createDispatchesFor(runner.run()); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - // deferredRunnerList.add(runner); - } - - public static void createDeferredDispatches() { - while (!deferredRunnerList.isEmpty()) { - List deferredRunnables = new ArrayList<>(deferredRunnerList); - - List deferredTypes = new ArrayList<>(deferredRunnables.size()); - deferredRunnables.forEach(runner -> { - try { - deferredTypes.add(runner.run()); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - }); - deferredTypes.forEach(PythonOverloadImplementor::createDispatchesFor); - deferredRunnerList.subList(0, deferredRunnables.size()).clear(); - } - } - - public static void createDispatchesFor(PythonLikeType pythonLikeType) { - for (String methodName : pythonLikeType.getKnownMethodsDefinedByClass()) { - PythonLikeFunction overloadDispatch = - createDispatchForMethod(pythonLikeType, methodName, pythonLikeType.getMethodType(methodName).orElseThrow(), - pythonLikeType.getMethodKind(methodName) - .orElse(PythonClassTranslator.PythonMethodKind.VIRTUAL_METHOD)); - pythonLikeType.$setAttribute(methodName, overloadDispatch); - } - - if (pythonLikeType.getConstructorType().isPresent()) { - PythonLikeFunction overloadDispatch = - createDispatchForMethod(pythonLikeType, "__init__", pythonLikeType.getConstructorType().orElseThrow(), - PythonClassTranslator.PythonMethodKind.VIRTUAL_METHOD); - pythonLikeType.setConstructor(overloadDispatch); - pythonLikeType.$setAttribute("__init__", overloadDispatch); - } - } - - private static PythonLikeFunction createDispatchForMethod(PythonLikeType pythonLikeType, - String methodName, - PythonKnownFunctionType knownFunctionType, - PythonClassTranslator.PythonMethodKind methodKind) { - String maybeClassName = PythonBytecodeToJavaBytecodeTranslator.GENERATED_PACKAGE_BASE - + pythonLikeType.getJavaTypeInternalName().replace('/', '.') + "." - + methodName + "$$Dispatcher"; - int numberOfInstances = - PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge(maybeClassName, 1, Integer::sum); - if (numberOfInstances > 1) { - maybeClassName = maybeClassName + "$$" + numberOfInstances; - } - String className = maybeClassName; - String internalClassName = className.replace('.', '/'); - - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - - classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, - Type.getInternalName(Object.class), new String[] { - Type.getInternalName(PythonLikeFunction.class) - }); - - // No args constructor for creating instance of this class - MethodVisitor methodVisitor = - classWriter.visitMethod(Modifier.PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE), - null, null); - methodVisitor.visitCode(); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitInsn(Opcodes.RETURN); - - methodVisitor.visitMaxs(-1, -1); - methodVisitor.visitEnd(); - - createDispatchFunction(pythonLikeType, knownFunctionType, classWriter); - createGetTypeFunction(methodKind, classWriter); - - classWriter.visitEnd(); - PythonBytecodeToJavaBytecodeTranslator.writeClassOutput(BuiltinTypes.classNameToBytecode, className, - classWriter.toByteArray()); - - try { - Class generatedClass = - (Class) BuiltinTypes.asmClassLoader.loadClass(className); - return generatedClass.getConstructor().newInstance(); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Impossible State: Unable to load generated class (" + - className + ") despite it being just generated.", e); - } catch (InvocationTargetException | InstantiationException | NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Impossible State: Unable to invoke constructor for generated class (" + - className + ").", e); - } - } - - private static void createGetTypeFunction(PythonClassTranslator.PythonMethodKind kind, ClassWriter classWriter) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$getType", - Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - null, - null); - - methodVisitor.visitCode(); - - switch (kind) { - case VIRTUAL_METHOD: - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(BuiltinTypes.class), - "FUNCTION_TYPE", Type.getDescriptor(PythonLikeType.class)); - break; - case STATIC_METHOD: - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(BuiltinTypes.class), - "STATIC_FUNCTION_TYPE", Type.getDescriptor(PythonLikeType.class)); - break; - case CLASS_METHOD: - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(BuiltinTypes.class), - "CLASS_FUNCTION_TYPE", Type.getDescriptor(PythonLikeType.class)); - break; - default: - throw new IllegalStateException("Unhandled case: " + kind); - } - methodVisitor.visitInsn(Opcodes.ARETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - - } - - private static void createDispatchFunction(PythonLikeType type, PythonKnownFunctionType knownFunctionType, - ClassWriter classWriter) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - null, - null); - - methodVisitor = MethodVisitorAdapters.adapt(methodVisitor, "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class))); - - methodVisitor.visitParameter("positionalArguments", 0); - methodVisitor.visitParameter("namedArguments", 0); - methodVisitor.visitParameter("callerInstance", 0); - methodVisitor.visitCode(); - - List overloadList = knownFunctionType.getOverloadFunctionSignatureList(); - - Map> pythonFunctionSignatureByArgumentLength = overloadList.stream() - .filter(sig -> sig.getExtraPositionalArgumentsVariableIndex().isEmpty() - && sig.getExtraKeywordArgumentsVariableIndex().isEmpty()) - .collect(Collectors.groupingBy(sig -> sig.getParameterTypes().length)); - - Optional maybeGenericFunctionSignature = overloadList.stream() - .findAny(); - - if (overloadList.get(0).isFromArgumentSpec() - || pythonFunctionSignatureByArgumentLength.isEmpty()) { // only generic overload - // No error message since we MUST have a generic overload - createGenericDispatch(methodVisitor, type, maybeGenericFunctionSignature, ""); - } else { - int[] argCounts = pythonFunctionSignatureByArgumentLength.keySet().stream().sorted().mapToInt(i -> i).toArray(); - Label[] argCountLabel = new Label[argCounts.length]; - Label defaultCase = new Label(); - - for (int i = 0; i < argCountLabel.length; i++) { - argCountLabel[i] = new Label(); - } - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), "size", - Type.getMethodDescriptor(Type.INT_TYPE), true); - if (!overloadList.get(0).getMethodDescriptor().getMethodType().isStatic()) { - methodVisitor.visitInsn(Opcodes.ICONST_M1); - methodVisitor.visitInsn(Opcodes.IADD); - } - methodVisitor.visitLookupSwitchInsn(defaultCase, argCounts, argCountLabel); - - for (int i = 0; i < argCounts.length; i++) { - methodVisitor.visitLabel(argCountLabel[i]); - createDispatchForArgCount(methodVisitor, argCounts[i], type, - pythonFunctionSignatureByArgumentLength.get(argCounts[i]), - maybeGenericFunctionSignature); - } - methodVisitor.visitLabel(defaultCase); - - createGenericDispatch(methodVisitor, type, maybeGenericFunctionSignature, - "No overload has the given argcount. Possible overload(s) are: " + - knownFunctionType.getOverloadFunctionSignatureList().stream().map(PythonFunctionSignature::toString) - .collect(Collectors.joining(",\n"))); - } - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - private static void createDispatchForArgCount(MethodVisitor methodVisitor, int argCount, - PythonLikeType type, List functionSignatureList, - Optional maybeGenericDispatch) { - final int MATCHING_OVERLOAD_SET_VARIABLE_INDEX = 3; // 0 = this; 1 = posArguments; 2 = namedArguments - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(HashSet.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(functionSignatureList.size()); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(HashSet.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false); - for (int i = 0; i < functionSignatureList.size(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", - Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - } - methodVisitor.visitVarInsn(Opcodes.ASTORE, MATCHING_OVERLOAD_SET_VARIABLE_INDEX); - - int startIndex = 0; - if (!functionSignatureList.get(0).getMethodDescriptor().getMethodType().isStatic()) { - startIndex = 1; - } - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - - // At the start of each iteration, stack = arg_1, arg_2, ..., arg_(i-1), pos_args_list - for (int i = 0; i < argCount; i++) { - methodVisitor.visitInsn(Opcodes.DUP); - - // Get the ith positional argument - methodVisitor.visitLdcInsn(i + startIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - - SortedMap> typeToPossibleSignatures = - getTypeForParameter(functionSignatureList, i); - - Label endOfInstanceOfIfs = new Label(); - for (PythonLikeType pythonLikeType : typeToPossibleSignatures.keySet()) { - Label nextIf = new Label(); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, pythonLikeType.getJavaTypeInternalName()); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, nextIf); - - // pythonLikeType matches argument type - List matchingOverloadList = typeToPossibleSignatures.get(pythonLikeType); - - if (matchingOverloadList.size() != functionSignatureList.size()) { - // Remove overloads that do not match from the matching overload set - methodVisitor.visitVarInsn(Opcodes.ALOAD, MATCHING_OVERLOAD_SET_VARIABLE_INDEX); - for (int sigIndex = 0; sigIndex < functionSignatureList.size(); sigIndex++) { - if (!matchingOverloadList.contains(functionSignatureList.get(sigIndex))) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(sigIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", - Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "remove", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - } - } - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfInstanceOfIfs); - } else { - methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfInstanceOfIfs); - } - methodVisitor.visitLabel(nextIf); - } - // This is an else at the end of the instanceof if's, which clear the set as no overloads match - methodVisitor.visitVarInsn(Opcodes.ALOAD, MATCHING_OVERLOAD_SET_VARIABLE_INDEX); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), "clear", - Type.getMethodDescriptor(Type.VOID_TYPE), true); - - // end of instance of ifs - methodVisitor.visitLabel(endOfInstanceOfIfs); - methodVisitor.visitInsn(Opcodes.POP); // remove argument (need to typecast it later) - } - methodVisitor.visitInsn(Opcodes.POP); // Remove list - - // Stack is arg_1, arg_2, ..., arg_(argCount) - methodVisitor.visitVarInsn(Opcodes.ALOAD, MATCHING_OVERLOAD_SET_VARIABLE_INDEX); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), "size", - Type.getMethodDescriptor(Type.INT_TYPE), true); - - Label setIsNotEmpty = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, setIsNotEmpty); - - createGenericDispatch(methodVisitor, type, maybeGenericDispatch, - "No overload match the given arguments. Possible overload(s) for " + argCount - + " arguments are: " + - functionSignatureList.stream().map(PythonFunctionSignature::toString) - .collect(Collectors.joining(",\n"))); - - methodVisitor.visitLabel(setIsNotEmpty); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Iterable.class), "iterator", - Type.getMethodDescriptor(Type.getType(Iterator.class)), true); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Iterator.class), "next", - Type.getMethodDescriptor(Type.getType(Object.class)), true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Integer.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", - Type.getMethodDescriptor(Type.INT_TYPE), false); - - Label defaultHandler = new Label(); - Label[] signatureIndexToDispatch = new Label[functionSignatureList.size()]; - for (int i = 0; i < functionSignatureList.size(); i++) { - signatureIndexToDispatch[i] = new Label(); - } - - methodVisitor.visitTableSwitchInsn(0, functionSignatureList.size() - 1, defaultHandler, signatureIndexToDispatch); - - for (int i = 0; i < functionSignatureList.size(); i++) { - methodVisitor.visitLabel(signatureIndexToDispatch[i]); - PythonFunctionSignature matchingSignature = functionSignatureList.get(i); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - - if (startIndex != 0) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - matchingSignature.getMethodDescriptor().getDeclaringClassInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - - for (int argIndex = 0; argIndex < argCount; argIndex++) { - PythonLikeType parameterType = matchingSignature.getParameterTypes()[argIndex]; - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(argIndex + startIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, parameterType.getJavaTypeInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - matchingSignature.getMethodDescriptor().callMethod(methodVisitor); - methodVisitor.visitInsn(Opcodes.ARETURN); - } - - methodVisitor.visitLabel(defaultHandler); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalStateException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn("Return signature index is out of bounds"); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalStateException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - - private static void createGenericDispatch(MethodVisitor methodVisitor, - PythonLikeType type, Optional maybeGenericDispatch, String errorMessage) { - if (maybeGenericDispatch.isEmpty()) { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(TypeError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), - false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonOverloadImplementor.class), - "getCallErrorInfo", Type.getMethodDescriptor(Type.getType(String.class), - Type.getType(List.class), - Type.getType(Map.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.getType(String.class)), - false); - methodVisitor.visitLdcInsn(errorMessage); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "toString", Type.getMethodDescriptor(Type.getType(String.class)), - false); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(TypeError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } else { - PythonFunctionSignature functionSignature = maybeGenericDispatch.get(); - if (functionSignature.getMethodDescriptor().getMethodType() != MethodDescriptor.MethodType.STATIC) { - // It a class/virtual method, so need to load instance/type from argument list - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitLdcInsn(0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - if (functionSignature.getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.CLASS) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(BoundPythonLikeFunction.class), - "boundToTypeOfObject", - Type.getMethodDescriptor(Type.getType(BoundPythonLikeFunction.class), - Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeFunction.class)), - false); - } else { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(BoundPythonLikeFunction.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeFunction.class)), - false); - } - - // Load the sublist without the self/type argument - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "size", Type.getMethodDescriptor(Type.INT_TYPE), true); - methodVisitor.visitLdcInsn(1); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "subList", Type.getMethodDescriptor(Type.getType(List.class), Type.INT_TYPE, Type.INT_TYPE), - true); - } else { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - } - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - KnownCallImplementor.callUnpackListAndMap(functionSignature.getDefaultArgumentHolderClassInternalName(), - functionSignature.getMethodDescriptor(), methodVisitor); - methodVisitor.visitInsn(Opcodes.ARETURN); - } - } - - public static String getCallErrorInfo(List positionalArgs, - Map namedArgs) { - return "Could not find an overload that accept " + positionalArgs.stream() - .map(arg -> arg.$getType().getTypeName()).collect(Collectors.joining(", ", "(", ") argument types. ")); - } - - private static SortedMap> - getTypeForParameter(List functionSignatureList, int parameter) { - SortedMap> out = new TreeMap<>(TYPE_DEPTH_COMPARATOR); - for (PythonFunctionSignature functionSignature : functionSignatureList) { - out.computeIfAbsent(functionSignature.getParameterTypes()[parameter], type -> new ArrayList<>()) - .add(functionSignature); - } - return out; - } - -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonTernaryOperator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonTernaryOperator.java deleted file mode 100644 index 604e5085..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonTernaryOperator.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter; - -/** - * The list of all Python Ternary Operators, which take - * self and two other arguments. - * - * ex: a.__setitem__(key, value) - */ -public enum PythonTernaryOperator { - // Descriptor operations - // https://docs.python.org/3/howto/descriptor.html - GET("__get__"), - SET("__set__"), - - // Attribute access - SET_ATTRIBUTE("__setattr__"), - - // List operations - // https://docs.python.org/3/reference/datamodel.html#object.__setitem__ - SET_ITEM("__setitem__"); - - public final String dunderMethod; - - PythonTernaryOperator(String dunderMethod) { - this.dunderMethod = dunderMethod; - } - - public String getDunderMethod() { - return dunderMethod; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonUnaryOperator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonUnaryOperator.java deleted file mode 100644 index 7d32924a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonUnaryOperator.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter; - -/** - * The list of all Python Unary Operators, which take - * self as the only argument. - * - * ex: a.__neg__() - */ -public enum PythonUnaryOperator { - NEGATIVE("__neg__"), - POSITIVE("__pos__"), - ABS("__abs__"), - INVERT("__invert__"), - ITERATOR("__iter__"), - AS_BOOLEAN("__bool__"), - AS_FLOAT("__float__"), - AS_INT("__int__"), - AS_INDEX("__index__"), - AS_STRING("__str__"), - REPRESENTATION("__repr__"), - ENTER("__enter__"), - LENGTH("__len__"), - NEXT("__next__"), - REVERSED("__reversed__"), - HASH("__hash__"); - - final String dunderMethod; - - PythonUnaryOperator(String dunderMethod) { - this.dunderMethod = dunderMethod; - } - - public String getDunderMethod() { - return dunderMethod; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java deleted file mode 100644 index 794fff78..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java +++ /dev/null @@ -1,111 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.Objects; - -public final class PythonVersion implements Comparable { - private final int hexversion; - - 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_10; - - public PythonVersion(int hexversion) { - this.hexversion = hexversion; - } - - public PythonVersion(int major, int minor) { - // Select the final version for the first Python release with the given major and minor - this(major, minor, 0); - } - - public PythonVersion(int major, int minor, int micro) { - // Select the final version for the first Python release with the given major, minor and micro - this((major << (8 * 3)) + (minor << (8 * 2)) + (micro << 8) + 0xF0); - } - - public int getMajorVersion() { - return (hexversion & 0xFF000000) >> (8 * 3); - } - - public int getMinorVersion() { - return (hexversion & 0x00FF0000) >> (8 * 2); - } - - public int getMicroVersion() { - return (hexversion & 0x0000FF00) >> 8; - } - - public int getReleaseLevel() { - return (hexversion & 0x000000F0) >> 4; - } - - public int getReleaseSerial() { - return (hexversion & 0x0000000F); - } - - @Override - public int compareTo(PythonVersion pythonVersion) { - return hexversion - pythonVersion.hexversion; - } - - public boolean isBefore(PythonVersion release) { - return compareTo(release) < 0; - } - - public boolean isAfter(PythonVersion release) { - return compareTo(release) > 0; - } - - public boolean isAtLeast(PythonVersion release) { - return compareTo(release) >= 0; - } - - public boolean isBetween(PythonVersion afterInclusive, PythonVersion beforeInclusive) { - return compareTo(afterInclusive) >= 0 && compareTo(beforeInclusive) <= 0; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonVersion that = (PythonVersion) o; - return hexversion == that.hexversion; - } - - @Override - public int hashCode() { - return Objects.hash(hexversion); - } - - public String toString() { - return getMajorVersion() + "." + getMinorVersion() + "." + getMinorVersion() + getReleaseLevelString() - + getReleaseSerial(); - } - - private String getReleaseLevelString() { - switch (getReleaseLevel()) { - case 0xA: - return "a"; // Alpha release - - case 0xB: - return "b"; // Beta release - - case 0xC: - return "rc"; // Release Candidate - - case 0xF: - return ""; // Final - - default: - // https://docs.python.org/3/c-api/apiabiversion.html#apiabiversion - // only use 4 states out of the possible 16. - return ("<" + getReleaseLevel() + ">"); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java deleted file mode 100644 index 9b314ca0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java +++ /dev/null @@ -1,354 +0,0 @@ -package ai.timefold.jpyinterpreter; - -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; - -import ai.timefold.jpyinterpreter.opcodes.OpcodeWithoutSource; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class StackMetadata { - public static final StackMetadata DEAD_CODE = new StackMetadata(); - - public final LocalVariableHelper localVariableHelper; - - private final List stackValueSources; - private final List localVariableValueSources; - private final List cellVariableValueSources; - - private List callKeywordNameList; - - private StackMetadata() { - this.localVariableHelper = null; - this.stackValueSources = null; - this.localVariableValueSources = null; - this.cellVariableValueSources = null; - this.callKeywordNameList = null; - } - - public StackMetadata(LocalVariableHelper localVariableHelper) { - this.localVariableHelper = localVariableHelper; - this.stackValueSources = new ArrayList<>(); - this.localVariableValueSources = new ArrayList<>(localVariableHelper.getNumberOfLocalVariables()); - this.cellVariableValueSources = new ArrayList<>(localVariableHelper.getNumberOfCells()); - for (int i = 0; i < localVariableHelper.getNumberOfLocalVariables(); i++) { - localVariableValueSources.add(null); - } - for (int i = 0; i < localVariableHelper.getNumberOfCells(); i++) { - cellVariableValueSources.add(ValueSourceInfo.of(new OpcodeWithoutSource(), - BuiltinTypes.BASE_TYPE)); - } - this.callKeywordNameList = Collections.emptyList(); - } - - private StackMetadata(LocalVariableHelper localVariableHelper, List stackValueSources, - List localVariableValueSources, List cellVariableValueSources, - List callKeywordNameList) { - this.localVariableHelper = localVariableHelper; - this.stackValueSources = stackValueSources; - this.localVariableValueSources = localVariableValueSources; - this.cellVariableValueSources = cellVariableValueSources; - this.callKeywordNameList = callKeywordNameList; - } - - public boolean isDeadCode() { - return this == DEAD_CODE; - } - - public int getStackSize() { - return stackValueSources.size(); - } - - /** - * 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)). - * - * @param stackIndex The stack index (how many elements below TOS) - * @return The corresponding list index corresponding to the element at the given distance from TOS - * (i.e. STACK_SIZE - distance - 1) - */ - private int getListIndexForStackIndex(int stackIndex) { - return stackValueSources.size() - stackIndex - 1; - } - - /** - * Returns the value source for the given stack index (stack index is how many - * elements below TOS (i.e. 0 is TOS, 1 is TOS1)). - * - * @param index The stack index (how many elements below TOS) - * @return The type at the given stack index - */ - public ValueSourceInfo getValueSourceForStackIndex(int index) { - return stackValueSources.get(getListIndexForStackIndex(index)); - } - - /** - * Returns the value sources up to (and not including) the given stack index (stack index is how many - * elements below TOS (i.e. 0 is TOS, 1 is TOS1)). - * - * @param index The stack index (how many elements below TOS) - * @return The value sources up to (and not including) the given stack index - */ - public List getValueSourcesUpToStackIndex(int index) { - return stackValueSources.subList(stackValueSources.size() - index, stackValueSources.size()); - } - - /** - * Returns the type at the given stack index (stack index is how many - * elements below TOS (i.e. 0 is TOS, 1 is TOS1)). - * - * @param index The stack index (how many elements below TOS) - * @return The type at the given stack index - */ - public PythonLikeType getTypeAtStackIndex(int index) { - ValueSourceInfo valueSourceInfo = stackValueSources.get(getListIndexForStackIndex(index)); - if (valueSourceInfo != null) { - return valueSourceInfo.valueType; - } - // Unknown type - return BuiltinTypes.BASE_TYPE; - } - - /** - * Returns the value source for the local variable in slot {@code index} - * - * @param index The slot - * @return The type for the local variable in the given slot - */ - public ValueSourceInfo getLocalVariableValueSource(int index) { - return localVariableValueSources.get(index); - } - - /** - * Returns the value source for the cell variable in slot {@code index} - * - * @param index The slot - * @return The type for the cell variable in the given slot - */ - public ValueSourceInfo getCellVariableValueSource(int index) { - return cellVariableValueSources.get(index); - } - - public PythonLikeType getTOSType() { - return getTypeAtStackIndex(0); - } - - public ValueSourceInfo getTOSValueSource() { - return getValueSourceForStackIndex(0); - } - - public StackMetadata copy() { - StackMetadata out = new StackMetadata(localVariableHelper, new ArrayList<>(stackValueSources), - new ArrayList<>(localVariableValueSources), - new ArrayList<>(cellVariableValueSources), - callKeywordNameList); - return out; - } - - public StackMetadata unifyWith(StackMetadata other) { - if (this == DEAD_CODE) { - return other; - } - - if (other == DEAD_CODE) { - return this; - } - - StackMetadata out = copy(); - if (out.stackValueSources.size() != other.stackValueSources.size() || - out.localVariableValueSources.size() != other.localVariableValueSources.size() || - out.cellVariableValueSources.size() != other.cellVariableValueSources.size()) { - throw new IllegalArgumentException("Impossible State: Bytecode stack metadata size does not match when " + - "unifying (" + out.stackValueSources.stream() - .map(valueSource -> valueSource.valueType.toString()).collect(Collectors.joining(", ", "[", "]")) - + - ") with (" + other.stackValueSources.stream() - .map(valueSource -> valueSource.valueType.toString()).collect(Collectors.joining(", ", "[", "]")) - + ")"); - } - - for (int i = 0; i < out.stackValueSources.size(); i++) { - out.stackValueSources.set(i, unifyTypes(stackValueSources.get(i), other.stackValueSources.get(i))); - } - - for (int i = 0; i < out.localVariableValueSources.size(); i++) { - out.localVariableValueSources.set(i, - unifyTypes(localVariableValueSources.get(i), other.localVariableValueSources.get(i))); - } - - for (int i = 0; i < out.cellVariableValueSources.size(); i++) { - out.cellVariableValueSources.set(i, - unifyTypes(cellVariableValueSources.get(i), other.cellVariableValueSources.get(i))); - } - - return out; - } - - private static ValueSourceInfo unifyTypes(ValueSourceInfo a, ValueSourceInfo b) { - if (Objects.equals(a, b)) { - return a; - } - - if (a == null) { // a or b are null when they are deleted/are not set yet - return b; // TODO: Optional type? - } - - if (b == null) { - return a; - } - - return a.unifyWith(b); - } - - /** - * Return a new StackMetadata with {@code type} added as the new - * TOS element. - * - * @param type The type to push to TOS - */ - public StackMetadata push(ValueSourceInfo type) { - StackMetadata out = copy(); - out.stackValueSources.add(type); - return out; - } - - public StackMetadata set(int index, ValueSourceInfo type) { - StackMetadata out = copy(); - out.stackValueSources.set(getListIndexForStackIndex(index), type); - return out; - } - - public StackMetadata pushTemp(PythonLikeType type) { - return push(ValueSourceInfo.of(new OpcodeWithoutSource(), type)); - } - - /** - * Return a new StackMetadata with {@code types} added as the new - * elements. The last element of {@code types} is TOS. - * - * @param types The types to push to TOS - */ - public StackMetadata push(ValueSourceInfo... types) { - StackMetadata out = copy(); - out.stackValueSources.addAll(Arrays.asList(types)); - return out; - } - - public StackMetadata pushTemps(PythonLikeType... types) { - StackMetadata out = copy(); - for (PythonLikeType type : types) { - out.stackValueSources.add(ValueSourceInfo.of(new OpcodeWithoutSource(), type)); - } - return out; - } - - /** - * Return a new StackMetadata with {@code types} as the stack; - * The original stack is cleared. - * - * @param types The stack types. - */ - public StackMetadata stack(ValueSourceInfo... types) { - StackMetadata out = copy(); - out.stackValueSources.clear(); - out.stackValueSources.addAll(Arrays.asList(types)); - return out; - } - - /** - * Return a new StackMetadata with TOS popped - */ - public StackMetadata pop() { - StackMetadata out = copy(); - out.stackValueSources.remove(stackValueSources.size() - 1); - return out; - } - - /** - * Return a new StackMetadata with the top {@code count} items popped. - */ - public StackMetadata pop(int count) { - StackMetadata out = copy(); - out.stackValueSources.subList(stackValueSources.size() - count, stackValueSources.size()).clear(); - return out; - } - - /** - * Return a new StackMetadata with the local variable in slot {@code index} type set to - * {@code type}. - */ - public StackMetadata setLocalVariableValueSource(int index, ValueSourceInfo type) { - StackMetadata out = copy(); - out.localVariableValueSources.set(index, type); - return out; - } - - /** - * Return a new StackMetadata with the given local types. Throws {@link IllegalArgumentException} if - * types.length != localVariableTypes.size(). - */ - public StackMetadata locals(ValueSourceInfo... types) { - if (types.length != localVariableValueSources.size()) { - throw new IllegalArgumentException( - "Length mismatch: expected an array with {" + localVariableValueSources.size() + "} elements but got " + - "{" + Arrays.toString(types) + "}"); - } - StackMetadata out = copy(); - for (int i = 0; i < types.length; i++) { - out.localVariableValueSources.set(i, types[i]); - } - return out; - } - - /** - * Return a new StackMetadata with the cell variable in slot {@code index} type set to - * {@code type}. - */ - public StackMetadata setCellVariableValueSource(int index, ValueSourceInfo type) { - StackMetadata out = copy(); - out.cellVariableValueSources.set(index, type); - return out; - } - - public List getCallKeywordNameList() { - return callKeywordNameList; - } - - public StackMetadata setCallKeywordNameList(List callKeywordNameList) { - StackMetadata out = copy(); - out.callKeywordNameList = callKeywordNameList; - return out; - } - - public String toString() { - return "StackMetadata { stack: " + stackValueSources.toString() + "; locals: " + localVariableValueSources.toString() + - "; cells: " + cellVariableValueSources.toString() + "; }"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (this == DEAD_CODE || o == DEAD_CODE) { - return false; // this != o and one is DEAD_CODE - } - - StackMetadata that = (StackMetadata) o; - return stackValueSources.equals(that.stackValueSources) - && localVariableValueSources.equals(that.localVariableValueSources) - && cellVariableValueSources.equals(that.cellVariableValueSources); - } - - @Override - public int hashCode() { - return Objects.hash(stackValueSources, localVariableValueSources, cellVariableValueSources); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/TypeHint.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/TypeHint.java deleted file mode 100644 index c7341c23..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/TypeHint.java +++ /dev/null @@ -1,65 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public record TypeHint(PythonLikeType type, List annotationList, TypeHint[] genericArgs, - PythonLikeType javaGetterType) { - public TypeHint { - annotationList = Collections.unmodifiableList(annotationList); - } - - public TypeHint(PythonLikeType type, List annotationList) { - this(type, annotationList, null, type); - } - - public TypeHint(PythonLikeType type, List annotationList, PythonLikeType javaGetterType) { - this(type, annotationList, null, javaGetterType); - } - - public TypeHint addAnnotations(List addedAnnotations) { - List combinedAnnotations = new ArrayList<>(annotationList.size() + addedAnnotations.size()); - combinedAnnotations.addAll(annotationList); - combinedAnnotations.addAll(addedAnnotations); - return new TypeHint(type, combinedAnnotations, genericArgs, javaGetterType); - } - - public static TypeHint withoutAnnotations(PythonLikeType type) { - return new TypeHint(type, Collections.emptyList()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof TypeHint typeHint)) { - return false; - } - return Objects.equals(type, typeHint.type) && Objects.deepEquals(genericArgs, - typeHint.genericArgs) - && Objects.equals(javaGetterType, - typeHint.javaGetterType) - && Objects.equals(annotationList, typeHint.annotationList); - } - - @Override - public int hashCode() { - return Objects.hash(type, annotationList, Arrays.hashCode(genericArgs), javaGetterType); - } - - @Override - public String toString() { - return "TypeHint{" + - "type=" + type + - ", annotationList=" + annotationList + - ", genericArgs=" + Arrays.toString(genericArgs) + - ", javaGetterType=" + javaGetterType + - '}'; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ValueSourceInfo.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ValueSourceInfo.java deleted file mode 100644 index 86b06dc9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/ValueSourceInfo.java +++ /dev/null @@ -1,96 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class ValueSourceInfo { - final PythonLikeType valueType; - final Set possibleSourceOpcodeSet; - final Set valueDependencySet; - - private ValueSourceInfo(PythonLikeType valueType, - Set possibleSourceOpcodeSet, - Set valueDependencySet) { - this.valueType = valueType; - this.possibleSourceOpcodeSet = possibleSourceOpcodeSet; - this.valueDependencySet = valueDependencySet; - } - - public PythonLikeType getValueType() { - return valueType; - } - - public Set getPossibleSourceOpcodeSet() { - return possibleSourceOpcodeSet; - } - - public Set getValueDependencySet() { - return valueDependencySet; - } - - public ValueSourceInfo unifyWith(ValueSourceInfo other) { - PythonLikeType newValueType = valueType.unifyWith(other.valueType); - - Set newPossibleSourceOpcodeSet = - new HashSet<>(possibleSourceOpcodeSet.size() + other.possibleSourceOpcodeSet.size()); - newPossibleSourceOpcodeSet.addAll(possibleSourceOpcodeSet); - newPossibleSourceOpcodeSet.addAll(other.possibleSourceOpcodeSet); - - Set newValueDependencySet = new HashSet<>(valueDependencySet.size() + other.valueDependencySet.size()); - newValueDependencySet.addAll(valueDependencySet); - - for (ValueSourceInfo dependency : other.valueDependencySet) { - Optional maybeCommonDependency = valueDependencySet.stream() - .filter(otherValueSource -> otherValueSource.possibleSourceOpcodeSet - .equals(dependency.getPossibleSourceOpcodeSet())) - .findAny(); - - // If it is not empty, it was added in the previous loop - if (maybeCommonDependency.isEmpty()) { - newValueDependencySet.add(dependency); - } - } - return new ValueSourceInfo(newValueType, newPossibleSourceOpcodeSet, newValueDependencySet); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ValueSourceInfo that = (ValueSourceInfo) o; - return valueType.equals(that.valueType) && possibleSourceOpcodeSet.equals(that.possibleSourceOpcodeSet) - && valueDependencySet.equals(that.valueDependencySet); - } - - @Override - public int hashCode() { - return Objects.hash(valueType, possibleSourceOpcodeSet, valueDependencySet); - } - - @Override - public String toString() { - return "ValueSourceInfo{" + - "valueType=" + valueType + - ", possibleSourceOpcodeList=" + possibleSourceOpcodeSet + - ", valueDependencyList=" + valueDependencySet + - '}'; - } - - public static ValueSourceInfo of(Opcode sourceOpcode, PythonLikeType valueType, ValueSourceInfo... dependencies) { - return new ValueSourceInfo(valueType, Set.of(sourceOpcode), Set.of(dependencies)); - } - - public static ValueSourceInfo of(Opcode sourceOpcode, PythonLikeType valueType, List dependencyList) { - return new ValueSourceInfo(valueType, Set.of(sourceOpcode), new HashSet<>(dependencyList)); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/BinaryDunderBuiltin.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/BinaryDunderBuiltin.java deleted file mode 100644 index de2377f5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/BinaryDunderBuiltin.java +++ /dev/null @@ -1,50 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class BinaryDunderBuiltin implements PythonLikeFunction { - private final String DUNDER_METHOD_NAME; - - public static final BinaryDunderBuiltin DIVMOD = new BinaryDunderBuiltin(PythonBinaryOperator.DIVMOD); - public static final BinaryDunderBuiltin ADD = new BinaryDunderBuiltin(PythonBinaryOperator.ADD); - public static final BinaryDunderBuiltin LESS_THAN = new BinaryDunderBuiltin(PythonBinaryOperator.LESS_THAN); - public static final BinaryDunderBuiltin GET_ITEM = new BinaryDunderBuiltin(PythonBinaryOperator.GET_ITEM); - public static final BinaryDunderBuiltin GET_ATTRIBUTE = new BinaryDunderBuiltin(PythonBinaryOperator.GET_ATTRIBUTE); - public static final BinaryDunderBuiltin POWER = new BinaryDunderBuiltin(PythonBinaryOperator.POWER); - public static final BinaryDunderBuiltin FORMAT = new BinaryDunderBuiltin(PythonBinaryOperator.FORMAT); - - public BinaryDunderBuiltin(String dunderMethodName) { - DUNDER_METHOD_NAME = dunderMethodName; - } - - public BinaryDunderBuiltin(PythonBinaryOperator operator) { - DUNDER_METHOD_NAME = operator.getDunderMethod(); - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - namedArguments = (namedArguments != null) ? namedArguments : Map.of(); - - if (positionalArguments.size() != 2) { - throw new ValueError("Function " + DUNDER_METHOD_NAME + " expects 2 positional arguments"); - } - - PythonLikeObject object = positionalArguments.get(0); - PythonLikeObject arg = positionalArguments.get(1); - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object, arg), Map.of(), null); - } - - public PythonLikeObject invoke(PythonLikeObject object, PythonLikeObject arg) { - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object, arg), Map.of(), null); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/FunctionBuiltinOperations.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/FunctionBuiltinOperations.java deleted file mode 100644 index 1837d11b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/FunctionBuiltinOperations.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.BoundPythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; - -public class FunctionBuiltinOperations { - public static PythonLikeObject bindFunctionToInstance(final PythonLikeFunction function, final PythonLikeObject instance, - final PythonLikeType type) { - if (instance == PythonNone.INSTANCE) { - return function; - } - return new BoundPythonLikeFunction(instance, function); - } - - public static PythonLikeObject bindFunctionToType(final PythonLikeFunction function, final PythonLikeObject instance, - final PythonLikeType type) { - return new BoundPythonLikeFunction(type, function); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java deleted file mode 100644 index 716465a5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java +++ /dev/null @@ -1,1752 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.BASE_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.BOOLEAN_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.BYTES_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.BYTE_ARRAY_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.COMPLEX_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.DICT_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.FLOAT_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.FROZEN_SET_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.INT_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.LIST_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.NONE_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.RANGE_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.SET_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.SLICE_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.STRING_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.TUPLE_TYPE; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.TYPE_TYPE; -import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.StreamSupport; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; -import ai.timefold.jpyinterpreter.types.Ellipsis; -import ai.timefold.jpyinterpreter.types.NotImplemented; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonSlice; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.PythonSuperObject; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; -import ai.timefold.jpyinterpreter.types.errors.BufferError; -import ai.timefold.jpyinterpreter.types.errors.GeneratorExit; -import ai.timefold.jpyinterpreter.types.errors.ImportError; -import ai.timefold.jpyinterpreter.types.errors.ModuleNotFoundError; -import ai.timefold.jpyinterpreter.types.errors.NameError; -import ai.timefold.jpyinterpreter.types.errors.NotImplementedError; -import ai.timefold.jpyinterpreter.types.errors.PythonAssertionError; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; -import ai.timefold.jpyinterpreter.types.errors.PythonException; -import ai.timefold.jpyinterpreter.types.errors.RecursionError; -import ai.timefold.jpyinterpreter.types.errors.ReferenceError; -import ai.timefold.jpyinterpreter.types.errors.RuntimeError; -import ai.timefold.jpyinterpreter.types.errors.StopAsyncIteration; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.UnboundLocalError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.ArithmeticError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.FloatingPointError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.OverflowError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.ZeroDivisionError; -import ai.timefold.jpyinterpreter.types.errors.io.BlockingIOError; -import ai.timefold.jpyinterpreter.types.errors.io.ChildProcessError; -import ai.timefold.jpyinterpreter.types.errors.io.EOFError; -import ai.timefold.jpyinterpreter.types.errors.io.FileExistsError; -import ai.timefold.jpyinterpreter.types.errors.io.FileNotFoundError; -import ai.timefold.jpyinterpreter.types.errors.io.InterruptedError; -import ai.timefold.jpyinterpreter.types.errors.io.IsADirectoryError; -import ai.timefold.jpyinterpreter.types.errors.io.KeyboardInterrupt; -import ai.timefold.jpyinterpreter.types.errors.io.MemoryError; -import ai.timefold.jpyinterpreter.types.errors.io.NotADirectoryError; -import ai.timefold.jpyinterpreter.types.errors.io.OSError; -import ai.timefold.jpyinterpreter.types.errors.io.PermissionError; -import ai.timefold.jpyinterpreter.types.errors.io.ProcessLookupError; -import ai.timefold.jpyinterpreter.types.errors.io.SystemError; -import ai.timefold.jpyinterpreter.types.errors.io.SystemExit; -import ai.timefold.jpyinterpreter.types.errors.io.TimeoutError; -import ai.timefold.jpyinterpreter.types.errors.io.connection.BrokenPipeError; -import ai.timefold.jpyinterpreter.types.errors.io.connection.ConnectionAbortedError; -import ai.timefold.jpyinterpreter.types.errors.io.connection.ConnectionError; -import ai.timefold.jpyinterpreter.types.errors.io.connection.ConnectionRefusedError; -import ai.timefold.jpyinterpreter.types.errors.io.connection.ConnectionResetError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.errors.lookup.KeyError; -import ai.timefold.jpyinterpreter.types.errors.lookup.LookupError; -import ai.timefold.jpyinterpreter.types.errors.syntax.IndentationError; -import ai.timefold.jpyinterpreter.types.errors.syntax.SyntaxError; -import ai.timefold.jpyinterpreter.types.errors.syntax.TabError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeDecodeError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeEncodeError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeTranslateError; -import ai.timefold.jpyinterpreter.types.errors.warning.BytesWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.DeprecationWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.EncodingWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.FutureWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.ImportWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.PendingDeprecationWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.ResourceWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.RuntimeWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.SyntaxWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.UnicodeWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.UserWarning; -import ai.timefold.jpyinterpreter.types.errors.warning.Warning; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class GlobalBuiltins { - private final static StackWalker stackWalker = getStackWalkerInstance(); - private final static Map builtinConstantMap = new HashMap<>(); - - static { - loadBuiltinConstants(); - } - - private static StackWalker getStackWalkerInstance() { - return StackWalker.getInstance(RETAIN_CLASS_REFERENCE); - } - - public static void addBuiltinType(PythonLikeType type) { - addBuiltinConstant(type.getTypeName(), type); - } - - public static void addBuiltinConstant(String builtinName, PythonLikeObject value) { - builtinConstantMap.put(builtinName, value); - } - - public static List getBuiltinTypes() { - List out = new ArrayList<>(); - for (PythonLikeObject constant : builtinConstantMap.values()) { - if (constant instanceof PythonLikeType) { - out.add((PythonLikeType) constant); - } - } - return out; - } - - public static void loadBuiltinConstants() { - // Constants - addBuiltinConstant("None", PythonNone.INSTANCE); - addBuiltinConstant("Ellipsis", Ellipsis.INSTANCE); - addBuiltinConstant("NotImplemented", NotImplemented.INSTANCE); - addBuiltinConstant("True", PythonBoolean.TRUE); - addBuiltinConstant("False", PythonBoolean.FALSE); - - // Types - addBuiltinType(BOOLEAN_TYPE); - addBuiltinType(INT_TYPE); - addBuiltinType(FLOAT_TYPE); - addBuiltinType(COMPLEX_TYPE); - - addBuiltinType(TUPLE_TYPE); - addBuiltinType(LIST_TYPE); - addBuiltinType(SET_TYPE); - addBuiltinType(FROZEN_SET_TYPE); - addBuiltinType(DICT_TYPE); - - addBuiltinType(STRING_TYPE); - addBuiltinType(BYTES_TYPE); - addBuiltinType(BYTE_ARRAY_TYPE); - - addBuiltinType(NONE_TYPE); - addBuiltinType(RANGE_TYPE); - addBuiltinType(SLICE_TYPE); - - // Exceptions - addBuiltinType(ArithmeticError.ARITHMETIC_ERROR_TYPE); - addBuiltinType(FloatingPointError.FLOATING_POINT_ERROR_TYPE); - addBuiltinType(OverflowError.OVERFLOW_ERROR_TYPE); - addBuiltinType(ZeroDivisionError.ZERO_DIVISION_ERROR_TYPE); - - addBuiltinType(BrokenPipeError.BROKEN_PIPE_ERROR_TYPE); - addBuiltinType(ConnectionAbortedError.CONNECTION_ABORTED_ERROR_TYPE); - addBuiltinType(ConnectionError.CONNECTION_ERROR_TYPE); - addBuiltinType(ConnectionRefusedError.CONNECTION_REFUSED_ERROR_TYPE); - addBuiltinType(ConnectionResetError.CONNECTION_RESET_ERROR_TYPE); - - addBuiltinType(BlockingIOError.BLOCKING_IO_ERROR_TYPE); - addBuiltinType(ChildProcessError.CHILD_PROCESS_ERROR_TYPE); - addBuiltinType(EOFError.EOF_ERROR_TYPE); - addBuiltinType(FileExistsError.FILE_EXISTS_ERROR_TYPE); - addBuiltinType(FileNotFoundError.FILE_NOT_FOUND_ERROR_TYPE); - addBuiltinType(InterruptedError.INTERRUPTED_ERROR_TYPE); - addBuiltinType(IsADirectoryError.IS_A_DIRECTORY_ERROR_TYPE); - addBuiltinType(KeyboardInterrupt.KEYBOARD_INTERRUPT_TYPE); - addBuiltinType(MemoryError.MEMORY_ERROR_TYPE); - addBuiltinType(NotADirectoryError.NOT_A_DIRECTORY_ERROR_TYPE); - addBuiltinType(OSError.OS_ERROR_TYPE); - addBuiltinType(PermissionError.PERMISSION_ERROR_TYPE); - addBuiltinType(ProcessLookupError.PROCESS_LOOKUP_ERROR_TYPE); - addBuiltinType(SystemError.SYSTEM_ERROR_TYPE); - addBuiltinType(SystemExit.SYSTEM_EXIT_TYPE); - addBuiltinType(TimeoutError.TIMEOUT_ERROR_TYPE); - - addBuiltinType(IndexError.INDEX_ERROR_TYPE); - addBuiltinType(KeyError.KEY_ERROR_TYPE); - addBuiltinType(LookupError.LOOKUP_ERROR_TYPE); - - addBuiltinType(IndentationError.INDENTATION_ERROR_TYPE); - addBuiltinType(SyntaxError.SYNTAX_ERROR_TYPE); - addBuiltinType(TabError.TAB_ERROR_TYPE); - - addBuiltinType(UnicodeDecodeError.UNICODE_DECODE_ERROR_TYPE); - addBuiltinType(UnicodeEncodeError.UNICODE_ENCODE_ERROR_TYPE); - addBuiltinType(UnicodeError.UNICODE_ERROR_TYPE); - addBuiltinType(UnicodeTranslateError.UNICODE_TRANSLATE_ERROR_TYPE); - - addBuiltinType(BytesWarning.BYTES_WARNING_TYPE); - addBuiltinType(DeprecationWarning.DEPRECATION_WARNING_TYPE); - addBuiltinType(EncodingWarning.ENCODING_WARNING_TYPE); - addBuiltinType(FutureWarning.FUTURE_WARNING_TYPE); - addBuiltinType(ImportWarning.IMPORT_WARNING_TYPE); - addBuiltinType(PendingDeprecationWarning.PENDING_DEPRECATION_WARNING_TYPE); - addBuiltinType(ResourceWarning.RESOURCE_WARNING_TYPE); - addBuiltinType(RuntimeWarning.RUNTIME_WARNING_TYPE); - addBuiltinType(SyntaxWarning.SYNTAX_WARNING_TYPE); - addBuiltinType(UnicodeWarning.UNICODE_WARNING_TYPE); - addBuiltinType(UserWarning.USER_WARNING_TYPE); - addBuiltinType(Warning.WARNING_TYPE); - - addBuiltinType(AttributeError.ATTRIBUTE_ERROR_TYPE); - addBuiltinType(BufferError.BUFFER_ERROR_TYPE); - addBuiltinType(GeneratorExit.GENERATOR_EXIT_TYPE); - addBuiltinType(ImportError.IMPORT_ERROR_TYPE); - addBuiltinType(ModuleNotFoundError.MODULE_NOT_FOUND_ERROR_TYPE); - addBuiltinType(NameError.NAME_ERROR_TYPE); - addBuiltinType(NotImplementedError.NOT_IMPLEMENTED_ERROR_TYPE); - addBuiltinType(PythonAssertionError.ASSERTION_ERROR_TYPE); - addBuiltinType(PythonBaseException.BASE_EXCEPTION_TYPE); - addBuiltinType(PythonException.EXCEPTION_TYPE); - addBuiltinType(RecursionError.RECURSION_ERROR_TYPE); - addBuiltinType(ReferenceError.REFERENCE_ERROR_TYPE); - addBuiltinType(RuntimeError.RUNTIME_ERROR_TYPE); - addBuiltinType(StopAsyncIteration.STOP_ASYNC_ITERATION_TYPE); - addBuiltinType(StopIteration.STOP_ITERATION_TYPE); - addBuiltinType(UnboundLocalError.UNBOUND_LOCAL_ERROR_TYPE); - addBuiltinType(TypeError.TYPE_ERROR_TYPE); - addBuiltinType(ValueError.VALUE_ERROR_TYPE); - - PythonOverloadImplementor.createDeferredDispatches(); - } - - public static PythonLikeObject lookup(PythonInterpreter interpreter, String builtinName) { - switch (builtinName) { - case "abs": - return UnaryDunderBuiltin.ABS; - case "all": - return ((PythonLikeFunction) GlobalBuiltins::all); - case "any": - return ((PythonLikeFunction) GlobalBuiltins::any); - case "ascii": - return ((PythonLikeFunction) GlobalBuiltins::ascii); - case "bin": - return ((PythonLikeFunction) GlobalBuiltins::bin); - case "bool": - return BOOLEAN_TYPE; - case "bytes": - return BYTES_TYPE; - case "bytearray": - return BYTE_ARRAY_TYPE; - case "callable": - return ((PythonLikeFunction) GlobalBuiltins::callable); - case "chr": - return ((PythonLikeFunction) GlobalBuiltins::chr); - case "delattr": - return ((PythonLikeFunction) GlobalBuiltins::delattr); - case "divmod": - return ((PythonLikeFunction) GlobalBuiltins::divmod); - case "dict": - return DICT_TYPE; - case "enumerate": - return ((PythonLikeFunction) GlobalBuiltins::enumerate); - case "filter": - return ((PythonLikeFunction) GlobalBuiltins::filter); - case "float": - return FLOAT_TYPE; - case "format": - return ((PythonLikeFunction) GlobalBuiltins::format); - case "frozenset": - return FROZEN_SET_TYPE; - case "getattr": - return ((PythonLikeFunction) GlobalBuiltins::getattr); - case "globals": - return ((PythonLikeFunction) GlobalBuiltins::globals); - case "hasattr": - return ((PythonLikeFunction) GlobalBuiltins::hasattr); - case "hash": - return UnaryDunderBuiltin.HASH; - case "hex": - return ((PythonLikeFunction) GlobalBuiltins::hex); - case "id": - return ((PythonLikeFunction) GlobalBuiltins::id); - case "input": - return GlobalBuiltins.input(interpreter); - case "int": - return INT_TYPE; - case "isinstance": - return ((PythonLikeFunction) GlobalBuiltins::isinstance); - case "issubclass": - return ((PythonLikeFunction) GlobalBuiltins::issubclass); - case "iter": - return UnaryDunderBuiltin.ITERATOR; // TODO: Iterator with sentinel value - case "len": - return UnaryDunderBuiltin.LENGTH; - case "list": - return LIST_TYPE; - case "locals": - return ((PythonLikeFunction) GlobalBuiltins::locals); - case "map": - return ((PythonLikeFunction) GlobalBuiltins::map); - case "min": - return ((PythonLikeFunction) GlobalBuiltins::min); - case "max": - return ((PythonLikeFunction) GlobalBuiltins::max); - case "next": - return UnaryDunderBuiltin.NEXT; - case "object": - return BASE_TYPE; - case "oct": - return ((PythonLikeFunction) GlobalBuiltins::oct); - case "ord": - return ((PythonLikeFunction) GlobalBuiltins::ord); - case "pow": - return ((PythonLikeFunction) GlobalBuiltins::pow); - case "print": - return GlobalBuiltins.print(interpreter); - case "range": - return RANGE_TYPE; - case "repr": - return UnaryDunderBuiltin.REPRESENTATION; - case "reversed": - return ((PythonLikeFunction) GlobalBuiltins::reversed); - case "round": - return ((PythonLikeFunction) GlobalBuiltins::round); - case "set": - return SET_TYPE; - case "setattr": - return ((PythonLikeFunction) GlobalBuiltins::setattr); - case "slice": - return PythonSlice.SLICE_TYPE; - case "sorted": - return ((PythonLikeFunction) GlobalBuiltins::sorted); - case "str": - return STRING_TYPE; - case "sum": - return ((PythonLikeFunction) GlobalBuiltins::sum); - case "super": - return ((PythonLikeFunction) GlobalBuiltins::superOfCaller); - case "tuple": - return TUPLE_TYPE; - case "type": - return TYPE_TYPE; - case "vars": - return ((PythonLikeFunction) GlobalBuiltins::vars); - case "zip": - return ((PythonLikeFunction) GlobalBuiltins::zip); - case "__import__": - return GlobalBuiltins.importFunction(interpreter); - default: - return builtinConstantMap.get(builtinName); - } - } - - public static PythonLikeObject lookupOrError(PythonInterpreter interpreter, String builtinName) { - PythonLikeObject out = lookup(interpreter, builtinName); - if (out == null) { - throw new IllegalArgumentException(builtinName + " does not exist in global scope"); - } - return out; - } - - public static PythonBoolean all(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - Iterator iterator; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(positionalArgs.get(0)); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("iterable"))) { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR - .invoke(keywordArgs.get(PythonString.valueOf("iterable"))); - } else { - throw new ValueError("all expects 1 argument, got " + positionalArgs.size()); - } - - while (iterator.hasNext()) { - PythonLikeObject element = iterator.next(); - if (!PythonBoolean.isTruthful(element)) { - return PythonBoolean.FALSE; - } - } - - return PythonBoolean.TRUE; - } - - public static PythonBoolean any(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - Iterator iterator; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(positionalArgs.get(0)); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("iterable"))) { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR - .invoke(keywordArgs.get(PythonString.valueOf("iterable"))); - } else { - throw new ValueError("any expects 1 argument, got " + positionalArgs.size()); - } - - while (iterator.hasNext()) { - PythonLikeObject element = iterator.next(); - if (PythonBoolean.isTruthful(element)) { - return PythonBoolean.TRUE; - } - } - - return PythonBoolean.FALSE; - } - - public static PythonString ascii(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("object"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - } else { - throw new ValueError("ascii expects 1 argument, got " + positionalArgs.size()); - } - - PythonString reprString = (PythonString) UnaryDunderBuiltin.REPRESENTATION.invoke(object); - String asciiString = reprString.value.codePoints().flatMap((character) -> { - if (character < 128) { - return IntStream.of(character); - } else { - // \Uxxxxxxxx - IntStream.Builder builder = IntStream.builder().add('\\').add('U'); - String hexString = Integer.toHexString(character); - for (int i = 8; i > hexString.length(); i--) { - builder.add('0'); - } - hexString.codePoints().forEach(builder); - return builder.build(); - } - }).collect(StringBuilder::new, - StringBuilder::appendCodePoint, - StringBuilder::append).toString(); - - return PythonString.valueOf(asciiString); - } - - public static PythonString bin(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("x"))) { - object = keywordArgs.get(PythonString.valueOf("x")); - } else { - throw new ValueError("bin expects 1 argument, got " + positionalArgs.size()); - } - - PythonInteger integer; - - if (object instanceof PythonInteger) { - integer = (PythonInteger) object; - } else { - integer = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(object); - } - - String binaryString = integer.value.toString(2); - - if (binaryString.startsWith("-")) { - return PythonString.valueOf("-0b" + binaryString.substring(1)); - } else { - return PythonString.valueOf("0b" + binaryString); - } - } - - public static PythonBoolean callable(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("object"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - } else { - throw new ValueError("callable expects 1 argument, got " + positionalArgs.size()); - } - - return PythonBoolean.valueOf(object instanceof PythonLikeFunction); - } - - public static PythonString chr(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("i"))) { - object = keywordArgs.get(PythonString.valueOf("i")); - } else { - throw new ValueError("chr expects 1 argument, got " + positionalArgs.size()); - } - - PythonInteger integer = (PythonInteger) object; - - if (integer.value.compareTo(BigInteger.valueOf(0x10FFFF)) > 0 || integer.value.compareTo(BigInteger.ZERO) < 0) { - throw new ValueError("Integer (" + integer + ") outside valid range for chr (0 through 1,114,111)"); - } - - return PythonString.valueOf(Character.toString(integer.value.intValueExact())); - } - - public static PythonNone delattr(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - PythonString name; - - if (positionalArgs.size() == 2) { - object = positionalArgs.get(0); - name = (PythonString) positionalArgs.get(1); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("name"))) { - object = positionalArgs.get(0); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("object")) - && keywordArgs.containsKey(PythonString.valueOf("name"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - } else { - throw new ValueError("delattr expects 2 argument, got " + positionalArgs.size()); - } - - object.$deleteAttribute(name.value); - - return PythonNone.INSTANCE; - } - - public static PythonLikeObject divmod(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject left; - PythonLikeObject right; - - if (positionalArgs.size() == 2) { - left = positionalArgs.get(0); - right = positionalArgs.get(1); - } else { - throw new TypeError("divmod() expects 2 positional arguments"); - } - - PythonLikeObject maybeDivmod = left.$getType().$getAttributeOrNull("__divmod__"); - if (maybeDivmod != null) { - PythonLikeObject result = ((PythonLikeFunction) maybeDivmod).$call(List.of(left, right), Map.of(), null); - if (result != NotImplemented.INSTANCE) { - return result; - } - maybeDivmod = right.$getType().$getAttributeOrNull("__rdivmod__"); - if (maybeDivmod != null) { - result = ((PythonLikeFunction) maybeDivmod).$call(List.of(right, left), Map.of(), null); - if (result != NotImplemented.INSTANCE) { - return result; - } else { - PythonLikeObject maybeDiv = left.$getType().$getAttributeOrNull("__floordiv__"); - PythonLikeObject maybeMod = left.$getType().$getAttributeOrNull("__mod__"); - - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = - ((PythonLikeFunction) maybeDiv).$call(List.of(left, right), Map.of(), null); - PythonLikeObject modResult = - ((PythonLikeFunction) maybeMod).$call(List.of(left, right), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - divResult = ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - modResult = ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = - ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - PythonLikeObject modResult = - ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } - } - } else { - maybeDivmod = right.$getType().$getAttributeOrNull("__rdivmod__"); - if (maybeDivmod != null) { - PythonLikeObject result = ((PythonLikeFunction) maybeDivmod).$call(List.of(right, left), Map.of(), null); - if (result != NotImplemented.INSTANCE) { - return result; - } else { - PythonLikeObject maybeDiv = left.$getType().$getAttributeOrNull("__floordiv__"); - PythonLikeObject maybeMod = left.$getType().$getAttributeOrNull("__mod__"); - - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = - ((PythonLikeFunction) maybeDiv).$call(List.of(left, right), Map.of(), null); - PythonLikeObject modResult = - ((PythonLikeFunction) maybeMod).$call(List.of(left, right), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - divResult = ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - modResult = ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = - ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - PythonLikeObject modResult = - ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } - } else { - PythonLikeObject maybeDiv = left.$getType().$getAttributeOrNull("__floordiv__"); - PythonLikeObject maybeMod = left.$getType().$getAttributeOrNull("__mod__"); - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = ((PythonLikeFunction) maybeDiv).$call(List.of(left, right), Map.of(), null); - PythonLikeObject modResult = ((PythonLikeFunction) maybeMod).$call(List.of(left, right), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - divResult = ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - modResult = ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } else { - maybeDiv = right.$getType().$getAttributeOrNull("__rfloordiv__"); - maybeMod = right.$getType().$getAttributeOrNull("__rmod__"); - - if (maybeDiv != null && maybeMod != null) { - PythonLikeObject divResult = - ((PythonLikeFunction) maybeDiv).$call(List.of(right, left), Map.of(), null); - PythonLikeObject modResult = - ((PythonLikeFunction) maybeMod).$call(List.of(right, left), Map.of(), null); - if (divResult != NotImplemented.INSTANCE && modResult != NotImplemented.INSTANCE) { - return PythonLikeTuple.fromItems(divResult, modResult); - } else { - throw new TypeError( - "Unsupported operands for divmod: " + left.$getType() + ", " + right.$getType()); - } - } - } - } - } - - return PythonNone.INSTANCE; - } - - public static PythonLikeObject enumerate(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject iterable; - PythonLikeObject start = PythonInteger.valueOf(0); - - if (positionalArgs.size() == 2) { - iterable = positionalArgs.get(0); - start = positionalArgs.get(1); - } else if (positionalArgs.size() == 1) { - iterable = positionalArgs.get(0); - if (keywordArgs.containsKey(PythonString.valueOf("start"))) { - start = keywordArgs.get(PythonString.valueOf("start")); - } - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("iterable"))) { - iterable = keywordArgs.get(PythonString.valueOf("iterable")); - if (keywordArgs.containsKey(PythonString.valueOf("start"))) { - start = keywordArgs.get(PythonString.valueOf("start")); - } - } else { - throw new ValueError("enumerate expects 1 or 2 argument, got " + positionalArgs.size()); - } - - final PythonLikeObject iterator = UnaryDunderBuiltin.ITERATOR.invoke(iterable); - final AtomicReference currentValue = new AtomicReference(null); - final AtomicReference currentIndex = new AtomicReference(start); - final AtomicBoolean shouldCallNext = new AtomicBoolean(true); - - return new DelegatePythonIterator(new Iterator() { - @Override - public boolean hasNext() { - if (shouldCallNext.get()) { - try { - currentValue.set(UnaryDunderBuiltin.NEXT.invoke(iterator)); - } catch (StopIteration e) { - currentValue.set(null); - shouldCallNext.set(false); - return false; - } - shouldCallNext.set(false); - return true; - } else { - // we already called next - return currentValue.get() != null; - } - } - - @Override - public PythonLikeObject next() { - PythonLikeObject value; - if (currentValue.get() != null) { - shouldCallNext.set(true); - value = currentValue.get(); - currentValue.set(null); - } else { - value = UnaryDunderBuiltin.NEXT.invoke(iterator); - shouldCallNext.set(true); - } - PythonLikeObject index = currentIndex.get(); - currentIndex.set(BinaryDunderBuiltin.ADD.invoke(index, PythonInteger.valueOf(1))); - return PythonLikeTuple.fromItems(index, value); - } - }); - } - - public static DelegatePythonIterator filter(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject function; - PythonLikeObject iterable; - - if (positionalArgs.size() == 2 && keywordArgs.isEmpty()) { - function = positionalArgs.get(0); - iterable = positionalArgs.get(1); - } else if (positionalArgs.size() == 1) { - function = positionalArgs.get(0); - iterable = keywordArgs.get(PythonString.valueOf("iterable")); - if (iterable == null) { - throw new ValueError("iterable is None"); - } - } else if (positionalArgs.size() == 0) { - function = keywordArgs.get(PythonString.valueOf("function")); - iterable = keywordArgs.get(PythonString.valueOf("iterable")); - if (iterable == null) { - throw new ValueError("iterable is None"); - } - - if (function == null) { - function = PythonNone.INSTANCE; - } - } else { - throw new ValueError("filter expects 2 argument, got " + positionalArgs.size()); - } - - Iterator iterator; - if (iterable instanceof Collection) { - iterator = ((Collection) iterable).iterator(); - } else if (iterable instanceof Iterator) { - iterator = (Iterator) iterable; - } else { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - } - - PythonLikeFunction predicate; - - if (function == PythonNone.INSTANCE) { - predicate = (pos, keywords, callerInstance) -> pos.get(0); - } else { - predicate = (PythonLikeFunction) function; - } - - return new DelegatePythonIterator(StreamSupport.stream( - Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), - false) - .filter(element -> PythonBoolean - .isTruthful(predicate.$call(List.of((PythonLikeObject) element), Map.of(), null))) - .iterator()); - } - - public static PythonLikeObject format(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject toFormat; - PythonLikeObject formatSpec = PythonString.valueOf(""); - - if (positionalArgs.size() == 2 && keywordArgs.isEmpty()) { - toFormat = positionalArgs.get(0); - formatSpec = positionalArgs.get(1); - } else if (positionalArgs.size() == 1) { - toFormat = positionalArgs.get(0); - if (keywordArgs.containsKey(PythonString.valueOf("format_spec"))) { - formatSpec = keywordArgs.get(PythonString.valueOf("format_spec")); - } - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("value"))) { - toFormat = keywordArgs.get(PythonString.valueOf("value")); - if (keywordArgs.containsKey(PythonString.valueOf("format_spec"))) { - formatSpec = keywordArgs.get(PythonString.valueOf("format_spec")); - } - } else { - throw new ValueError("format expects 1 or 2 arguments, got " + positionalArgs.size()); - } - - return toFormat.$method$__format__((PythonString) formatSpec); - } - - public static PythonLikeObject getattr(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - PythonString name; - PythonLikeObject defaultValue = null; - - if (positionalArgs.size() == 3) { - object = positionalArgs.get(0); - name = (PythonString) positionalArgs.get(1); - defaultValue = positionalArgs.get(2); - } else if (positionalArgs.size() == 2) { - object = positionalArgs.get(0); - name = (PythonString) positionalArgs.get(1); - defaultValue = keywordArgs.get(PythonString.valueOf("default")); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("name"))) { - object = positionalArgs.get(0); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - defaultValue = keywordArgs.get(PythonString.valueOf("default")); - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("object")) - && keywordArgs.containsKey(PythonString.valueOf("name"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - defaultValue = keywordArgs.get(PythonString.valueOf("default")); - } else { - throw new ValueError("getattr expects 2 or 3 arguments, got " + positionalArgs.size()); - } - - PythonLikeFunction getAttribute = (PythonLikeFunction) object.$getType().$getAttributeOrError("__getattribute__"); - - try { - return getAttribute.$call(List.of(object, name), Map.of(), null); - } catch (AttributeError attributeError) { - if (defaultValue != null) { - return defaultValue; - } else { - throw attributeError; - } - } - } - - public static PythonLikeDict globals(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - if (!positionalArgs.isEmpty() && keywordArgs.isEmpty()) { - throw new ValueError("globals expects 0 arguments, got " + positionalArgs.size()); - } - Class callerClass = stackWalker.getCallerClass(); - - try { - Map globalsMap = - (Map) callerClass.getField(PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME).get(null); - return PythonLikeDict.mirror(globalsMap); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException("Caller (" + callerClass + ") is not a generated class", e); - } - } - - public static PythonBoolean hasattr(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - try { - getattr(positionalArgs, keywordArgs, instance); - return PythonBoolean.TRUE; - } catch (AttributeError error) { - return PythonBoolean.FALSE; - } - } - - public static PythonString hex(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("x"))) { - object = keywordArgs.get(PythonString.valueOf("x")); - } else { - throw new ValueError("hex expects 1 argument, got " + positionalArgs.size()); - } - - PythonInteger integer; - - if (object instanceof PythonInteger) { - integer = (PythonInteger) object; - } else { - integer = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(object); - } - - String hexString = integer.value.toString(16); - - if (hexString.startsWith("-")) { - return PythonString.valueOf("-0x" + hexString.substring(1)); - } else { - return PythonString.valueOf("0x" + hexString); - } - } - - public static PythonInteger id(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("object"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - } else { - throw new ValueError("id expects 1 argument, got " + positionalArgs.size()); - } - - if (object instanceof CPythonBackedPythonLikeObject) { - CPythonBackedPythonLikeObject cPythonBackedPythonLikeObject = (CPythonBackedPythonLikeObject) object; - if (cPythonBackedPythonLikeObject.$cpythonId != null) { - return cPythonBackedPythonLikeObject.$cpythonId; - } - } - - return PythonInteger.valueOf(System.identityHashCode(object)); - } - - public static PythonLikeFunction input(PythonInterpreter interpreter) { - return (positionalArguments, namedArguments, callerInstance) -> { - PythonString prompt = null; - if (positionalArguments.size() == 1) { - prompt = (PythonString) positionalArguments.get(0); - } else if (positionalArguments.size() == 0 && namedArguments.containsKey(PythonString.valueOf("prompt"))) { - prompt = (PythonString) namedArguments.get(PythonString.valueOf("prompt")); - } else { - throw new ValueError("input expects 0 or 1 arguments, got " + positionalArguments.size()); - } - - if (prompt != null) { - interpreter.write(prompt.value); - } - - String line = interpreter.readLine(); - - return PythonString.valueOf(line); - }; - } - - public static PythonBoolean isinstance(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - PythonLikeObject classInfo; - - if (positionalArgs.size() == 2) { - object = positionalArgs.get(0); - classInfo = positionalArgs.get(1); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("classinfo"))) { - object = positionalArgs.get(0); - classInfo = keywordArgs.get(PythonString.valueOf("classinfo")); - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("object")) - && keywordArgs.containsKey(PythonString.valueOf("classinfo"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - classInfo = keywordArgs.get(PythonString.valueOf("classinfo")); - } else { - throw new ValueError("isinstance expects 2 arguments, got " + positionalArgs.size()); - } - - if (classInfo instanceof PythonLikeType) { - return PythonBoolean.valueOf(((PythonLikeType) classInfo).isInstance(object)); - } else if (classInfo instanceof List) { - for (PythonLikeObject possibleType : (List) classInfo) { - if (isinstance(List.of(object, possibleType), null, instance).getBooleanValue()) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } else { - throw new ValueError("classInfo (" + classInfo + ") is not a tuple of types or a type"); // TODO: Use TypeError - } - } - - public static PythonBoolean issubclass(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeType type; - PythonLikeObject classInfo; - - if (positionalArgs.size() == 2) { - if (!(positionalArgs.get(0) instanceof PythonLikeType)) { - throw new TypeError("issubclass argument 0 must be a class, not " + positionalArgs.get(0).$getType()); - } - type = (PythonLikeType) positionalArgs.get(0); - classInfo = positionalArgs.get(1); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("classinfo"))) { - if (!(positionalArgs.get(0) instanceof PythonLikeType)) { - throw new TypeError("issubclass argument 0 must be a class, not " + positionalArgs.get(0).$getType()); - } - type = (PythonLikeType) positionalArgs.get(0); - classInfo = keywordArgs.get(PythonString.valueOf("classinfo")); - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("class")) - && keywordArgs.containsKey(PythonString.valueOf("classinfo"))) { - if (!(keywordArgs.get(PythonString.valueOf("class")) instanceof PythonLikeType)) { - throw new TypeError("issubclass argument 0 must be a class, not " + positionalArgs.get(0).$getType()); - } - type = (PythonLikeType) keywordArgs.get(PythonString.valueOf("class")); - classInfo = keywordArgs.get(PythonString.valueOf("classinfo")); - } else { - throw new ValueError("isinstance expects 2 arguments, got " + positionalArgs.size()); - } - - if (classInfo instanceof PythonLikeType) { - return PythonBoolean.valueOf(type.isSubclassOf((PythonLikeType) classInfo)); - } else if (classInfo instanceof List) { - for (PythonLikeObject possibleType : (List) classInfo) { - if (issubclass(List.of(type, possibleType), null, instance).getBooleanValue()) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } else { - throw new ValueError("classInfo (" + classInfo + ") is not a tuple of types or a type"); // TODO: Use TypeError - } - } - - public static PythonLikeDict locals(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - throw new ValueError("builtin locals() is not supported when executed in Java bytecode"); - } - - public static DelegatePythonIterator map(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeFunction function; - List iterableList = new ArrayList<>(); - - if (positionalArgs.size() >= 2 && keywordArgs.isEmpty()) { - function = (PythonLikeFunction) positionalArgs.get(0); - iterableList = positionalArgs.subList(1, positionalArgs.size()); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("iterable"))) { - function = (PythonLikeFunction) positionalArgs.get(0); - iterableList.add(keywordArgs.get(PythonString.valueOf("iterable"))); - } else if (positionalArgs.size() == 0 - && keywordArgs.containsKey(PythonString.valueOf("function")) - && keywordArgs.containsKey(PythonString.valueOf("iterable"))) { - function = (PythonLikeFunction) keywordArgs.get(PythonString.valueOf("function")); - iterableList.add(keywordArgs.get(PythonString.valueOf("iterable"))); - } else { - throw new ValueError("map expects at least 2 argument, got " + positionalArgs.size()); - } - - final List iteratorList = new ArrayList<>(iterableList.size()); - - for (PythonLikeObject iterable : iterableList) { - Iterator iterator; - if (iterable instanceof Collection) { - iterator = ((Collection) iterable).iterator(); - } else if (iterable instanceof Iterator) { - iterator = (Iterator) iterable; - } else { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - } - iteratorList.add(iterator); - } - - Iterator> iteratorIterator = new Iterator>() { - @Override - public boolean hasNext() { - return iteratorList.stream().allMatch(Iterator::hasNext); - } - - @Override - public List next() { - List out = new ArrayList<>(iteratorList.size()); - for (Iterator iterator : iteratorList) { - out.add((PythonLikeObject) iterator.next()); - } - return out; - } - }; - - return new DelegatePythonIterator(StreamSupport.stream( - Spliterators.spliteratorUnknownSize(iteratorIterator, Spliterator.ORDERED), - false) - .map(element -> function.$call(element, Map.of(), null)) - .iterator()); - } - - public static PythonLikeObject min(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - if (positionalArgs.isEmpty()) { - PythonLikeObject defaultValue = keywordArgs.get(PythonString.valueOf("default")); - if (!keywordArgs.containsKey(PythonString.valueOf("default"))) { - throw new ValueError("No arguments were passed to min, and no default was provided"); - } - return defaultValue; - } else if (positionalArgs.size() == 1) { - Iterator iterator = (Iterator) ((PythonLikeFunction) (positionalArgs.get(0).$getType() - .$getAttributeOrError("__iter__"))).$call(List.of(positionalArgs.get(0)), - Map.of(), null); - Comparable min = null; - for (Iterator it = iterator; it.hasNext();) { - Comparable item = it.next(); - if (min == null) { - min = item; - } else { - if (item.compareTo(min) < 0) { - min = item; - } - } - } - if (min == null) { - PythonLikeObject defaultValue = keywordArgs.get(PythonString.valueOf("default")); - if (!keywordArgs.containsKey(PythonString.valueOf("default"))) { - throw new ValueError("Iterable is empty, and no default was provided"); - } - return defaultValue; - } else { - return (PythonLikeObject) min; - } - } else { - Comparable min = (Comparable) positionalArgs.get(0); - for (PythonLikeObject item : positionalArgs) { - Comparable comparableItem = (Comparable) item; - if (comparableItem.compareTo(min) < 0) { - min = comparableItem; - } - } - return (PythonLikeObject) min; - } - } - - public static PythonLikeObject max(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - if (positionalArgs.isEmpty()) { - PythonLikeObject defaultValue = keywordArgs.get(PythonString.valueOf("default")); - if (!keywordArgs.containsKey(PythonString.valueOf("default"))) { - throw new ValueError("No arguments were passed to max, and no default was provided"); - } - return defaultValue; - } else if (positionalArgs.size() == 1) { - Iterator iterator = (Iterator) ((PythonLikeFunction) (positionalArgs.get(0).$getType() - .$getAttributeOrError("__iter__"))).$call(List.of(positionalArgs.get(0)), - Map.of(), null); - Comparable max = null; - for (Iterator it = iterator; it.hasNext();) { - Comparable item = it.next(); - if (max == null) { - max = item; - } else { - if (item.compareTo(max) > 0) { - max = item; - } - } - } - if (max == null) { - PythonLikeObject defaultValue = keywordArgs.get(PythonString.valueOf("default")); - if (!keywordArgs.containsKey(PythonString.valueOf("default"))) { - throw new ValueError("Iterable is empty, and no default was provided"); - } - return defaultValue; - } else { - return (PythonLikeObject) max; - } - } else { - Comparable max = (Comparable) positionalArgs.get(0); - for (PythonLikeObject item : positionalArgs) { - Comparable comparableItem = (Comparable) item; - if (comparableItem.compareTo(max) > 0) { - max = comparableItem; - } - } - return (PythonLikeObject) max; - } - } - - public static PythonString oct(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - object = positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("x"))) { - object = keywordArgs.get(PythonString.valueOf("x")); - } else { - throw new ValueError("oct expects 1 argument, got " + positionalArgs.size()); - } - - PythonInteger integer; - - if (object instanceof PythonInteger) { - integer = (PythonInteger) object; - } else { - integer = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(object); - } - - String octString = integer.value.toString(8); - - if (octString.startsWith("-")) { - return PythonString.valueOf("-0o" + octString.substring(1)); - } else { - return PythonString.valueOf("0o" + octString); - } - } - - public static PythonInteger ord(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonString character; - - if (positionalArgs.size() == 1 && keywordArgs.isEmpty()) { - character = (PythonString) positionalArgs.get(0); - } else if (positionalArgs.isEmpty() && keywordArgs.size() == 1 - && keywordArgs.containsKey(PythonString.valueOf("c"))) { - character = (PythonString) keywordArgs.get(PythonString.valueOf("c")); - } else { - throw new ValueError("ord expects 1 argument, got " + positionalArgs.size()); - } - - if (character.length() != 1) { - throw new ValueError("String \"" + character + "\" does not represent a single character"); - } - - return PythonInteger.valueOf(character.value.charAt(0)); - } - - public static PythonLikeObject pow(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject base; - PythonLikeObject exp; - PythonLikeObject mod = null; - - if (positionalArgs.size() == 3 && keywordArgs.isEmpty()) { - base = positionalArgs.get(0); - exp = positionalArgs.get(1); - mod = positionalArgs.get(2); - } else if (positionalArgs.size() == 2) { - base = positionalArgs.get(0); - exp = positionalArgs.get(1); - mod = keywordArgs.get(PythonString.valueOf("mod")); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("exp"))) { - base = positionalArgs.get(0); - exp = keywordArgs.get(PythonString.valueOf("exp")); - mod = keywordArgs.get(PythonString.valueOf("mod")); - } else if (positionalArgs.isEmpty() && keywordArgs.containsKey(PythonString.valueOf("base")) - && keywordArgs.containsKey(PythonString.valueOf("exp"))) { - base = keywordArgs.get(PythonString.valueOf("base")); - exp = keywordArgs.get(PythonString.valueOf("exp")); - mod = keywordArgs.get(PythonString.valueOf("mod")); - } else { - throw new ValueError("pow expects 2 or 3 arguments, got " + positionalArgs.size()); - } - - if (mod == null) { - return BinaryDunderBuiltin.POWER.invoke(base, exp); - } else { - return TernaryDunderBuiltin.POWER.invoke(base, exp, mod); - } - } - - public static PythonLikeFunction print(PythonInterpreter interpreter) { - - return (positionalArgs, keywordArgs, callerInstance) -> { - List objects = positionalArgs; - - String sep; - if (!keywordArgs.containsKey(PythonString.valueOf("sep")) - || keywordArgs.get(PythonString.valueOf("sep")) == PythonNone.INSTANCE) { - sep = " "; - } else { - sep = ((PythonString) keywordArgs.get(PythonString.valueOf("sep"))).value; - } - String end; - if (!keywordArgs.containsKey(PythonString.valueOf("end")) - || keywordArgs.get(PythonString.valueOf("end")) == PythonNone.INSTANCE) { - end = "\n"; - } else { - end = ((PythonString) keywordArgs.get(PythonString.valueOf("end"))).value; - } - // TODO: support file keyword arg - - boolean flush; - if (!keywordArgs.containsKey(PythonString.valueOf("flush")) - || keywordArgs.get(PythonString.valueOf("flush")) == PythonNone.INSTANCE) { - flush = false; - } else { - flush = ((PythonBoolean) keywordArgs.get(PythonString.valueOf("flush"))).getBooleanValue(); - } - - for (int i = 0; i < objects.size() - 1; i++) { - interpreter.write(UnaryDunderBuiltin.STR.invoke(objects.get(i)).toString()); - interpreter.write(sep); - } - if (!objects.isEmpty()) { - interpreter.write(UnaryDunderBuiltin.STR.invoke(objects.get(objects.size() - 1)).toString()); - } - interpreter.write(end); - - if (flush) { - System.out.flush(); - } - - return PythonNone.INSTANCE; - }; - }; - - public static PythonLikeObject reversed(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject sequence; - if (positionalArgs.size() != 1) { - throw new ValueError("reversed() expects 1 argument, got " + positionalArgs.size()); - } - - sequence = positionalArgs.get(0); - PythonLikeType sequenceType = sequence.$getType(); - if (sequenceType.$getAttributeOrNull(PythonUnaryOperator.REVERSED.getDunderMethod()) != null) { - return UnaryDunderBuiltin.REVERSED.invoke(sequence); - } - - if (sequenceType.$getAttributeOrNull(PythonUnaryOperator.LENGTH.getDunderMethod()) != null && - sequenceType.$getAttributeOrNull(PythonBinaryOperator.GET_ITEM.getDunderMethod()) != null) { - PythonInteger length = (PythonInteger) UnaryDunderBuiltin.LENGTH.invoke(sequence); - Iterator reversedIterator = new Iterator<>() { - PythonInteger current = length.subtract(PythonInteger.ONE); - - @Override - public boolean hasNext() { - return current.compareTo(PythonInteger.ZERO) >= 0; - } - - @Override - public PythonLikeObject next() { - PythonLikeObject out = BinaryDunderBuiltin.GET_ITEM.invoke(sequence, current); - current = current.subtract(PythonInteger.ONE); - return out; - } - }; - - return new DelegatePythonIterator(reversedIterator); - } - - throw new ValueError(sequenceType + " does not has a __reversed__ method and does not implement the Sequence protocol"); - } - - public static PythonLikeObject round(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - if (!(positionalArgs.size() == 1 || positionalArgs.size() == 2)) { - throw new ValueError("round() expects 1 or 2 arguments, got " + positionalArgs.size()); - } - - PythonLikeObject number = positionalArgs.get(0); - PythonLikeType numberType = number.$getType(); - if (numberType.$getAttributeOrNull("__round__") != null) { - return ((PythonLikeFunction) numberType.$getAttributeOrNull("__round__")).$call(positionalArgs, keywordArgs, null); - } - - throw new ValueError(numberType + " does not has a __round__ method"); - } - - public static PythonLikeObject setattr(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - PythonLikeObject object; - PythonString name; - PythonLikeObject value; - - if (positionalArgs.size() == 3) { - object = positionalArgs.get(0); - name = (PythonString) positionalArgs.get(1); - value = positionalArgs.get(2); - } else if (positionalArgs.size() == 2 && keywordArgs.containsKey(PythonString.valueOf("value"))) { - object = positionalArgs.get(0); - name = (PythonString) positionalArgs.get(1); - value = keywordArgs.get(PythonString.valueOf("value")); - } else if (positionalArgs.size() == 1 && keywordArgs.containsKey(PythonString.valueOf("name")) && - keywordArgs.containsKey(PythonString.valueOf("value"))) { - object = positionalArgs.get(0); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - value = keywordArgs.get(PythonString.valueOf("value")); - } else if (positionalArgs.size() == 0 && keywordArgs.containsKey(PythonString.valueOf("object")) && - keywordArgs.containsKey(PythonString.valueOf("name")) && - keywordArgs.containsKey(PythonString.valueOf("value"))) { - object = keywordArgs.get(PythonString.valueOf("object")); - name = (PythonString) keywordArgs.get(PythonString.valueOf("name")); - value = keywordArgs.get(PythonString.valueOf("value")); - } else { - throw new ValueError("setattr expects 2 or 3 arguments, got " + positionalArgs.size()); - } - - return TernaryDunderBuiltin.SETATTR.invoke(object, name, value); - } - - public static PythonLikeObject sorted(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - - PythonLikeObject iterable = positionalArgs.get(0); - - boolean isReversed = false; - if (keywordArgs.containsKey(PythonString.valueOf("reverse"))) { - isReversed = ((PythonBoolean) keywordArgs.get(PythonString.valueOf("reverse"))).getBooleanValue(); - } - - PythonLikeList out = new PythonLikeList(); - if (iterable instanceof Collection) { - out.addAll((Collection) iterable); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - iterator.forEachRemaining(out::add); - } - - Comparator keyComparator = isReversed ? Comparator.reverseOrder() : Comparator.naturalOrder(); - List decoratedList = null; - - if (keywordArgs.containsKey(PythonString.valueOf("key"))) { - PythonLikeObject key = keywordArgs.get(PythonString.valueOf("key")); - if (key != PythonNone.INSTANCE) { - final PythonLikeFunction keyFunction = (PythonLikeFunction) key; - final Function keyExtractor = item -> keyFunction.$call(List.of((PythonLikeObject) item), Map.of(), null); - decoratedList = new ArrayList<>(out.size()); - for (int i = 0; i < out.size(); i++) { - decoratedList.add( - new KeyTuple((PythonLikeObject) keyExtractor.apply(out.get(i)), i, (PythonLikeObject) out.get(i))); - } - } else { - keyComparator = isReversed ? Comparator.reverseOrder() : Comparator.naturalOrder(); - } - } else { - keyComparator = isReversed ? Comparator.reverseOrder() : Comparator.naturalOrder(); - } - - if (decoratedList != null) { - Collections.sort(decoratedList, keyComparator); - out.clear(); - for (KeyTuple keyTuple : decoratedList) { - out.add(keyTuple.source); - } - } else { - Collections.sort(out, keyComparator); - } - - return out; - } - - public static PythonLikeObject sum(List positionalArgs, Map keywordArgs, - PythonLikeObject instance) { - PythonLikeObject iterable; - PythonLikeObject start; - - if (positionalArgs.size() == 2) { - iterable = positionalArgs.get(0); - start = positionalArgs.get(1); - } else if (positionalArgs.size() == 1) { - iterable = positionalArgs.get(0); - start = keywordArgs.getOrDefault(PythonString.valueOf("start"), PythonInteger.ZERO); - } else if (positionalArgs.size() == 0) { - iterable = keywordArgs.get(PythonString.valueOf("iterable")); - start = keywordArgs.getOrDefault(PythonString.valueOf("start"), PythonInteger.ZERO); - } else { - throw new ValueError("sum() expects 1 or 2 arguments, got " + positionalArgs.size()); - } - - PythonLikeObject current = start; - - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - while (iterator.hasNext()) { - PythonLikeObject item = iterator.next(); - current = BinaryDunderBuiltin.ADD.invoke(current, item); - } - - return current; - } - - public static PythonSuperObject superOfCaller(List positionalArgs, - Map keywordArgs, - PythonLikeObject instance) { - if (positionalArgs.isEmpty()) { - Class callerClass = stackWalker.getCallerClass(); - try { - PythonLikeType pythonClass = (PythonLikeType) callerClass - .getField(PythonBytecodeToJavaBytecodeTranslator.CLASS_CELL_STATIC_FIELD_NAME).get(null); - if (pythonClass == null) { - throw new RuntimeError("super(): no arguments"); - } - - if (instance != null) { - return new PythonSuperObject(pythonClass, instance); - } else { - return new PythonSuperObject(pythonClass); - } - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new RuntimeError("super(): no arguments"); - } - } else if (positionalArgs.size() == 1) { - PythonLikeType pythonClass = (PythonLikeType) positionalArgs.get(0); - return new PythonSuperObject(pythonClass); - } else if (positionalArgs.size() == 2) { - PythonLikeType pythonClass = (PythonLikeType) positionalArgs.get(0); - instance = positionalArgs.get(1); - return new PythonSuperObject(pythonClass, instance); - } else { - throw new TypeError("super() takes 0 to 2 arguments, got " + positionalArgs.size()); - } - } - - public static PythonLikeObject vars(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - if (positionalArgs.isEmpty()) { - throw new ValueError("0-argument version of vars is not supported when executed in Java bytecode"); - } - - return positionalArgs.get(0).$getAttributeOrError("__dict__"); - } - - public static DelegatePythonIterator zip(List positionalArgs, - Map keywordArgs, PythonLikeObject instance) { - List iterableList = positionalArgs; - boolean isStrict = false; - - if (keywordArgs.containsKey(PythonString.valueOf("strict"))) { - isStrict = ((PythonBoolean) keywordArgs.get(PythonString.valueOf("strict"))).getBooleanValue(); - } - - final List iteratorList = new ArrayList<>(iterableList.size()); - - for (PythonLikeObject iterable : iterableList) { - Iterator iterator; - if (iterable instanceof Collection) { - iterator = ((Collection) iterable).iterator(); - } else if (iterable instanceof Iterator) { - iterator = (Iterator) iterable; - } else { - iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - } - iteratorList.add(iterator); - } - - final boolean isStrictFinal = isStrict; - - if (iteratorList.isEmpty()) { - // Return an empty iterator if there are no iterators - return new DelegatePythonIterator(iteratorList.iterator()); - } - - Iterator> iteratorIterator = new Iterator>() { - @Override - public boolean hasNext() { - if (isStrictFinal) { - int firstWithoutNext = -1; - int firstWithNext = -1; - for (int i = 0; i < iteratorList.size(); i++) { - if (iteratorList.get(i).hasNext() && firstWithNext == -1) { - firstWithNext = i; - } else if (!iteratorList.get(i).hasNext() && firstWithoutNext == -1) { - firstWithoutNext = i; - } - - if (firstWithNext != -1 && firstWithoutNext != -1) { - throw new ValueError( - "zip() argument " + firstWithNext + " longer than argument " + firstWithoutNext); - } - } - return firstWithoutNext == -1; - } else { - return iteratorList.stream().allMatch(Iterator::hasNext); - } - } - - @Override - public List next() { - PythonLikeTuple out = new PythonLikeTuple(); - for (Iterator iterator : iteratorList) { - out.add((PythonLikeObject) iterator.next()); - } - return out; - } - }; - - return new DelegatePythonIterator(iteratorIterator); - } - - public static PythonLikeFunction importFunction(PythonInterpreter pythonInterpreter) { - return (positionalArguments, namedArguments, callerInstance) -> { - PythonString name; - PythonLikeDict globals; - PythonLikeDict locals; - PythonLikeTuple fromlist; - PythonInteger level; - - if (positionalArguments.size() == 0) { - name = (PythonString) namedArguments.get(PythonString.valueOf("name")); - if (name == null) { - throw new ValueError("name is required for __import__()"); - } - globals = (PythonLikeDict) namedArguments.getOrDefault(PythonString.valueOf("globals"), new PythonLikeDict()); - locals = (PythonLikeDict) namedArguments.getOrDefault(PythonString.valueOf("locals"), new PythonLikeDict()); - fromlist = - (PythonLikeTuple) namedArguments.getOrDefault(PythonString.valueOf("fromlist"), new PythonLikeTuple()); - level = (PythonInteger) namedArguments.getOrDefault(PythonString.valueOf("level"), PythonInteger.ZERO); - } else if (positionalArguments.size() == 1) { - name = (PythonString) positionalArguments.get(0); - globals = (PythonLikeDict) namedArguments.getOrDefault(PythonString.valueOf("globals"), new PythonLikeDict()); - locals = (PythonLikeDict) namedArguments.getOrDefault(PythonString.valueOf("locals"), new PythonLikeDict()); - fromlist = - (PythonLikeTuple) namedArguments.getOrDefault(PythonString.valueOf("fromlist"), new PythonLikeTuple()); - level = (PythonInteger) namedArguments.getOrDefault(PythonString.valueOf("level"), PythonInteger.ZERO); - } else if (positionalArguments.size() == 2) { - name = (PythonString) positionalArguments.get(0); - globals = (PythonLikeDict) positionalArguments.get(1); - locals = (PythonLikeDict) namedArguments.getOrDefault(PythonString.valueOf("locals"), new PythonLikeDict()); - fromlist = - (PythonLikeTuple) namedArguments.getOrDefault(PythonString.valueOf("fromlist"), new PythonLikeTuple()); - level = (PythonInteger) namedArguments.getOrDefault(PythonString.valueOf("level"), PythonInteger.ZERO); - } else if (positionalArguments.size() == 3) { - name = (PythonString) positionalArguments.get(0); - globals = (PythonLikeDict) positionalArguments.get(1); - locals = (PythonLikeDict) positionalArguments.get(2); - fromlist = - (PythonLikeTuple) namedArguments.getOrDefault(PythonString.valueOf("fromlist"), new PythonLikeTuple()); - level = (PythonInteger) namedArguments.getOrDefault(PythonString.valueOf("level"), PythonInteger.ZERO); - } else if (positionalArguments.size() == 4) { - name = (PythonString) positionalArguments.get(0); - globals = (PythonLikeDict) positionalArguments.get(1); - locals = (PythonLikeDict) positionalArguments.get(2); - fromlist = (PythonLikeTuple) positionalArguments.get(3); - level = (PythonInteger) namedArguments.getOrDefault(PythonString.valueOf("level"), PythonInteger.ZERO); - } else if (positionalArguments.size() == 5) { - name = (PythonString) positionalArguments.get(0); - globals = (PythonLikeDict) positionalArguments.get(1); - locals = (PythonLikeDict) positionalArguments.get(2); - fromlist = (PythonLikeTuple) positionalArguments.get(3); - level = (PythonInteger) positionalArguments.get(4); - } else { - throw new ValueError("__import__ expects 1 to 5 arguments, got " + positionalArguments.size()); - } - - Map stringGlobals = new HashMap<>(); - Map stringLocals = new HashMap<>(); - - for (PythonLikeObject key : globals.keySet()) { - stringGlobals.put(((PythonString) key).value, globals.get(key)); - } - - for (PythonLikeObject key : locals.keySet()) { - stringLocals.put(((PythonString) key).value, globals.get(key)); - } - - return pythonInterpreter.importModule(level, (List) fromlist, stringGlobals, stringLocals, name.value); - }; - } - - private final static class KeyTuple implements Comparable { - final PythonLikeObject key; - final int index; - final PythonLikeObject source; - - public KeyTuple(PythonLikeObject key, int index, PythonLikeObject source) { - this.key = key; - this.index = index; - this.source = source; - } - - @Override - public int compareTo(KeyTuple other) { - PythonBoolean result = (PythonBoolean) BinaryDunderBuiltin.LESS_THAN.invoke(key, other.key); - if (result.getBooleanValue()) { - return -1; - } - - result = (PythonBoolean) BinaryDunderBuiltin.LESS_THAN.invoke(other.key, key); - if (result.getBooleanValue()) { - return 1; - } - - return index - other.index; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - KeyTuple keyTuple = (KeyTuple) o; - return index == keyTuple.index && key.equals(keyTuple.key); - } - - @Override - public int hashCode() { - return Objects.hash(key, index); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/ObjectBuiltinOperations.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/ObjectBuiltinOperations.java deleted file mode 100644 index f22bbe27..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/ObjectBuiltinOperations.java +++ /dev/null @@ -1,4 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -public class ObjectBuiltinOperations { -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/TernaryDunderBuiltin.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/TernaryDunderBuiltin.java deleted file mode 100644 index 385f9e38..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/TernaryDunderBuiltin.java +++ /dev/null @@ -1,45 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class TernaryDunderBuiltin implements PythonLikeFunction { - private final String DUNDER_METHOD_NAME; - - public static final TernaryDunderBuiltin POWER = new TernaryDunderBuiltin("__pow__"); - public static final TernaryDunderBuiltin SETATTR = new TernaryDunderBuiltin(PythonTernaryOperator.SET_ATTRIBUTE); - public static final TernaryDunderBuiltin GET_DESCRIPTOR = new TernaryDunderBuiltin(PythonTernaryOperator.GET); - - public TernaryDunderBuiltin(String dunderMethodName) { - DUNDER_METHOD_NAME = dunderMethodName; - } - - public TernaryDunderBuiltin(PythonTernaryOperator operator) { - DUNDER_METHOD_NAME = operator.getDunderMethod(); - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - if (positionalArguments.size() != 3) { - throw new ValueError("Function " + DUNDER_METHOD_NAME + " expects 3 positional arguments"); - } - - PythonLikeObject object = positionalArguments.get(0); - PythonLikeObject arg1 = positionalArguments.get(1); - PythonLikeObject arg2 = positionalArguments.get(2); - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object, arg1, arg2), Map.of(), null); - } - - public PythonLikeObject invoke(PythonLikeObject object, PythonLikeObject arg1, PythonLikeObject arg2) { - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object, arg1, arg2), Map.of(), null); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/UnaryDunderBuiltin.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/UnaryDunderBuiltin.java deleted file mode 100644 index f592252f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/UnaryDunderBuiltin.java +++ /dev/null @@ -1,50 +0,0 @@ -package ai.timefold.jpyinterpreter.builtins; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class UnaryDunderBuiltin implements PythonLikeFunction { - private final String DUNDER_METHOD_NAME; - - public static final UnaryDunderBuiltin ABS = new UnaryDunderBuiltin(PythonUnaryOperator.ABS); - public static final UnaryDunderBuiltin HASH = new UnaryDunderBuiltin(PythonUnaryOperator.HASH); - public static final UnaryDunderBuiltin INT = new UnaryDunderBuiltin(PythonUnaryOperator.AS_INT); - public static final UnaryDunderBuiltin FLOAT = new UnaryDunderBuiltin(PythonUnaryOperator.AS_FLOAT); - public static final UnaryDunderBuiltin INDEX = new UnaryDunderBuiltin(PythonUnaryOperator.AS_INDEX); - public static final UnaryDunderBuiltin ITERATOR = new UnaryDunderBuiltin(PythonUnaryOperator.ITERATOR); - public static final UnaryDunderBuiltin LENGTH = new UnaryDunderBuiltin(PythonUnaryOperator.LENGTH); - public static final UnaryDunderBuiltin NEXT = new UnaryDunderBuiltin(PythonUnaryOperator.NEXT); - public static final UnaryDunderBuiltin REVERSED = new UnaryDunderBuiltin(PythonUnaryOperator.REVERSED); - public static final UnaryDunderBuiltin REPRESENTATION = new UnaryDunderBuiltin(PythonUnaryOperator.REPRESENTATION); - public static final UnaryDunderBuiltin STR = new UnaryDunderBuiltin(PythonUnaryOperator.AS_STRING); - - public UnaryDunderBuiltin(String dunderMethodName) { - DUNDER_METHOD_NAME = dunderMethodName; - } - - public UnaryDunderBuiltin(PythonUnaryOperator operator) { - DUNDER_METHOD_NAME = operator.getDunderMethod(); - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - if (positionalArguments.size() != 1) { - throw new ValueError("Function " + DUNDER_METHOD_NAME + " expects 1 positional argument"); - } - PythonLikeObject object = positionalArguments.get(0); - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object), Map.of(), null); - } - - public PythonLikeObject invoke(PythonLikeObject object) { - PythonLikeFunction dunderMethod = (PythonLikeFunction) object.$getType().$getAttributeOrError(DUNDER_METHOD_NAME); - return dunderMethod.$call(List.of(object), Map.of(), null); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/BasicBlock.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/BasicBlock.java deleted file mode 100644 index 51633454..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/BasicBlock.java +++ /dev/null @@ -1,65 +0,0 @@ -package ai.timefold.jpyinterpreter.dag; - -import java.util.List; -import java.util.Objects; - -import ai.timefold.jpyinterpreter.opcodes.Opcode; - -public class BasicBlock { - int startAtIndex; - List blockOpcodeList; - - public BasicBlock(int startAtIndex, List blockOpcodeList) { - this.startAtIndex = startAtIndex; - this.blockOpcodeList = blockOpcodeList; - } - - public int getStartInclusive() { - return startAtIndex; - } - - public int getEndExclusive() { - return startAtIndex + blockOpcodeList.size(); - } - - public Opcode getLeader() { - return blockOpcodeList.get(0); - } - - public Opcode getFinalOpcode() { - return blockOpcodeList.get(blockOpcodeList.size() - 1); - } - - public boolean containsIndex(int index) { - return getStartInclusive() <= index && index < getEndExclusive(); - } - - public List getBlockOpcodeList() { - return blockOpcodeList; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BasicBlock that = (BasicBlock) o; - return startAtIndex == that.startAtIndex; - } - - @Override - public int hashCode() { - return Objects.hash(startAtIndex); - } - - @Override - public String toString() { - return "BasicBlock{" + - "startAtIndex=" + startAtIndex + - ", blockOpcodeList=" + blockOpcodeList + - '}'; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/FlowGraph.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/FlowGraph.java deleted file mode 100644 index 57f12a89..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/FlowGraph.java +++ /dev/null @@ -1,322 +0,0 @@ -package ai.timefold.jpyinterpreter.dag; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.ExceptionBlock; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -public class FlowGraph { - BasicBlock initialBlock; - List basicBlockList; - Map> basicBlockToSourcesMap; - - Map> basicBlockToJumpSourcesMap; - - Map opcodeIndexToJumpSourceMap; - - List stackMetadataForOperations; - - private FlowGraph(BasicBlock initialBlock, List basicBlockList, - Map> basicBlockToSourcesMap, - Map> basicBlockToJumpSourcesMap, - Map opcodeIndexToJumpSourceMap) { - this.initialBlock = initialBlock; - this.basicBlockList = basicBlockList; - this.basicBlockToSourcesMap = basicBlockToSourcesMap; - this.basicBlockToJumpSourcesMap = basicBlockToJumpSourcesMap; - this.opcodeIndexToJumpSourceMap = opcodeIndexToJumpSourceMap; - } - - public List getStackMetadataForOperations() { - return stackMetadataForOperations; - } - - @SuppressWarnings("unchecked") - public void visitOperations(Class opcodeClass, BiConsumer visitor) { - for (BasicBlock basicBlock : basicBlockList) { - for (Opcode opcode : basicBlock.getBlockOpcodeList()) { - if (opcodeClass.isAssignableFrom(opcode.getClass())) { - StackMetadata stackMetadata = stackMetadataForOperations.get(opcode.getBytecodeIndex()); - - if (!stackMetadata.isDeadCode()) { - visitor.accept((T) opcode, stackMetadata); - } - } - } - } - } - - public static FlowGraph createFlowGraph(FunctionMetadata functionMetadata, - StackMetadata initialStackMetadata, - List opcodeList) { - List leaderIndexList = new ArrayList<>(); - boolean wasPreviousInstructionGoto = true; // True so first instruction get added as a leader - for (int i = 0; i < opcodeList.size(); i++) { - if (wasPreviousInstructionGoto || opcodeList.get(i).isJumpTarget() || - functionMetadata.pythonCompiledFunction.co_exceptiontable.containsJumpTarget(i)) { - leaderIndexList.add(i); - } - wasPreviousInstructionGoto = opcodeList.get(i).isForcedJump(); - } - - List basicBlockList = new ArrayList<>(leaderIndexList.size()); - Map jumpTargetToBasicBlock = new HashMap<>(); - - for (int i = 0; i < leaderIndexList.size() - 1; i++) { - basicBlockList.add(new BasicBlock(leaderIndexList.get(i), opcodeList.subList(leaderIndexList.get(i), - leaderIndexList.get(i + 1)))); - jumpTargetToBasicBlock.put(leaderIndexList.get(i), basicBlockList.get(i)); - } - - basicBlockList.add(new BasicBlock(leaderIndexList.get(leaderIndexList.size() - 1), - opcodeList.subList(leaderIndexList.get(leaderIndexList.size() - 1), - opcodeList.size()))); - jumpTargetToBasicBlock.put(leaderIndexList.get(leaderIndexList.size() - 1), - basicBlockList.get(leaderIndexList.size() - 1)); - - BasicBlock initialBlock = basicBlockList.get(0); - Map> basicBlockToSourcesMap = new HashMap<>(); - Map> basicBlockToJumpSourcesMap = new HashMap<>(); - Map opcodeIndexToJumpSourceMap = new HashMap<>(); - - for (BasicBlock basicBlock : basicBlockList) { - basicBlockToSourcesMap.put(basicBlock, new ArrayList<>()); - } - - for (BasicBlock basicBlock : basicBlockList) { - for (Opcode opcode : basicBlock.getBlockOpcodeList()) { - for (int branch = 0; branch < opcode.getPossibleNextBytecodeIndexList().size(); branch++) { - int jumpTarget = opcode.getPossibleNextBytecodeIndexList().get(branch); - if (!basicBlock.containsIndex(jumpTarget) || jumpTarget <= opcode.getBytecodeIndex()) { - BasicBlock jumpTargetBlock = jumpTargetToBasicBlock.get(jumpTarget); - JumpSource jumpSource = new JumpSource(basicBlock); - basicBlockToSourcesMap.computeIfAbsent(jumpTargetBlock, key -> new ArrayList<>()).add(basicBlock); - basicBlockToJumpSourcesMap.computeIfAbsent(jumpTargetBlock, key -> new ArrayList<>()).add(jumpSource); - opcodeIndexToJumpSourceMap.put(new IndexBranchPair(opcode.getBytecodeIndex(), branch), jumpSource); - } - } - } - } - - FlowGraph out = new FlowGraph(initialBlock, basicBlockList, basicBlockToSourcesMap, basicBlockToJumpSourcesMap, - opcodeIndexToJumpSourceMap); - out.computeStackMetadataForOperations(functionMetadata, initialStackMetadata); - return out; - } - - private static StackMetadata getExceptionStackMetadata(ExceptionBlock exceptionBlock, - FunctionMetadata functionMetadata, StackMetadata initialStackMetadata, - StackMetadata previousStackMetadata) { - if (previousStackMetadata == StackMetadata.DEAD_CODE) { - previousStackMetadata = initialStackMetadata; - } - - while (previousStackMetadata.getStackSize() < exceptionBlock.getStackDepth()) { - previousStackMetadata = previousStackMetadata.pushTemp(BuiltinTypes.NONE_TYPE); - } - - while (previousStackMetadata.getStackSize() > exceptionBlock.getStackDepth()) { - previousStackMetadata = previousStackMetadata.pop(); - } - - if (exceptionBlock.isPushLastIndex()) { - return previousStackMetadata.pushTemp(BuiltinTypes.INT_TYPE) - .pushTemp(PythonBaseException.BASE_EXCEPTION_TYPE); - } else { - return previousStackMetadata.pushTemp(PythonBaseException.BASE_EXCEPTION_TYPE); - } - } - - private void computeStackMetadataForOperations(FunctionMetadata functionMetadata, - StackMetadata initialStackMetadata) { - Map opcodeIndexToStackMetadata = new HashMap<>(); - opcodeIndexToStackMetadata.put(0, initialStackMetadata); - - for (BasicBlock basicBlock : basicBlockList) { - for (Opcode opcode : basicBlock.getBlockOpcodeList()) { - StackMetadata currentStackMetadata = - opcodeIndexToStackMetadata.computeIfAbsent(opcode.getBytecodeIndex(), k -> StackMetadata.DEAD_CODE); - - List branchList = opcode.getPossibleNextBytecodeIndexList(); - List nextStackMetadataList; - - try { - nextStackMetadataList = currentStackMetadata.isDeadCode() - ? branchList.stream().map(i -> StackMetadata.DEAD_CODE).collect(Collectors.toList()) - : opcode.getStackMetadataAfterInstructionForBranches(functionMetadata, currentStackMetadata); - } catch (Throwable t) { - throw new IllegalStateException("Failed to calculate successor stack metadata for opcode (" + opcode - + ") with prior stack metadata (" - + currentStackMetadata + ").", t); - } - - for (int i = 0; i < branchList.size(); i++) { - IndexBranchPair indexBranchPair = new IndexBranchPair(opcode.getBytecodeIndex(), i); - int nextBytecodeIndex = branchList.get(i); - StackMetadata nextStackMetadata = nextStackMetadataList.get(i); - if (opcodeIndexToJumpSourceMap.containsKey(indexBranchPair)) { - opcodeIndexToJumpSourceMap.get(indexBranchPair).setStackMetadata(nextStackMetadata); - } - try { - opcodeIndexToStackMetadata.merge(nextBytecodeIndex, nextStackMetadata, StackMetadata::unifyWith); - } catch (IllegalArgumentException e) { - throw new IllegalStateException( - "Cannot unify block starting at " + nextBytecodeIndex + ": different stack sizes;\n" - + PythonBytecodeToJavaBytecodeTranslator - .getPythonBytecodeListing(functionMetadata.pythonCompiledFunction), - e); - } - - } - } - } - for (ExceptionBlock exceptionBlock : functionMetadata.pythonCompiledFunction.co_exceptiontable - .getEntries()) { - try { - opcodeIndexToStackMetadata.merge(exceptionBlock.getTargetInstruction(), - getExceptionStackMetadata(exceptionBlock, - functionMetadata, - initialStackMetadata, - opcodeIndexToStackMetadata.getOrDefault(exceptionBlock.getBlockStartInstructionInclusive(), - StackMetadata.DEAD_CODE)), - StackMetadata::unifyWith); - } catch (IllegalArgumentException e) { - throw new IllegalStateException( - "Cannot unify block starting at " + exceptionBlock.getTargetInstruction() + ": different stack sizes;\n" - + PythonBytecodeToJavaBytecodeTranslator - .getPythonBytecodeListing(functionMetadata.pythonCompiledFunction), - e); - } - } - - boolean hasChanged; - do { // Keep unifying until no changes are detected - hasChanged = false; - for (BasicBlock basicBlock : basicBlockList) { - StackMetadata originalMetadata = opcodeIndexToStackMetadata.get(basicBlock.startAtIndex); - StackMetadata newMetadata = originalMetadata; - for (JumpSource jumpSource : basicBlockToJumpSourcesMap.getOrDefault(basicBlock, Collections.emptyList())) { - newMetadata = newMetadata.unifyWith(jumpSource.getStackMetadata()); - } - hasChanged |= !newMetadata.equals(originalMetadata); - opcodeIndexToStackMetadata.put(basicBlock.startAtIndex, newMetadata); - for (Opcode opcode : basicBlock.getBlockOpcodeList()) { - StackMetadata currentStackMetadata = opcodeIndexToStackMetadata.get(opcode.getBytecodeIndex()); - List branchList = opcode.getPossibleNextBytecodeIndexList(); - List nextStackMetadataList; - - try { - nextStackMetadataList = currentStackMetadata.isDeadCode() - ? branchList.stream().map(i -> StackMetadata.DEAD_CODE).collect(Collectors.toList()) - : opcode.getStackMetadataAfterInstructionForBranches(functionMetadata, currentStackMetadata); - } catch (Throwable t) { - throw new IllegalStateException("Failed to calculate successor stack metadata for opcode (" + opcode - + ") with prior stack metadata (" - + currentStackMetadata + ").", t); - } - for (int i = 0; i < branchList.size(); i++) { - IndexBranchPair indexBranchPair = new IndexBranchPair(opcode.getBytecodeIndex(), i); - int nextBytecodeIndex = branchList.get(i); - StackMetadata nextStackMetadata = nextStackMetadataList.get(i); - if (opcodeIndexToJumpSourceMap.containsKey(indexBranchPair)) { - opcodeIndexToJumpSourceMap.get(indexBranchPair).setStackMetadata(nextStackMetadata); - } - try { - StackMetadata originalOpcodeMetadata = opcodeIndexToStackMetadata.get(nextBytecodeIndex); - StackMetadata newOpcodeMetadata = - opcodeIndexToStackMetadata.merge(nextBytecodeIndex, nextStackMetadata, - StackMetadata::unifyWith); - hasChanged |= !newOpcodeMetadata.equals(originalOpcodeMetadata); - } catch (IllegalArgumentException e) { - throw new IllegalStateException( - "Cannot unify branch (" + indexBranchPair.branch + ": to index " + indexBranchPair.index - + ") stack metadata (" + - nextStackMetadata + ") for source opcode (" + opcode + ") with" + - "prior stack metadata (" + opcodeIndexToStackMetadata.get(nextBytecodeIndex) - + "): different stack sizes;\n" - + PythonBytecodeToJavaBytecodeTranslator - .getPythonBytecodeListing(functionMetadata.pythonCompiledFunction), - e); - } - } - } - } - for (ExceptionBlock exceptionBlock : functionMetadata.pythonCompiledFunction.co_exceptiontable - .getEntries()) { - try { - StackMetadata originalOpcodeMetadata = - opcodeIndexToStackMetadata.get(exceptionBlock.getTargetInstruction()); - StackMetadata newOpcodeMetadata = opcodeIndexToStackMetadata.merge(exceptionBlock.getTargetInstruction(), - getExceptionStackMetadata(exceptionBlock, - functionMetadata, - initialStackMetadata, - opcodeIndexToStackMetadata.getOrDefault(exceptionBlock.getBlockStartInstructionInclusive(), - StackMetadata.DEAD_CODE)), - StackMetadata::unifyWith); - hasChanged |= !newOpcodeMetadata.equals(originalOpcodeMetadata); - } catch (IllegalArgumentException e) { - throw new IllegalStateException( - "Cannot unify block starting at " + exceptionBlock.getTargetInstruction() - + ": different stack sizes;\n" - + PythonBytecodeToJavaBytecodeTranslator - .getPythonBytecodeListing(functionMetadata.pythonCompiledFunction), - e); - } - } - } while (hasChanged); - - stackMetadataForOperations = opcodeIndexToStackMetadata - .entrySet() - .stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - } - - private static class IndexBranchPair { - final Integer index; - final Integer branch; - - public IndexBranchPair(Integer index, Integer branch) { - this.index = index; - this.branch = branch; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - IndexBranchPair that = (IndexBranchPair) o; - return index.equals(that.index) && branch.equals(that.branch); - } - - @Override - public int hashCode() { - return Objects.hash(index, branch); - } - - @Override - public String toString() { - return "IndexBranchPair{" + - "index=" + index + - ", branch=" + branch + - '}'; - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/JumpSource.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/JumpSource.java deleted file mode 100644 index 2667044a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/dag/JumpSource.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.dag; - -import ai.timefold.jpyinterpreter.StackMetadata; - -public class JumpSource { - BasicBlock fromBasicBlock; - StackMetadata stackMetadata; - - public JumpSource(BasicBlock fromBasicBlock) { - this.fromBasicBlock = fromBasicBlock; - } - - public StackMetadata getStackMetadata() { - return stackMetadata; - } - - public void setStackMetadata(StackMetadata stackMetadata) { - this.stackMetadata = stackMetadata; - } - - @Override - public String toString() { - return "JumpSource{" + - "fromBasicBlock=" + fromBasicBlock + - ", stackMetadata=" + stackMetadata + - '}'; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementor.java deleted file mode 100644 index e590f2f3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementor.java +++ /dev/null @@ -1,664 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.PythonSlice; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes related to collections (list, tuple, set, dict). - */ -public class CollectionImplementor { - - /** - * TOS is an iterator; perform TOS' = next(TOS). - * If TOS is exhausted (which is indicated when it raises a {@link StopIteration} exception), - * Jump relatively by the instruction argument and pop TOS. Otherwise, - * leave TOS below TOS' and go to the next instruction. - * - * Note: {@link StopIteration} does not fill its stack trace, which make it much more efficient than - * normal exceptions. - */ - public static void iterateIterator(MethodVisitor methodVisitor, int jumpTarget, - StackMetadata stackMetadata, - FunctionMetadata functionMetadata) { - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label catchStartLabel = new Label(); - Label catchEndLabel = new Label(); - Label loopEndLabel = - functionMetadata.bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, - key -> new Label()); - - int[] storedStack = StackManipulationImplementor.storeStack(methodVisitor, stackMetadata); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, catchStartLabel, - Type.getInternalName(StopIteration.class)); - - methodVisitor.visitLabel(tryStartLabel); - - methodVisitor.visitInsn(Opcodes.DUP); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - methodVisitor.visitLabel(tryEndLabel); - methodVisitor.visitJumpInsn(Opcodes.GOTO, catchEndLabel); - - methodVisitor.visitLabel(catchStartLabel); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitJumpInsn(Opcodes.GOTO, loopEndLabel); - methodVisitor.visitLabel(catchEndLabel); - - functionMetadata.bytecodeCounterToCodeArgumenterList - .computeIfAbsent(jumpTarget, key -> new ArrayList<>()) - .add(() -> { - StackManipulationImplementor.restoreStack(methodVisitor, stackMetadata, storedStack); - methodVisitor.visitInsn(Opcodes.POP); - }); - } - - /** - * TOS is an iterable; push {@code toUnpack} elements from it to the stack - * (with first item of the iterable as the new TOS). Raise an exception if it does not - * have exactly {@code toUnpack} elements. - */ - public static void unpackSequence(MethodVisitor methodVisitor, int toUnpack, LocalVariableHelper localVariableHelper) { - // Initialize size and unpacked elements local variables - int sizeLocal = localVariableHelper.newLocal(); - - methodVisitor.visitInsn(Opcodes.ICONST_0); - localVariableHelper.writeTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - - int[] unpackedLocals = new int[toUnpack]; - - for (int i = 0; i < toUnpack; i++) { - unpackedLocals[i] = localVariableHelper.newLocal(); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - localVariableHelper.writeTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - } - - // Get the iterator for the iterable - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.ITERATOR); - - // Surround the unpacking code in a try...finally block - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label finallyStartLabel = new Label(); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, finallyStartLabel, null); - - methodVisitor.visitLabel(tryStartLabel); - - for (int i = 0; i < toUnpack + 1; i++) { - // Call the next method of the iterator - methodVisitor.visitInsn(Opcodes.DUP); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - if (i < toUnpack) { // Store the unpacked value in a local - localVariableHelper.writeTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - } else { // Try to get more elements to see if the iterable contain exactly enough - methodVisitor.visitInsn(Opcodes.POP); - } - // increment size - localVariableHelper.incrementTemp(methodVisitor, sizeLocal); - } - methodVisitor.visitLabel(tryEndLabel); - - methodVisitor.visitLabel(finallyStartLabel); - - Label toFewElements = new Label(); - Label toManyElements = new Label(); - Label exactNumberOfElements = new Label(); - - // Pop off the iterator - methodVisitor.visitInsn(Opcodes.POP); - - // Check if too few - localVariableHelper.readTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - methodVisitor.visitLdcInsn(toUnpack); - methodVisitor.visitJumpInsn(Opcodes.IF_ICMPLT, toFewElements); - - // Check if too many - localVariableHelper.readTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - methodVisitor.visitLdcInsn(toUnpack); - methodVisitor.visitJumpInsn(Opcodes.IF_ICMPGT, toManyElements); - - // Must have exactly enough - methodVisitor.visitJumpInsn(Opcodes.GOTO, exactNumberOfElements); - - // TODO: Throw ValueError instead - methodVisitor.visitLabel(toFewElements); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - - // Build error message string - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn("not enough values to unpack (expected " + toUnpack + ", got "); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - - localVariableHelper.readTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.INT_TYPE), - false); - - methodVisitor.visitLdcInsn(")"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), - "toString", Type.getMethodDescriptor(Type.getType(String.class)), - false); - - // Call the constructor of the Error - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - // And throw it - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(toManyElements); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn("too many values to unpack (expected " + toUnpack + ")"); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(exactNumberOfElements); - for (int i = toUnpack - 1; i >= 0; i--) { - // Unlike all other collection operators, UNPACK_SEQUENCE unpacks the result in reverse order - localVariableHelper.readTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - } - - localVariableHelper.freeLocal(); - for (int i = 0; i < toUnpack; i++) { - localVariableHelper.freeLocal(); - } - } - - /** - * TOS is an iterable; push {@code toUnpack} elements from it to the stack - * (with first item of the iterable as the new TOS). Below the elements in the stack - * is a list containing the remaining elements in the iterable (empty if there are none). - * Raise an exception if it has less than {@code toUnpack} elements. - */ - public static void unpackSequenceWithTail(MethodVisitor methodVisitor, int toUnpack, - LocalVariableHelper localVariableHelper) { - // TODO: Correctly handle when high byte is set - // Initialize size, unpacked elements and tail local variables - int sizeLocal = localVariableHelper.newLocal(); - - methodVisitor.visitInsn(Opcodes.ICONST_0); - localVariableHelper.writeTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - - int[] unpackedLocals = new int[toUnpack]; - - for (int i = 0; i < toUnpack; i++) { - unpackedLocals[i] = localVariableHelper.newLocal(); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - localVariableHelper.writeTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - } - - int tailLocal = localVariableHelper.newLocal(); - CollectionImplementor.buildCollection(PythonLikeList.class, methodVisitor, 0); - localVariableHelper.writeTemp(methodVisitor, Type.getType(Object.class), tailLocal); - - // Get the iterator for the iterable - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.ITERATOR); - - // Surround the unpacking code in a try...finally block - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label finallyStartLabel = new Label(); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, finallyStartLabel, null); - - methodVisitor.visitLabel(tryStartLabel); - - for (int i = 0; i < toUnpack; i++) { - // Call the next method of the iterator - methodVisitor.visitInsn(Opcodes.DUP); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - // Store the unpacked value in a local - localVariableHelper.writeTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - // increment size - localVariableHelper.incrementTemp(methodVisitor, sizeLocal); - } - - // Keep iterating through the iterable until StopIteration is raised to get all of its elements - Label tailLoopStart = new Label(); - methodVisitor.visitLabel(tailLoopStart); - - methodVisitor.visitInsn(Opcodes.DUP); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - localVariableHelper.readTemp(methodVisitor, Type.getType(Object.class), tailLocal); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); // Pop the return value of add - methodVisitor.visitJumpInsn(Opcodes.GOTO, tailLoopStart); - - methodVisitor.visitLabel(tryEndLabel); - - methodVisitor.visitLabel(finallyStartLabel); - - Label exactNumberOfElements = new Label(); - - // Pop off the iterator - methodVisitor.visitInsn(Opcodes.POP); - - // Check if too few - localVariableHelper.readTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - methodVisitor.visitLdcInsn(toUnpack); - methodVisitor.visitJumpInsn(Opcodes.IF_ICMPGE, exactNumberOfElements); - - // TODO: Throw ValueError instead - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - - // Build error message string - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn("not enough values to unpack (expected " + toUnpack + ", got "); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - - localVariableHelper.readTemp(methodVisitor, Type.INT_TYPE, sizeLocal); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.INT_TYPE), - false); - - methodVisitor.visitLdcInsn(")"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), - "toString", Type.getMethodDescriptor(Type.getType(String.class)), - false); - - // Call the constructor of the Error - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - // And throw it - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(exactNumberOfElements); - - // Unlike all other collection operators, UNPACK_SEQUENCE unpacks the result in reverse order - localVariableHelper.readTemp(methodVisitor, Type.getType(Object.class), tailLocal); - for (int i = toUnpack - 1; i >= 0; i--) { - localVariableHelper.readTemp(methodVisitor, Type.getType(Object.class), unpackedLocals[i]); - } - - localVariableHelper.freeLocal(); - for (int i = 0; i < toUnpack; i++) { - localVariableHelper.freeLocal(); - } - localVariableHelper.freeLocal(); - } - - /** - * Constructs a collection from the top {@code itemCount} on the stack. - * {@code collectionType} MUST implement PythonLikeObject and define - * a reverseAdd(PythonLikeObject) method. Basically generate the following code: - * - * - *
-     *     CollectionType collection = new CollectionType(itemCount);
-     *     collection.reverseAdd(TOS);
-     *     collection.reverseAdd(TOS1);
-     *     ...
-     *     collection.reverseAdd(TOS(itemCount - 1));
-     * 
- *
- * - * @param collectionType The type of collection to create - * @param itemCount The number of items to put into collection from the stack - */ - public static void buildCollection(Class collectionType, MethodVisitor methodVisitor, - int itemCount) { - String typeInternalName = Type.getInternalName(collectionType); - methodVisitor.visitTypeInsn(Opcodes.NEW, typeInternalName); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(itemCount); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, typeInternalName, "", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false); - - for (int i = 0; i < itemCount; i++) { - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typeInternalName, - "reverseAdd", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class)), - false); - } - } - - /** - * Convert TOS from a List to a tuple. Basically generates this code - * - * - *
-     *     TOS' = PythonLikeTuple.fromList(TOS);
-     * 
- *
- */ - public static void convertListToTuple(MethodVisitor methodVisitor) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonLikeTuple.class), - "fromList", - Type.getMethodDescriptor(Type.getType(PythonLikeTuple.class), Type.getType(List.class)), - false); - } - - /** - * Constructs a map from the top {@code 2 * itemCount} on the stack. - * {@code mapType} MUST implement PythonLikeObject. Basically generate the following code: - * - * - *
-     *     MapType collection = new MapType(itemCount);
-     *     collection.put(TOS1, TOS);
-     *     collection.put(TOS3, TOS2);
-     *     ...
-     *     collection.put(TTOS(2*itemCount - 1), TOS(2*itemCount - 2));
-     * 
- *
- * - * @param mapType The type of map to create - * @param itemCount The number of key value pairs to put into map from the stack - */ - public static void buildMap(Class mapType, MethodVisitor methodVisitor, - int itemCount) { - String typeInternalName = Type.getInternalName(mapType); - methodVisitor.visitTypeInsn(Opcodes.NEW, typeInternalName); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, typeInternalName, "", "()V", false); - - for (int i = 0; i < itemCount; i++) { - methodVisitor.visitInsn(Opcodes.DUP_X2); - StackManipulationImplementor.rotateThree(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "put", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), - Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); // pop return value of "put" - } - } - - /** - * Constructs a map from the top {@code itemCount + 1} on the stack. - * TOS is a tuple containing keys; TOS1-TOS(itemCount) are the values - * {@code mapType} MUST implement PythonLikeObject. Basically generate the following code: - * - * - *
-     *     MapType collection = new MapType();
-     *     collection.put(TOS[0], TOS1);
-     *     collection.put(TOS[1], TOS2);
-     *     ...
-     *     collection.put(TOS[itemCount-1], TOS(itemCount));
-     * 
- *
- * - * @param mapType The type of map to create - * @param itemCount The number of key value pairs to put into map from the stack - */ - public static void buildConstKeysMap(Class mapType, MethodVisitor methodVisitor, - int itemCount) { - String typeInternalName = Type.getInternalName(mapType); - methodVisitor.visitTypeInsn(Opcodes.NEW, typeInternalName); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, typeInternalName, "", "()V", false); - - for (int i = 0; i < itemCount; i++) { - // Stack is value, keyTuple, Map - methodVisitor.visitInsn(Opcodes.DUP_X2); - StackManipulationImplementor.rotateThree(methodVisitor); - - // Stack is Map, Map, value, keyTuple - methodVisitor.visitInsn(Opcodes.DUP_X2); - - //Stack is Map, keyTuple, Map, value, keyTuple - methodVisitor.visitLdcInsn(itemCount - i - 1); // We are adding in reverse order - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(int.class)), - true); - - // Stack is Map, keyTuple, Map, value, key - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is Map, keyTuple, Map, key, value - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "put", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), - Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); // pop return value of "put" - - // Stack is Map, keyTuple - methodVisitor.visitInsn(Opcodes.SWAP); - } - // Stack is keyTuple, Map - // Pop the keyTuple off the stack - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * Implements TOS1 in TOS. TOS must be a collection/object that implement the "__contains__" dunder method. - */ - public static void containsOperator(MethodVisitor methodVisitor, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - StackManipulationImplementor.swap(methodVisitor); - DunderOperatorImplementor.binaryOperator(methodVisitor, stackMetadata - .pop(2) - .push(stackMetadata.getTOSValueSource()) - .push(stackMetadata.getValueSourceForStackIndex(1)), PythonBinaryOperator.CONTAINS); - // TODO: implement fallback on __iter__ if __contains__ does not exist - if (instruction.arg() == 1) { - PythonBuiltinOperatorImplementor.performNotOnTOS(methodVisitor); - } - } - - /** - * Implements TOS1[TOS] = TOS2. TOS1 must be a collection/object that implement the "__setitem__" dunder method. - */ - public static void setItem(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Stack is TOS2, TOS1, TOS - StackManipulationImplementor.rotateThree(methodVisitor); - StackManipulationImplementor.rotateThree(methodVisitor); - // Stack is TOS1, TOS, TOS2 - DunderOperatorImplementor.ternaryOperator(functionMetadata, stackMetadata.pop(3) - .push(stackMetadata.getValueSourceForStackIndex(1)) - .push(stackMetadata.getValueSourceForStackIndex(0)) - .push(stackMetadata.getValueSourceForStackIndex(2)), PythonTernaryOperator.SET_ITEM); - StackManipulationImplementor.popTOS(methodVisitor); - } - - /** - * Calls collection.add(TOS[i], TOS). TOS[i] remains on stack; TOS is popped. Used to implement list/set comprehensions. - */ - public static void collectionAdd(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // instruction.arg is distance from TOS - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg()); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - StackManipulationImplementor.popTOS(methodVisitor); // pop Collection.add return value - } - - /** - * Calls collection.addAll(TOS[i], TOS). TOS[i] remains on stack; TOS is popped. Used to build lists/maps. - */ - public static void collectionAddAll(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // instruction.arg is distance from TOS - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg()); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "addAll", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Collection.class)), - true); - StackManipulationImplementor.popTOS(methodVisitor); // pop Collection.add return value - } - - /** - * Calls map.put(TOS1[i], TOS1, TOS). TOS1[i] remains on stack; TOS and TOS1 are popped. Used to implement map - * comprehensions. - */ - public static void mapPut(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // instruction.arg is distance from TOS1, so add 1 to get distance from TOS - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg() + 1); - - StackManipulationImplementor.rotateThree(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "put", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)), - true); - StackManipulationImplementor.popTOS(methodVisitor); // pop Map.put return value - } - - /** - * Calls map.putAll(TOS[i], TOS). TOS[i] remains on stack; TOS is popped. Used to build maps - */ - public static void mapPutAll(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - // instruction.arg is distance from TOS - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg()); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "putAll", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class)), - true); - } - - /** - * Calls map.putAll(TOS1[i], TOS) if TOS does not share any common keys with TOS[i]. - * If TOS shares common keys with TOS[i], an exception is thrown. - * TOS1[i] remains on stack; TOS is popped. Used to build maps - */ - public static void mapPutAllOnlyIfAllNewElseThrow(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // instruction.arg is distance from TOS - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg()); - StackManipulationImplementor.swap(methodVisitor); - - // Duplicate both maps so we can get their key sets - StackManipulationImplementor.duplicateTOSAndTOS1(methodVisitor); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "keySet", - Type.getMethodDescriptor(Type.getType(Set.class)), - true); - - StackManipulationImplementor.swap(methodVisitor); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "keySet", - Type.getMethodDescriptor(Type.getType(Set.class)), - true); - - // Check if the two key sets are disjoints - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), - "disjoint", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Collection.class), Type.getType(Collection.class)), - false); - - Label performPutAllLabel = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, performPutAllLabel); // if result == 1 (i.e. true), do the putAll operation - - // else, throw a new exception - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), - "", - Type.getMethodDescriptor(Type.VOID_TYPE), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(performPutAllLabel); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), - "putAll", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class)), - true); - } - - public static void buildSlice(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int argCount) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - if (argCount == 2) { - // Push 1 as the third argument (step) - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInteger.class), "ONE", - Type.getDescriptor(PythonInteger.class)); - } else if (argCount == 3) { - // do nothing; third argument already on stack - } else { - throw new IllegalArgumentException("arg for BUILD_SLICE must be 2 or 3"); - } - - // Store step in temp variable (need to move slice down 2) - int stepTemp = localVariableHelper.newLocal(); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), stepTemp); - - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonSlice.class)); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - // Restore step - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stepTemp); - localVariableHelper.freeLocal(); - - // Create the slice - 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); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DelegatingInterfaceImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DelegatingInterfaceImplementor.java deleted file mode 100644 index 33657508..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DelegatingInterfaceImplementor.java +++ /dev/null @@ -1,280 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledClass; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.util.MethodVisitorAdapters; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class DelegatingInterfaceImplementor extends JavaInterfaceImplementor { - final String internalClassName; - final Class interfaceClass; - final Map methodNameToFieldDescriptor; - - public DelegatingInterfaceImplementor(String internalClassName, Class interfaceClass, - Map methodNameToFieldDescriptor) { - this.internalClassName = internalClassName; - this.interfaceClass = interfaceClass; - this.methodNameToFieldDescriptor = methodNameToFieldDescriptor; - } - - @Override - public Class getInterfaceClass() { - return interfaceClass; - } - - @Override - public void implement(ClassWriter classWriter, PythonCompiledClass compiledClass) { - for (Method method : interfaceClass.getMethods()) { - if (!Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().isInterface()) { - implementMethod(classWriter, compiledClass, method); - } - } - } - - private void implementMethod(ClassWriter classWriter, PythonCompiledClass compiledClass, Method interfaceMethod) { - if (!methodNameToFieldDescriptor.containsKey(interfaceMethod.getName())) { - if (interfaceMethod.isDefault()) { - return; - } else { - throw new TypeError("Class %s cannot implement interface %s because it does not implement method %s." - .formatted(compiledClass.className, interfaceMethod.getDeclaringClass().getName(), - interfaceMethod.getName())); - } - } - var interfaceMethodDescriptor = Type.getMethodDescriptor(interfaceMethod); - - // Generates interfaceMethod(A a, B b, ...) { return delegate.interfaceMethod(a, b, ...); } - var interfaceMethodVisitor = classWriter.visitMethod(Modifier.PUBLIC, interfaceMethod.getName(), - interfaceMethodDescriptor, null, null); - - interfaceMethodVisitor = - MethodVisitorAdapters.adapt(interfaceMethodVisitor, interfaceMethod.getName(), interfaceMethodDescriptor); - - for (var parameter : interfaceMethod.getParameters()) { - interfaceMethodVisitor.visitParameter(parameter.getName(), 0); - } - interfaceMethodVisitor.visitCode(); - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - interfaceMethodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IdentityHashMap.class)); - interfaceMethodVisitor.visitInsn(Opcodes.DUP); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IdentityHashMap.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - interfaceMethodVisitor.visitVarInsn(Opcodes.ASTORE, interfaceMethod.getParameterCount() + 1); - - // Generates TOS = MyClass.methodInstance.getClass().getField(ARGUMENT_SPEC_INSTANCE_FIELD).get(MyClass.methodInstance); - var functionInterfaceDeclaration = methodNameToFieldDescriptor.get(interfaceMethod.getName()); - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - interfaceMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, - PythonClassTranslator.getJavaMethodName(interfaceMethod.getName()), - functionInterfaceDeclaration.descriptor()); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), - "getClass", Type.getMethodDescriptor(Type.getType(Class.class)), false); - interfaceMethodVisitor.visitLdcInsn(PythonBytecodeToJavaBytecodeTranslator.ARGUMENT_SPEC_INSTANCE_FIELD_NAME); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Class.class), - "getField", Type.getMethodDescriptor(Type.getType(Field.class), Type.getType(String.class)), false); - interfaceMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, - PythonClassTranslator.getJavaMethodName(interfaceMethod.getName()), - functionInterfaceDeclaration.descriptor()); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Field.class), - "get", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), false); - interfaceMethodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ArgumentSpec.class)); - var methodType = functionInterfaceDeclaration.methodType(); - int argumentCount = methodType.getArgumentCount(); - - prepareParametersForMethodCallFromArgumentSpec(interfaceMethod, interfaceMethodVisitor, argumentCount, methodType, - true); - - Type[] javaParameterTypes = new Type[Math.max(0, argumentCount - 1)]; - - for (int i = 1; i < argumentCount; i++) { - javaParameterTypes[i - 1] = methodType.getArgumentTypes()[i]; - } - String javaMethodDescriptor = Type.getMethodDescriptor(methodType.getReturnType(), javaParameterTypes); - - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, - PythonClassTranslator.getJavaMethodName(interfaceMethod.getName()), - javaMethodDescriptor, false); - - var returnType = interfaceMethod.getReturnType(); - if (returnType.equals(void.class)) { - interfaceMethodVisitor.visitInsn(Opcodes.RETURN); - } else { - if (returnType.isPrimitive()) { - loadBoxedPrimitiveTypeClass(returnType, interfaceMethodVisitor); - } else { - interfaceMethodVisitor.visitLdcInsn(Type.getType(returnType)); - } - interfaceMethodVisitor.visitInsn(Opcodes.SWAP); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "convertPythonObjectToJavaType", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Class.class), Type.getType( - PythonLikeObject.class)), - false); - if (returnType.isPrimitive()) { - unboxBoxedPrimitiveType(returnType, interfaceMethodVisitor); - interfaceMethodVisitor.visitInsn(Type.getType(returnType).getOpcode(Opcodes.IRETURN)); - } else { - interfaceMethodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(returnType)); - interfaceMethodVisitor.visitInsn(Opcodes.ARETURN); - } - } - interfaceMethodVisitor.visitMaxs(interfaceMethod.getParameterCount() + 2, 1); - interfaceMethodVisitor.visitEnd(); - } - - public static void prepareParametersForMethodCallFromArgumentSpec(Method interfaceMethod, - MethodVisitor interfaceMethodVisitor, int argumentCount, - Type methodType, boolean skipSelf) { - int parameterStart = skipSelf ? 1 : 0; - interfaceMethodVisitor.visitLdcInsn(interfaceMethod.getParameterCount()); - interfaceMethodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(PythonLikeObject.class)); - interfaceMethodVisitor.visitVarInsn(Opcodes.ASTORE, interfaceMethod.getParameterCount() + 2); - for (int i = 0; i < interfaceMethod.getParameterCount(); i++) { - var parameterType = interfaceMethod.getParameterTypes()[i]; - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, interfaceMethod.getParameterCount() + 2); - interfaceMethodVisitor.visitLdcInsn(i); - interfaceMethodVisitor.visitVarInsn(Type.getType(parameterType).getOpcode(Opcodes.ILOAD), - i + 1); - if (parameterType.isPrimitive()) { - convertPrimitiveToObjectType(parameterType, interfaceMethodVisitor); - } - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, interfaceMethod.getParameterCount() + 1); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "wrapJavaObject", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(Object.class), Type.getType( - Map.class)), - false); - interfaceMethodVisitor.visitInsn(Opcodes.AASTORE); - } - - interfaceMethodVisitor.visitVarInsn(Opcodes.ALOAD, interfaceMethod.getParameterCount() + 2); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(List.class), - "of", Type.getMethodDescriptor(Type.getType(List.class), Type.getType(Object[].class)), - true); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), - "emptyMap", Type.getMethodDescriptor(Type.getType(Map.class)), false); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ArgumentSpec.class), - "extractArgumentList", Type.getMethodDescriptor( - Type.getType(List.class), Type.getType(List.class), Type.getType(Map.class)), - false); - - for (int i = 0; i < argumentCount - parameterStart; i++) { - interfaceMethodVisitor.visitInsn(Opcodes.DUP); - interfaceMethodVisitor.visitLdcInsn(i); - interfaceMethodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - interfaceMethodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodType.getArgumentTypes()[i + parameterStart].getInternalName()); - interfaceMethodVisitor.visitInsn(Opcodes.SWAP); - } - interfaceMethodVisitor.visitInsn(Opcodes.POP); - } - - public static void convertPrimitiveToObjectType(Class primitiveType, MethodVisitor methodVisitor) { - if (primitiveType.equals(boolean.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Boolean.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Boolean.class), Type.BOOLEAN_TYPE), false); - } else if (primitiveType.equals(byte.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Byte.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Byte.class), Type.BYTE_TYPE), false); - } else if (primitiveType.equals(char.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Character.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Character.class), Type.CHAR_TYPE), false); - } else if (primitiveType.equals(short.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Short.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Short.class), Type.SHORT_TYPE), false); - } else if (primitiveType.equals(int.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); - } else if (primitiveType.equals(long.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Long.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Long.class), Type.LONG_TYPE), false); - } else if (primitiveType.equals(float.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Float.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Float.class), Type.FLOAT_TYPE), false); - } else if (primitiveType.equals(double.class)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Double.class), - "valueOf", Type.getMethodDescriptor(Type.getType(Double.class), Type.DOUBLE_TYPE), false); - } else { - throw new IllegalStateException("Unknown primitive type %s.".formatted(primitiveType)); - } - } - - public static void loadBoxedPrimitiveTypeClass(Class primitiveType, MethodVisitor methodVisitor) { - if (primitiveType.equals(boolean.class)) { - methodVisitor.visitLdcInsn(Type.getType(Boolean.class)); - } else if (primitiveType.equals(byte.class)) { - methodVisitor.visitLdcInsn(Type.getType(Byte.class)); - } else if (primitiveType.equals(char.class)) { - methodVisitor.visitLdcInsn(Type.getType(Character.class)); - } else if (primitiveType.equals(short.class)) { - methodVisitor.visitLdcInsn(Type.getType(Short.class)); - } else if (primitiveType.equals(int.class)) { - methodVisitor.visitLdcInsn(Type.getType(Integer.class)); - } else if (primitiveType.equals(long.class)) { - methodVisitor.visitLdcInsn(Type.getType(Long.class)); - } else if (primitiveType.equals(float.class)) { - methodVisitor.visitLdcInsn(Type.getType(Float.class)); - } else if (primitiveType.equals(double.class)) { - methodVisitor.visitLdcInsn(Type.getType(Double.class)); - } else { - throw new IllegalStateException("Unknown primitive type %s.".formatted(primitiveType)); - } - } - - public static void unboxBoxedPrimitiveType(Class primitiveType, MethodVisitor methodVisitor) { - if (primitiveType.equals(boolean.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Boolean.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Boolean.class), - "booleanValue", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false); - } else if (primitiveType.equals(byte.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Byte.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Byte.class), - "byteValue", Type.getMethodDescriptor(Type.BYTE_TYPE), false); - } else if (primitiveType.equals(char.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Character.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Character.class), - "charValue", Type.getMethodDescriptor(Type.CHAR_TYPE), false); - } else if (primitiveType.equals(short.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Short.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Short.class), - "shortValue", Type.getMethodDescriptor(Type.SHORT_TYPE), false); - } else if (primitiveType.equals(int.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Integer.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Integer.class), - "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false); - } else if (primitiveType.equals(long.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Long.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Long.class), - "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false); - } else if (primitiveType.equals(float.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Float.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Float.class), - "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false); - } else if (primitiveType.equals(double.class)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Double.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Double.class), - "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false); - } else { - throw new IllegalStateException("Unknown primitive type %s.".formatted(primitiveType)); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java deleted file mode 100644 index c1f46083..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java +++ /dev/null @@ -1,737 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.NotImplemented; -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; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes that delegate to dunder/magic methods. - */ -public class DunderOperatorImplementor { - - public static void unaryOperator(MethodVisitor methodVisitor, StackMetadata stackMetadata, PythonUnaryOperator operator) { - PythonLikeType operand = Optional.ofNullable(stackMetadata.getTOSType()).orElse(BuiltinTypes.BASE_TYPE); - - Optional maybeKnownFunctionType = operand.getMethodType(operator.getDunderMethod()); - if (maybeKnownFunctionType.isPresent()) { - PythonKnownFunctionType knownFunctionType = maybeKnownFunctionType.get(); - Optional maybeFunctionSignature = knownFunctionType.getFunctionForParameters(); - if (maybeFunctionSignature.isPresent()) { - PythonFunctionSignature functionSignature = maybeFunctionSignature.get(); - MethodDescriptor methodDescriptor = functionSignature.getMethodDescriptor(); - if (methodDescriptor.getParameterTypes().length < 1) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getDeclaringClassInternalName()); - } else { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getParameterTypes()[0].getInternalName()); - } - functionSignature.getMethodDescriptor().callMethod(methodVisitor); - } else { - unaryOperator(methodVisitor, operator); - } - } else { - unaryOperator(methodVisitor, operator); - } - } - - /** - * Performs a unary dunder operation on TOS. Generate codes that look like this: - * - * - *
-     *    BiFunction[List, Map, Result] operand_method = TOS.$getType().$getAttributeOrError(operator.getDunderMethod());
-     *    List args = new ArrayList(1);
-     *    args.set(0) = TOS
-     *    pop TOS
-     *    TOS' = operand_method.apply(args, null)
-     * 
- *
- * - */ - public static void unaryOperator(MethodVisitor methodVisitor, PythonUnaryOperator operator) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - - // Stack is now TOS, method - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is now method, TOS - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonLikeList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonLikeList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - // Stack is now method, TOS, argList - pushArgumentIntoList(methodVisitor); - - // Stack is now method, argList - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - public static void binaryOperator(MethodVisitor methodVisitor, StackMetadata stackMetadata, - PythonBinaryOperator operator) { - binaryOperator(methodVisitor, stackMetadata, operator, true, true, false); - } - - private static void binaryOperator(MethodVisitor methodVisitor, StackMetadata stackMetadata, - PythonBinaryOperator operator, boolean isLeft, boolean leftCheckSuccessful, - boolean forceFallback) { - PythonLikeType leftOperand = - Optional.ofNullable(stackMetadata.getTypeAtStackIndex(1)).orElse(BuiltinTypes.BASE_TYPE); - PythonLikeType rightOperand = - Optional.ofNullable(stackMetadata.getTypeAtStackIndex(0)).orElse(BuiltinTypes.BASE_TYPE); - - PythonBinaryOperator actualOperator = operator; - if (forceFallback || (!isLeft && operator.getFallbackOperation().isPresent())) { - actualOperator = operator.getFallbackOperation().get(); - } - - Optional maybeKnownFunctionType = - isLeft ? leftOperand.getMethodType(actualOperator.getDunderMethod()) - : rightOperand.getMethodType(actualOperator.getRightDunderMethod()); - - if (maybeKnownFunctionType.isEmpty() && operator.getFallbackOperation().isPresent()) { - maybeKnownFunctionType = - isLeft ? leftOperand.getMethodType(operator.getFallbackOperation().get().getDunderMethod()) - : rightOperand.getMethodType(operator.getFallbackOperation().get().getRightDunderMethod()); - actualOperator = operator.getFallbackOperation().get(); - } - - if (maybeKnownFunctionType.isPresent()) { - PythonKnownFunctionType knownFunctionType = maybeKnownFunctionType.get(); - Optional maybeFunctionSignature = - isLeft ? knownFunctionType.getFunctionForParameters(rightOperand) - : knownFunctionType.getFunctionForParameters(leftOperand); - - if (maybeFunctionSignature.isPresent()) { - PythonFunctionSignature functionSignature = maybeFunctionSignature.get(); - MethodDescriptor methodDescriptor = functionSignature.getMethodDescriptor(); - boolean needToCheckForNotImplemented = - (actualOperator.hasRightDunderMethod() || actualOperator.getFallbackOperation().isPresent()) - && BuiltinTypes.NOT_IMPLEMENTED_TYPE.isSubclassOf(functionSignature.getReturnType()); - - if (isLeft) { - if (methodDescriptor.getParameterTypes().length < 2) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getDeclaringClassInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[0].getInternalName()); - } else { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[0].getInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[1].getInternalName()); - } - } else { - if (methodDescriptor.getParameterTypes().length < 2) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[0].getInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getDeclaringClassInternalName()); - } else { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[1].getInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - methodDescriptor.getParameterTypes()[0].getInternalName()); - } - } - - if (needToCheckForNotImplemented) { - methodVisitor.visitInsn(Opcodes.DUP2); - } - - if (!isLeft) { - methodVisitor.visitInsn(Opcodes.SWAP); - } - functionSignature.getMethodDescriptor().callMethod(methodVisitor); - if (needToCheckForNotImplemented) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(NotImplemented.class), - "INSTANCE", Type.getDescriptor(NotImplemented.class)); - Label ifNotImplemented = new Label(); - Label done = new Label(); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, ifNotImplemented); - - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - methodVisitor.visitJumpInsn(Opcodes.GOTO, done); - - methodVisitor.visitLabel(ifNotImplemented); - if (isLeft) { - methodVisitor.visitInsn(Opcodes.POP); - if (actualOperator.getFallbackOperation().isPresent()) { - binaryOperator(methodVisitor, stackMetadata, operator, true, true, true); - } else { - binaryOperator(methodVisitor, stackMetadata, operator, false, true, false); - } - } else { - methodVisitor.visitInsn(Opcodes.POP); - raiseUnsupportedType(methodVisitor, stackMetadata.localVariableHelper, operator); - } - methodVisitor.visitLabel(done); - } - } else if (isLeft && actualOperator.hasRightDunderMethod()) { - binaryOperator(methodVisitor, stackMetadata, operator, false, false, false); - } else if (!isLeft && leftCheckSuccessful) { - binaryOperatorOnlyRight(methodVisitor, stackMetadata.localVariableHelper, actualOperator); - } else { - binaryOperator(methodVisitor, stackMetadata.localVariableHelper, operator); - } - } else if (isLeft && actualOperator.hasRightDunderMethod()) { - binaryOperator(methodVisitor, stackMetadata, operator, false, false, false); - } else if (!isLeft && leftCheckSuccessful) { - binaryOperatorOnlyRight(methodVisitor, stackMetadata.localVariableHelper, actualOperator); - } else { - binaryOperator(methodVisitor, stackMetadata.localVariableHelper, operator); - } - } - - private static void raiseUnsupportedType(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper, - PythonBinaryOperator operator) { - int right = localVariableHelper.newLocal(); - int left = localVariableHelper.newLocal(); - - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), left); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), right); - - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(TypeError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - if (!operator.getOperatorSymbol().isEmpty()) { - methodVisitor.visitLdcInsn("unsupported operand type(s) for " + operator.getOperatorSymbol() + ": '"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.getType(String.class)), - false); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), left); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), - "getTypeName", Type.getMethodDescriptor(Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.getType(String.class)), - false); - methodVisitor.visitLdcInsn("' and '"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.getType(String.class)), - false); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), right); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), - "getTypeName", Type.getMethodDescriptor(Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.getType(String.class)), - false); - methodVisitor.visitLdcInsn("'"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "append", Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.getType(String.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "toString", Type.getMethodDescriptor(Type.getType(String.class)), - false); - - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(TypeError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } else { - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - - switch (operator) { - case GET_ITEM: // TODO: Error message - default: - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(TypeError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - } - } - - /** - * Performs a binary dunder operation on TOS and TOS1. Generate codes that look like this: - * - * - *
-     *    BiFunction[List, Map, Result] operand_method = TOS1.$getType().$getAttributeOrError(operator.getDunderMethod());
-     *    List args = new ArrayList(2);
-     *    args.set(0) = TOS1
-     *    args.set(1) = TOS
-     *    pop TOS, TOS1
-     *    TOS' = operand_method.apply(args, null)
-     * 
- *
- * - */ - public static void binaryOperator(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper, - PythonBinaryOperator operator) { - Label noLeftMethod = new Label(); - methodVisitor.visitInsn(Opcodes.DUP2); - if (operator.hasRightDunderMethod() || operator.getFallbackOperation().isPresent()) { - methodVisitor.visitInsn(Opcodes.DUP2); - } - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is now (TOS1, TOS,)? TOS, TOS1 - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, noLeftMethod); - - // Stack is now(TOS1, TOS,)? TOS, TOS1, method - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is now (TOS1, TOS,)? method, TOS, TOS1 - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonLikeList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonLikeList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - // Stack is now (TOS1, TOS,)? method, TOS, TOS1, argList - pushArgumentIntoList(methodVisitor); - pushArgumentIntoList(methodVisitor); - - // Stack is now (TOS1, TOS,)? method, argList - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - - // Stack is now (TOS1, TOS,)? method_result - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(NotImplemented.class), - "INSTANCE", Type.getDescriptor(NotImplemented.class)); - Label ifNotImplemented = new Label(); - Label done = new Label(); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, ifNotImplemented); - // Stack is TOS1, TOS, method_result - if (operator.hasRightDunderMethod() || operator.getFallbackOperation().isPresent()) { - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - } - // Stack is method_result - methodVisitor.visitJumpInsn(Opcodes.GOTO, done); - - methodVisitor.visitLabel(noLeftMethod); - methodVisitor.visitInsn(Opcodes.POP2); - methodVisitor.visitLabel(ifNotImplemented); - methodVisitor.visitInsn(Opcodes.POP); - - Label raiseError = new Label(); - if (operator.getFallbackOperation().isPresent()) { - binaryOperator(methodVisitor, localVariableHelper, operator.getFallbackOperation().get()); - methodVisitor.visitJumpInsn(Opcodes.GOTO, done); - } else if (operator.hasRightDunderMethod()) { - Label noRightMethod = new Label(); - // Stack is now TOS1, TOS - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getRightDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, noRightMethod); - - // Stack is now TOS1, TOS, method - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is now method, TOS1, TOS - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonLikeList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonLikeList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - // Stack is now method, TOS1, TOS, argList - pushArgumentIntoList(methodVisitor); - pushArgumentIntoList(methodVisitor); - - // Stack is now method, argList - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - - // Stack is now method_result - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(NotImplemented.class), - "INSTANCE", Type.getDescriptor(NotImplemented.class)); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, done); - // Stack is TOS1, TOS, NotImplemented - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitJumpInsn(Opcodes.GOTO, raiseError); - - methodVisitor.visitLabel(noRightMethod); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - } - methodVisitor.visitLabel(raiseError); - methodVisitor.visitInsn(Opcodes.SWAP); - raiseUnsupportedType(methodVisitor, localVariableHelper, operator); - - methodVisitor.visitLabel(done); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - } - - public static void binaryOperatorOnlyRight(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper, - PythonBinaryOperator operator) { - Label done = new Label(); - Label raiseError = new Label(); - Label noRightMethod = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP2); - - // Stack is now TOS1, TOS - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getRightDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, noRightMethod); - - // Stack is now TOS1, TOS, method - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is now method, TOS1, TOS - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonLikeList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonLikeList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - // Stack is now method, TOS1, TOS, argList - pushArgumentIntoList(methodVisitor); - pushArgumentIntoList(methodVisitor); - - // Stack is now method, argList - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - - // Stack is now method_result - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(NotImplemented.class), - "INSTANCE", Type.getDescriptor(NotImplemented.class)); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, done); - // Stack is TOS1, TOS, NotImplemented - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitJumpInsn(Opcodes.GOTO, raiseError); - - methodVisitor.visitLabel(noRightMethod); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - - methodVisitor.visitLabel(raiseError); - methodVisitor.visitInsn(Opcodes.SWAP); - raiseUnsupportedType(methodVisitor, localVariableHelper, operator); - - methodVisitor.visitLabel(done); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP2); - } - - /** - * Performs a ternary dunder operation on TOS, TOS1 and TOS2. Generate codes that look like this: - * - * - *
-     *    BiFunction[List, Map, Result] operand_method = TOS2.$getType().$getAttributeOrError(operator.getDunderMethod());
-     *    List args = new ArrayList(2);
-     *    args.set(0) = TOS2
-     *    args.set(1) = TOS1
-     *    args.set(2) = TOS
-     *    pop TOS, TOS1, TOS2
-     *    TOS' = operand_method.apply(args, null)
-     * 
- *
- * - */ - public static void ternaryOperator(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonTernaryOperator operator) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - StackManipulationImplementor.rotateThree(methodVisitor); - methodVisitor.visitInsn(Opcodes.SWAP); - // Stack is now TOS, TOS1, TOS2 - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - // Stack is now TOS, TOS1, TOS2, method - StackManipulationImplementor.rotateFour(functionMetadata, stackMetadata.pop(3) - .push(stackMetadata.getValueSourceForStackIndex(0)) - .push(stackMetadata.getValueSourceForStackIndex(1)) - .push(stackMetadata.getValueSourceForStackIndex(2)) - .pushTemp(BuiltinTypes.FUNCTION_TYPE)); - - // Stack is now method, TOS, TOS1, TOS2 - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonLikeList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonLikeList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - // Stack is now method, TOS, TOS1, TOS2, argList - pushArgumentIntoList(methodVisitor); - pushArgumentIntoList(methodVisitor); - pushArgumentIntoList(methodVisitor); - - // Stack is now method, argList - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * TOS is a list and TOS1 is an argument. Pushes TOS1 into TOS, and leave TOS on the stack (pops TOS1). - */ - private static void pushArgumentIntoList(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * Compares TOS and TOS1 via their dunder methods. {@code CompareOp} indicates the operation - * to perform. - */ - public static void compareValues(MethodVisitor methodVisitor, StackMetadata stackMetadata, CompareOp op) { - switch (op) { - case LESS_THAN: - binaryOperator(methodVisitor, stackMetadata, PythonBinaryOperator.LESS_THAN); - break; - case LESS_THAN_OR_EQUALS: - binaryOperator(methodVisitor, stackMetadata, PythonBinaryOperator.LESS_THAN_OR_EQUAL); - break; - case EQUALS: - case NOT_EQUALS: - binaryOpOverridingLeftIfSpecific(methodVisitor, stackMetadata, op); - break; - case GREATER_THAN: - binaryOperator(methodVisitor, stackMetadata, PythonBinaryOperator.GREATER_THAN); - break; - case GREATER_THAN_OR_EQUALS: - binaryOperator(methodVisitor, stackMetadata, PythonBinaryOperator.GREATER_THAN_OR_EQUAL); - break; - default: - throw new IllegalStateException("Unhandled branch: " + op); - } - } - - private static void binaryOpOverridingLeftIfSpecific(MethodVisitor methodVisitor, StackMetadata stackMetadata, - CompareOp op) { - switch (op) { - case EQUALS: - case NOT_EQUALS: - break; - default: - throw new IllegalArgumentException("Should only be called for equals and not equals"); - } - - PythonBinaryOperator operator = - (op == CompareOp.EQUALS) ? PythonBinaryOperator.EQUAL : PythonBinaryOperator.NOT_EQUAL; - - // If we know TOS1 defines == or !=, we don't need to go here - if (stackMetadata.getTypeAtStackIndex(1).getDefiningTypeOrNull(operator.getDunderMethod()) != BuiltinTypes.BASE_TYPE) { - binaryOperator(methodVisitor, stackMetadata, operator); - return; - } - - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP_X1); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(operator.getDunderMethod()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), - "getDefiningTypeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeType.class), - Type.getType(String.class)), - false); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(BuiltinTypes.class), - "BASE_TYPE", Type.getDescriptor(PythonLikeType.class)); - - Label ifDefined = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifDefined); - methodVisitor.visitInsn(Opcodes.SWAP); - 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 deleted file mode 100644 index e1f0778c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java +++ /dev/null @@ -1,523 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - -import ai.timefold.jpyinterpreter.ExceptionBlock; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.opcodes.OpcodeWithoutSource; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.types.BoundPythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -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; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes related to exceptions - */ -public class ExceptionImplementor { - - /** - * Creates an AssertionError and pushes it to the stack. - */ - public static void createAssertionError(MethodVisitor methodVisitor) { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonAssertionError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonAssertionError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), - false); - } - - /** - * Reraise the last exception (stored in the exception local variable slot) - */ - public static void reraiseLast(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper) { - localVariableHelper.readCurrentException(methodVisitor); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - - /** - * TOS is an exception or an exception type. Reraise it (i.e. throw it). - */ - public static void reraise(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(Throwable.class)); - - Label ifNotThrowable = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, ifNotThrowable); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(ifNotThrowable); - - // Construct an instance of the type and throw it - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - methodVisitor.visitInsn(Opcodes.ATHROW); - } - - /** - * TOS is an exception; TOS1 is a type or an exception instance. Raise - * TOS1 with TOS as its cause. - */ - public static void raiseWithCause(MethodVisitor methodVisitor) { - StackManipulationImplementor.swap(methodVisitor); - - Label ifExceptionIsInstanceStart = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(PythonLikeType.class)); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, ifExceptionIsInstanceStart); - - // Exception is type: turn it to instance via its constructor - FunctionImplementor.callGenericFunction(methodVisitor, 0); // a type is callable; calling it results in calling its constructor - - methodVisitor.visitLabel(ifExceptionIsInstanceStart); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Throwable.class), - "initCause", Type.getMethodDescriptor(Type.getType(Throwable.class), - Type.getType(Throwable.class)), - false); - reraise(methodVisitor); - } - - /** - * Raise an exception, with the stack and effect varying depending on {@code instruction.arg}: - * - * instruction.arg = 0: Stack is empty. Reraise the last exception. - * instruction.arg = 1: TOS is an exception or exception type; raise it. - * instruction.arg = 2: TOS1 is an exception/exception type, and TOS is the cause. Raise TOS1 with TOS as the cause. - */ - public static void raiseWithOptionalExceptionAndCause(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction, - LocalVariableHelper localVariableHelper) { - switch (instruction.arg()) { - case 0 -> reraiseLast(methodVisitor, localVariableHelper); - case 1 -> reraise(methodVisitor); - case 2 -> raiseWithCause(methodVisitor); - default -> throw new IllegalStateException("Impossible argc value (" + instruction.arg() + ") for RAISE_VARARGS."); - } - } - - /** - * Creates a try...finally block. Python also treat catch blocks as finally blocks, which - * are handled via the {@link ControlOpDescriptor#JUMP_IF_NOT_EXC_MATCH} instruction. - * {@code instruction.arg} is the difference in bytecode offset to the first catch/finally block. - */ - public static void createTryFinallyBlock(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - int handlerLocation, - Map bytecodeCounterToLabelMap, - BiConsumer bytecodeCounterCodeArgumentConsumer) { - var methodVisitor = functionMetadata.methodVisitor; - var className = functionMetadata.className; - // Store the stack in local variables so the except block has access to them - int[] stackLocals = StackManipulationImplementor.storeStack(methodVisitor, stackMetadata); - - Label finallyStart = - bytecodeCounterToLabelMap.computeIfAbsent(handlerLocation, - key -> new Label()); - Label tryStart = new Label(); - - methodVisitor.visitTryCatchBlock(tryStart, finallyStart, finallyStart, Type.getInternalName(PythonBaseException.class)); - - methodVisitor.visitLabel(tryStart); - - // At finallyStart, stack is expected to be: - // in Python 3.10 and below - // [(stack-before-try), instruction, level, label, tb, value, exception] - // or in Python 3.11 and above - // [(stack-before-try), instruction, level, label, tb, value, exception] ; where: - // (stack-before-try) = the stack state before the try statement - // (see https://github.com/python/cpython/blob/b6558d768f19584ad724be23030603280f9e6361/Python/compile.c#L3241-L3268 ) - // instruction = instruction that created the block - // level = stack depth at the time the block was created - // label = label to go to for exception - // (see https://stackoverflow.com/a/66720684) - // tb = stack trace - // exception = exception instance - // value = the exception instance again? - // Results from Python 3.10 seems to indicate both exception and value are exception - // instances, since Python 3.10 use RERAISE on exception (TOS) - // and stores value into the exception variable (TOS1) - // Python 3.11 and above use a different code path - bytecodeCounterCodeArgumentConsumer.accept(handlerLocation, () -> { - // Stack is exception - // Duplicate exception to the current exception variable slot so we can reraise it if needed - stackMetadata.localVariableHelper.writeCurrentException(methodVisitor); - StackManipulationImplementor.restoreStack(methodVisitor, stackMetadata, stackLocals); - - // Stack is (stack-before-try) - - // Instruction - PythonConstantsImplementor.loadNone(methodVisitor); // We don't use it - - // Stack is (stack-before-try), instruction - - // Stack Size - methodVisitor.visitLdcInsn(stackMetadata.getStackSize()); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonInteger.class), - "valueOf", Type.getMethodDescriptor(Type.getType(PythonInteger.class), Type.INT_TYPE), - false); - - // Stack is (stack-before-try), instruction, stack-size - - // Label - PythonConstantsImplementor.loadNone(methodVisitor); // We don't use it - - // Stack is (stack-before-try), instruction, stack-size, label - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, className); // needed cast; type confusion on this? - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "getTraceback", Type.getMethodDescriptor(Type.getType(PythonTraceback.class)), - true); - - // Stack is (stack-before-try), instruction, stack-size, label, traceback - - // Load exception - stackMetadata.localVariableHelper.readCurrentException(methodVisitor); - - // Stack is (stack-before-try), instruction, stack-size, label, traceback, exception - - // Get exception value - methodVisitor.visitInsn(Opcodes.DUP); - // Stack is (stack-before-try), instruction, stack-size, label, traceback, value, exception - }); - } - - public static void startExceptOrFinally(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - // Clear the exception since it was handled - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - localVariableHelper.writeCurrentException(methodVisitor); - - // Pop off the block - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - methodVisitor.visitInsn(Opcodes.POP); - } else { - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.POP); - } - } - - public static void setupWith(int jumpTarget, FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitInsn(Opcodes.DUP); // duplicate context_manager twice; need one for __enter__, two for __exit__ - methodVisitor.visitInsn(Opcodes.DUP); - - // First load the method __exit__ - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn("__exit__"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); - - // bind it to the context_manager - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(BoundPythonLikeFunction.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeFunction.class)), - false); - - // Swap __exit__ method with duplicated context_manager - methodVisitor.visitInsn(Opcodes.SWAP); - - // Call __enter__ - DunderOperatorImplementor.unaryOperator(methodVisitor, stackMetadata, PythonUnaryOperator.ENTER); - - int enterResult = stackMetadata.localVariableHelper.newLocal(); - - // store enter result in temp, so it does not get saved in try block - stackMetadata.localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); - - // Create a try...finally block pointing to delta - StackMetadata currentStackMetadata = stackMetadata - .pop() - .push(ValueSourceInfo.of(new OpcodeWithoutSource(), PythonLikeFunction.getFunctionType(), - stackMetadata.getTOSValueSource())); - - createTryFinallyBlock(functionMetadata, currentStackMetadata, jumpTarget, - functionMetadata.bytecodeCounterToLabelMap, - (bytecodeIndex, runnable) -> { - functionMetadata.bytecodeCounterToCodeArgumenterList - .computeIfAbsent(bytecodeIndex, key -> new ArrayList<>()).add(runnable); - }); - - // Push enter result back to the stack - stackMetadata.localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); - // cannot free, since try block store stack in locals => freeing enterResult messes up indexing of locals - } - - public static void beforeWith(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitInsn(Opcodes.DUP); // duplicate context_manager twice; need one for __enter__, two for __exit__ - methodVisitor.visitInsn(Opcodes.DUP); - - // First load the method __exit__ - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn("__exit__"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); - - // bind it to the context_manager - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(BoundPythonLikeFunction.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeFunction.class)), - false); - - // Swap __exit__ method with duplicated context_manager - methodVisitor.visitInsn(Opcodes.SWAP); - - // Call __enter__ - DunderOperatorImplementor.unaryOperator(methodVisitor, stackMetadata, PythonUnaryOperator.ENTER); - - int enterResult = stackMetadata.localVariableHelper.newLocal(); - - // store enter result in temp, so it does not get saved in try block - stackMetadata.localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); - - // Push enter result back to the stack - stackMetadata.localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); - // cannot free, since try block store stack in locals => freeing enterResult messes up indexing of locals - } - - public static void startExceptBlock(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - ExceptionBlock exceptionBlock) { - // In Python 3.11 and above, the stack here is - // [(stack-before-try), exception] - - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Stack is exception - // Duplicate exception to the current exception variable slot so we can reraise it if needed - stackMetadata.localVariableHelper.writeCurrentException(methodVisitor); - StackManipulationImplementor.restoreExceptionTableStack(functionMetadata, stackMetadata, exceptionBlock); - - // Stack is (stack-before-try) - if (exceptionBlock.isPushLastIndex()) { - // Load 0 for last index since we don't use it - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInteger.class), "ZERO", - Type.getDescriptor(PythonInteger.class)); - } - - // Load exception - stackMetadata.localVariableHelper.readCurrentException(methodVisitor); - // Stack is (stack-before-try), index?, exception - } - - public static void pushExcInfo(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - localVariableHelper.readCurrentException(methodVisitor); - methodVisitor.visitInsn(Opcodes.SWAP); - } - - public static void checkExcMatch(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitInsn(Opcodes.DUP2); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeType.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), "isInstance", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonBoolean.class), "valueOf", - Type.getMethodDescriptor(Type.getType(PythonBoolean.class), Type.BOOLEAN_TYPE), - false); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - - public static void handleExceptionInWith(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - // First, store the top 7 items in the stack to be restored later - int exception = localVariableHelper.newLocal(); - int exceptionArgs = localVariableHelper.newLocal(); - int traceback = localVariableHelper.newLocal(); - int label = localVariableHelper.newLocal(); - int stackSize = localVariableHelper.newLocal(); - int instruction = localVariableHelper.newLocal(); - int exitFunction = localVariableHelper.newLocal(); - - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); - } else { - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); - } - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exitFunction); - - // load exitFunction - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exitFunction); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); - - // create the argument list - // (exc_type, exc_value, traceback) - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(ArrayList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(3); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), - false); - - methodVisitor.visitInsn(Opcodes.DUP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "add", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitInsn(Opcodes.DUP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "add", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitInsn(Opcodes.DUP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Collection.class), - "add", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); - methodVisitor.visitInsn(Opcodes.POP); - - // Use null for keywords - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - // Call the exit function - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - - // Restore the stack, raising the returned value to the top of the stack - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exitFunction); - methodVisitor.visitInsn(Opcodes.SWAP); - - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); - methodVisitor.visitInsn(Opcodes.SWAP); - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); - methodVisitor.visitInsn(Opcodes.SWAP); - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); - methodVisitor.visitInsn(Opcodes.SWAP); - } - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); - methodVisitor.visitInsn(Opcodes.SWAP); - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); - methodVisitor.visitInsn(Opcodes.SWAP); - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Free the 7 temps - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - - 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 deleted file mode 100644 index 60b3ebed..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementor.java +++ /dev/null @@ -1,891 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonCode; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeGenericType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implements opcodes related to functions - */ -public class FunctionImplementor { - - public static void callBinaryMethod(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, String methodName) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(methodName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.SWAP); - - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - getCallerInstance(functionMetadata, stackMetadata); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - public static void callBinaryMethod(MethodVisitor methodVisitor, String methodName) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn(methodName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.SWAP); - - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * Loads a method named co_names[namei] from the TOS object. TOS is popped. This bytecode distinguishes two cases: - * if TOS has a method with the correct name, the bytecode pushes the unbound method and TOS. - * 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, StackMetadata stackMetadata, - int nameIndex) { - var methodVisitor = functionMetadata.methodVisitor; - var function = functionMetadata.pythonCompiledFunction; - var className = functionMetadata.className; - PythonLikeType stackTosType = stackMetadata.getTOSType(); - PythonLikeType tosType; - boolean isTosType; - if (stackTosType instanceof PythonLikeGenericType) { - tosType = ((PythonLikeGenericType) stackTosType).getOrigin(); - isTosType = true; - } else { - tosType = stackTosType; - isTosType = false; - } - tosType.getMethodType(functionMetadata.pythonCompiledFunction.co_names.get(nameIndex)).ifPresentOrElse( - knownFunctionType -> { - if (isTosType && knownFunctionType.isStaticMethod()) { - 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)), - true); - - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - // Need to move NULL behind method - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - } - } else if (!isTosType && knownFunctionType.isStaticMethod()) { - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - 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)), - true); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - // Need to move NULL behind method - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - } - } else if (isTosType && knownFunctionType.isClassMethod()) { - methodVisitor.visitInsn(Opcodes.DUP); - 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)), - true); - methodVisitor.visitInsn(Opcodes.SWAP); - } else if (!isTosType && knownFunctionType.isClassMethod()) { - 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(nameIndex)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.SWAP); - } else if (isTosType) { - methodVisitor.visitInsn(Opcodes.DUP); - 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)), - true); - methodVisitor.visitInsn(Opcodes.SWAP); - } else { - 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(nameIndex)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - methodVisitor.visitInsn(Opcodes.SWAP); - } - }, - () -> loadGenericMethod(functionMetadata, methodVisitor, className, function, stackMetadata, - nameIndex)); - } - - /** - * Loads a method named co_names[namei] from the TOS object. TOS is popped. This bytecode distinguishes two cases: - * if TOS has a method with the correct name, the bytecode pushes the unbound method and TOS. - * 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. - */ - private static void loadGenericMethod(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, - PythonCompiledFunction function, - 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(nameIndex)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), - "loadMethod", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - Label blockEnd = new Label(); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, blockEnd); - - // TOS is null; type does not have attribute; do normal attribute lookup - // Stack is object, null - methodVisitor.visitInsn(Opcodes.POP); - ObjectImplementor.getAttribute(functionMetadata, stackMetadata, nameIndex); - - // Stack is method - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { - // Python 3.11+ swap these - methodVisitor.visitInsn(Opcodes.SWAP); - } - - methodVisitor.visitLabel(blockEnd); - - // Stack is either: - // object, method if it was in type - // null, method if it was not in type (Or method, null if Python 3.11+) - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is now: - // method, object if it was in type - // method, null if it was not in type (and prior to Python 3.11+) - // null, method if it was not in type (if Python 3.11+) - } - - public static void setCallKeywordNameTuple(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - int constantIndex) { - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - PythonConstantsImplementor.loadConstant(functionMetadata.methodVisitor, functionMetadata.className, constantIndex); - localVariableHelper.writeCallKeywords(functionMetadata.methodVisitor); - } - - /** - * Calls a function. argc is the number of positional arguments. Keyword arguments are stored in a local variable. - * Keyword arguments (if any) are at the top of the stack, followed by, positional arguments. - * Below them either self and an unbound method object or NULL and an arbitrary callable). - * All of them are popped and the return value is pushed. - */ - public static void call(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int argumentCount) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(argumentCount + 1); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - List keywordArgumentNameList = stackMetadata.getCallKeywordNameList(); - List callStackParameterTypes = stackMetadata.getValueSourcesUpToStackIndex(argumentCount) - .stream().map(ValueSourceInfo::getValueType).collect(Collectors.toList()); - - knownFunctionType - .getFunctionForParameters(argumentCount - keywordArgumentNameList.size(), keywordArgumentNameList, - callStackParameterTypes) - .ifPresentOrElse(functionSignature -> { - KnownCallImplementor.callPython311andAbove(functionSignature, functionMetadata, stackMetadata, - argumentCount, - stackMetadata.getCallKeywordNameList()); - }, () -> callGeneric(functionMetadata, stackMetadata, argumentCount)); - } else { - functionType = stackMetadata.getTypeAtStackIndex(argumentCount); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - List keywordArgumentNameList = stackMetadata.getCallKeywordNameList(); - List callStackParameterTypes = stackMetadata.getValueSourcesUpToStackIndex(argumentCount) - .stream().map(ValueSourceInfo::getValueType).collect(Collectors.toList()); - - knownFunctionType - .getFunctionForParameters(argumentCount - keywordArgumentNameList.size(), keywordArgumentNameList, - callStackParameterTypes) - .ifPresentOrElse(functionSignature -> { - KnownCallImplementor.callPython311andAbove(functionSignature, functionMetadata, stackMetadata, - argumentCount, - stackMetadata.getCallKeywordNameList()); - }, () -> callGeneric(functionMetadata, stackMetadata, argumentCount)); - } else { - callGeneric(functionMetadata, stackMetadata, argumentCount); - } - } - } - - private static void callGeneric(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - int argumentCount) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - int keywordArgs = localVariableHelper.newLocal(); - int positionalArgs = localVariableHelper.newLocal(); - - localVariableHelper.readCallKeywords(methodVisitor); - CollectionImplementor.buildCollection(TupleMapPair.class, methodVisitor, argumentCount + 1); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(TupleMapPair.class), "tuple", - Type.getDescriptor(PythonLikeTuple.class)); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeTuple.class), positionalArgs); - - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(TupleMapPair.class), "map", - Type.getDescriptor(PythonLikeDict.class)); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeDict.class), keywordArgs); - - // Stack is (null or method), (obj or method) - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is (obj or method) (null or method) - Label ifNullStart = new Label(); - Label blockEnd = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, ifNullStart); - - // Stack is obj, method - StackManipulationImplementor.swap(methodVisitor); - - // Stack is method, obj - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeTuple.class), positionalArgs); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - - // Stack is method, argList, obj, index - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is method, argList, argList, index, obj - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "add", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(Object.class)), - true); - - // Stack is method - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeTuple.class), positionalArgs); - - // Stack is method, positionalArgs - methodVisitor.visitJumpInsn(Opcodes.GOTO, blockEnd); - - methodVisitor.visitLabel(ifNullStart); - // Stack is method, null - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is method - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeTuple.class), positionalArgs); - - // Stack is method, positionalArgs - methodVisitor.visitLabel(blockEnd); - - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeDict.class), keywordArgs); - - // Stack is method, positionalArgs, keywordArgs - getCallerInstance(functionMetadata, stackMetadata); - - // Stack is callable, positionalArgs, keywordArgs, null - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - - localVariableHelper.resetCallKeywords(methodVisitor); - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - } - - /** - * Calls a method. argc is the number of positional arguments. Keyword arguments are not supported. - * This opcode is designed to be used with LOAD_METHOD. Positional arguments are on top of the stack. - * Below them, the two items described in LOAD_METHOD are on the stack - * (either self and an unbound method object or NULL and an arbitrary callable). - * All of them are popped and the return value is pushed. - */ - public static void callMethod(FunctionMetadata functionMetadata, StackMetadata stackMetadata, MethodVisitor methodVisitor, - PythonBytecodeInstruction instruction, LocalVariableHelper localVariableHelper) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg() + 1); - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - PythonLikeType[] parameterTypes = new PythonLikeType[instruction.arg()]; - for (int i = 0; i < parameterTypes.length; i++) { - parameterTypes[parameterTypes.length - i - 1] = stackMetadata.getTypeAtStackIndex(i); - } - knownFunctionType.getFunctionForParameters(parameterTypes) - .ifPresentOrElse(functionSignature -> { - KnownCallImplementor.callMethod(functionSignature, methodVisitor, localVariableHelper, - instruction.arg()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - if (knownFunctionType.isStaticMethod()) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - }, () -> callGenericMethod(functionMetadata, stackMetadata, methodVisitor, instruction, - localVariableHelper)); - } else { - callGenericMethod(functionMetadata, stackMetadata, methodVisitor, instruction, localVariableHelper); - } - } - - private static void callGenericMethod(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, - PythonBytecodeInstruction instruction, - LocalVariableHelper localVariableHelper) { - // Stack is method, (obj or null), arg0, ..., arg(argc - 1) - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, instruction.arg()); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is method, argList, (obj or null) - Label ifNullStart = new Label(); - Label blockEnd = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, ifNullStart); - - // Stack is method, argList, obj - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, 1); - StackManipulationImplementor.swap(methodVisitor); - - // Stack is method, argList, argList, obj - methodVisitor.visitInsn(Opcodes.ICONST_0); - - // Stack is method, argList, argList, obj, index - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is method, argList, argList, index, obj - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "add", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(Object.class)), - true); - - // Stack is method, argList - methodVisitor.visitJumpInsn(Opcodes.GOTO, blockEnd); - - methodVisitor.visitLabel(ifNullStart); - // Stack is method, argList, null - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is method, argList - methodVisitor.visitLabel(blockEnd); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - - // Stack is method, argList - getCallerInstance(functionMetadata, stackMetadata); - - // Stack is callable, argument_list, null - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * Calls a function. TOS...TOS[argc - 1] are the arguments to the function. - * TOS[argc] is the function to call. TOS...TOS[argc] are all popped and - * the result is pushed onto the stack. - */ - public static void callFunction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg()); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - knownFunctionType.getDefaultFunctionSignature() - .ifPresentOrElse(functionSignature -> { - KnownCallImplementor.callWithoutKeywords(functionSignature, functionMetadata, stackMetadata, - instruction.arg()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - }, () -> callGenericFunction(functionMetadata, stackMetadata, methodVisitor, instruction)); - } else { - callGenericFunction(functionMetadata, stackMetadata, methodVisitor, instruction); - } - } - - public static void callGenericFunction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - callGenericFunction(functionMetadata, stackMetadata, methodVisitor, instruction.arg()); - } - - public static void callGenericFunction(MethodVisitor methodVisitor, int argCount) { - // stack is callable, arg0, arg1, ..., arg(argc - 1) - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, argCount); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - // Stack is callable, argument_list, null - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - public static void callGenericFunction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, int argCount) { - // stack is callable, arg0, arg1, ..., arg(argc - 1) - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, argCount); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - getCallerInstance(functionMetadata, stackMetadata); - - // Stack is callable, argument_list, null - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * Calls a function. TOS is a tuple containing keyword names. - * TOS[1]...TOS[len(TOS)] are the keyword arguments to the function (TOS[1] is (TOS)[0], TOS[2] is (TOS)[1], ...). - * TOS[len(TOS) + 1]...TOS[argc + 1] are the positional arguments (rightmost first). - * TOS[argc + 2] is the function to call. TOS...TOS[argc + 2] are all popped and - * the result is pushed onto the stack. - */ - public static void callFunctionWithKeywords(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg() + 1); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - knownFunctionType.getDefaultFunctionSignature() - .ifPresentOrElse(functionSignature -> { - KnownCallImplementor.callWithKeywordsAndUnwrapSelf(functionSignature, functionMetadata, stackMetadata, - instruction.arg()); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - }, () -> callGenericFunction(functionMetadata, stackMetadata, methodVisitor, instruction)); - } else { - callGenericFunctionWithKeywords(functionMetadata, stackMetadata, methodVisitor, instruction); - } - } - - /** - * Calls a function. TOS is a tuple containing keyword names. - * TOS[1]...TOS[len(TOS)] are the keyword arguments to the function (TOS[1] is (TOS)[0], TOS[2] is (TOS)[1], ...). - * TOS[len(TOS) + 1]...TOS[argc + 1] are the positional arguments (rightmost first). - * TOS[argc + 2] is the function to call. TOS...TOS[argc + 2] are all popped and - * the result is pushed onto the stack. - */ - public static void callGenericFunctionWithKeywords(FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - // stack is callable, arg0, arg1, ..., arg(argc - len(keys)), ..., arg(argc - 1), keys - // We know the total number of arguments, but not the number of individual positional/keyword arguments - // Since Java Bytecode require consistent stack frames (i.e. the body of a loop must start with - // the same number of elements in the stack), we need to add the tuple/map in the same object - // which will delegate it to either the tuple or the map depending on position and the first item size - CollectionImplementor.buildCollection(TupleMapPair.class, methodVisitor, instruction.arg() + 1); - - // stack is callable, tupleMapPair - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(TupleMapPair.class), "tuple", - Type.getDescriptor(PythonLikeTuple.class)); - - // stack is callable, tupleMapPair, positionalArgs - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(TupleMapPair.class), "map", - Type.getDescriptor(PythonLikeDict.class)); - - getCallerInstance(functionMetadata, stackMetadata); - - // Stack is callable, positionalArgs, keywordArgs - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * Calls a function. If the lowest bit of instruction.arg is set, TOS is a mapping object containing keyword - * arguments, TOS[1] is an iterable containing positional arguments and TOS[2] is callable. Otherwise, - * TOS is an iterable containing positional arguments and TOS[1] is callable. - */ - public static void callFunctionUnpack(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - if ((instruction.arg() & 1) == 1) { - callFunctionUnpackMapAndIterable(functionMetadata, stackMetadata, functionMetadata.methodVisitor); - } else { - callFunctionUnpackIterable(functionMetadata, stackMetadata, functionMetadata.methodVisitor); - } - } - - public static void callFunctionUnpackMapAndIterable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - MethodVisitor methodVisitor) { - getCallerInstance(functionMetadata, stackMetadata); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - } - - public static void callFunctionUnpackIterable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - MethodVisitor methodVisitor) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - getCallerInstance(functionMetadata, stackMetadata); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), - "$call", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(List.class), - Type.getType(Map.class), - Type.getType(PythonLikeObject.class)), - true); - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - } - - private static void getCallerInstance(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - if (functionMetadata.pythonCompiledFunction.totalArgCount() > 0) { - // Use null as the key for the current instance, used by super() - stackMetadata.localVariableHelper.readLocal(methodVisitor, 0); - } else { - // Put the current instance as null - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } - } - - /** - * Creates a function. The stack depends on {@code instruction.arg}: - * - * - If (arg & 1) == 1, a tuple of default values for positional-only and positional-or-keyword parameters in positional - * order - * - If (arg & 2) == 2, a dictionary of keyword-only parameters’ default values - * - If (arg & 4) == 4, an annotation dictionary - * - If (arg & 8) == 8, a tuple containing cells for free variables - * - * The stack will contain the following items, in the given order: - * - * TOP - * [Mandatory] Function Name - * [Mandatory] Class of the PythonLikeFunction to create - * [Optional, flag = 0x8] A tuple containing the cells for free variables - * [Optional, flag = 0x4] A tuple containing key,value pairs for the annotation directory - * [Optional, flag = 0x2] A dictionary of keyword-only parameters’ default values - * [Optional, flag = 0x1] A tuple of default values for positional-only and positional-or-keyword parameters in positional - * order - * BOTTOM - * - * All arguments are popped. A new instance of Class is created with the arguments and pushed to the stack. - */ - public static void createFunction(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - String className = functionMetadata.className; - - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - // Python 3.11 and above removed qualified name, so we need to get it from the code object's class - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonCode.class)); - methodVisitor.visitInsn(Opcodes.DUP); - // TODO: maybe create qualifiedName field in PythonCode? - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonCode.class), "functionClass", - Type.getDescriptor(Class.class)); - // TODO: get qualified name from static field? - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Class.class), "getName", - Type.getMethodDescriptor(Type.getType(String.class)), false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), "valueOf", - Type.getMethodDescriptor(Type.getType(PythonString.class), Type.getType(String.class)), false); - stackMetadata = stackMetadata.pushTemp(BuiltinTypes.STRING_TYPE); - } - int providedOptionalArgs = Integer.bitCount(instruction.arg()); - - // If the argument present, decrement providedOptionalArgs to keep argument shifting logic the same - // Ex: present, missing, present, present -> need to shift default for missing down by 4 = 2 + (3 - 1) - // Ex: present, present, missing, present -> need to shift default for missing down by 3 = 2 + (3 - 2) - // Ex: present, missing1, missing2, present -> need to shift default for missing1 down by 3 = 2 + (2 - 1), - // need to shift default for missing2 down by 3 = 2 + (2 - 1) - StackMetadata tempStackmetadata = stackMetadata; - - if ((instruction.arg() & 1) != 1) { - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - - tempStackmetadata = tempStackmetadata.pushTemp(BuiltinTypes.TUPLE_TYPE); - tempStackmetadata = - StackManipulationImplementor.shiftTOSDownBy(functionMetadata, tempStackmetadata, 2 + providedOptionalArgs); - } else { - providedOptionalArgs--; - } - - if ((instruction.arg() & 2) != 2) { - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - - tempStackmetadata = tempStackmetadata.pushTemp(BuiltinTypes.DICT_TYPE); - tempStackmetadata = - StackManipulationImplementor.shiftTOSDownBy(functionMetadata, tempStackmetadata, 2 + providedOptionalArgs); - } else { - providedOptionalArgs--; - } - - if ((instruction.arg() & 4) != 4) { - // In Python 3.10 and above, it a tuple of string; in 3.9 and below, a dict - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_10)) { - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - - tempStackmetadata = tempStackmetadata.pushTemp(BuiltinTypes.DICT_TYPE); - tempStackmetadata = StackManipulationImplementor.shiftTOSDownBy(functionMetadata, tempStackmetadata, - 2 + providedOptionalArgs); - - } else { - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - - tempStackmetadata = tempStackmetadata.pushTemp(BuiltinTypes.TUPLE_TYPE); - tempStackmetadata = StackManipulationImplementor.shiftTOSDownBy(functionMetadata, tempStackmetadata, - 2 + providedOptionalArgs); - } - } else { - providedOptionalArgs--; - } - - if ((instruction.arg() & 8) != 8) { - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - - tempStackmetadata = tempStackmetadata.pushTemp(BuiltinTypes.TUPLE_TYPE); - tempStackmetadata = - StackManipulationImplementor.shiftTOSDownBy(functionMetadata, tempStackmetadata, 2 + providedOptionalArgs); - } - - // Stack is now: - // default positional args, default keyword args, annotation directory tuple, cell tuple, function class, function name - - // Do type casts for name string and code object - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonCode.class)); - - // Pass the current function's interpreter to the new function instance - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - // Need to change constructor depending on Python version - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_10)) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(FunctionImplementor.class), - "createInstance", Type.getMethodDescriptor(Type.getType(PythonLikeFunction.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonCode.class), - Type.getType(PythonInterpreter.class)), - false); - } else { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(FunctionImplementor.class), - "createInstance", Type.getMethodDescriptor(Type.getType(PythonLikeFunction.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeDict.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonLikeTuple.class), - Type.getType(PythonString.class), - Type.getType(PythonCode.class), - Type.getType(PythonInterpreter.class)), - false); - } - } - - // For Python 3.9 and below - @SuppressWarnings("unused") - public static PythonLikeFunction createInstance(PythonLikeTuple defaultPositionalArgs, - PythonLikeDict defaultKeywordArgs, - PythonLikeDict annotationDict, - PythonLikeTuple closure, - PythonString functionName, - PythonCode code, - PythonInterpreter pythonInterpreter) { - return createInstance(defaultPositionalArgs, defaultKeywordArgs, annotationDict.toFlattenKeyValueTuple(), - closure, functionName, code.functionClass, pythonInterpreter); - } - - // For Python 3.10 and above - @SuppressWarnings("unused") - public static PythonLikeFunction createInstance(PythonLikeTuple defaultPositionalArgs, - PythonLikeDict defaultKeywordArgs, - PythonLikeTuple annotationTuple, - PythonLikeTuple closure, - PythonString functionName, - PythonCode code, - PythonInterpreter pythonInterpreter) { - return createInstance(defaultPositionalArgs, defaultKeywordArgs, annotationTuple, closure, functionName, - code.functionClass, pythonInterpreter); - } - - public static T createInstance(PythonLikeTuple defaultPositionalArgs, - PythonLikeDict defaultKeywordArgs, - PythonLikeTuple annotationTuple, - PythonLikeTuple closure, - PythonString functionName, - Class functionClass, - PythonInterpreter pythonInterpreter) { - PythonLikeDict annotationDirectory = new PythonLikeDict(); - for (int i = 0; i < (annotationTuple.size() >> 1); i++) { - annotationDirectory.put(annotationTuple.get(i * 2), annotationTuple.get(i * 2 + 1)); - } - - try { - Constructor constructor = functionClass.getConstructor(PythonLikeTuple.class, - PythonLikeDict.class, - PythonLikeDict.class, - PythonLikeTuple.class, - PythonString.class, - PythonInterpreter.class); - return (T) constructor.newInstance(defaultPositionalArgs, defaultKeywordArgs, annotationDirectory, closure, - functionName, pythonInterpreter); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - public static class TupleMapPair { - public PythonLikeTuple tuple; - public PythonLikeDict map; - - List mapKeyTuple; - - final int totalNumberOfPositionalAndKeywordArguments; - - public TupleMapPair(int itemsToPop) { - tuple = null; // Tuple is created when we know how many items are in it - mapKeyTuple = null; // mapKeyTuple is the first item reverseAdded - map = new PythonLikeDict(); - this.totalNumberOfPositionalAndKeywordArguments = itemsToPop - 1; - } - - public void reverseAdd(PythonLikeObject object) { - if (mapKeyTuple == null) { - mapKeyTuple = (List) object; - tuple = new PythonLikeTuple(totalNumberOfPositionalAndKeywordArguments - mapKeyTuple.size()); - return; - } - - if (map.size() < mapKeyTuple.size()) { - map.put(mapKeyTuple.get(mapKeyTuple.size() - map.size() - 1), object); - } else { - tuple.reverseAdd(object); - } - } - } - -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/GeneratorImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/GeneratorImplementor.java deleted file mode 100644 index 45620650..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/GeneratorImplementor.java +++ /dev/null @@ -1,362 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.jpyinterpreter.BytecodeSwitchImplementor; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonGeneratorTranslator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.PythonGenerator; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class GeneratorImplementor { - - public static void restoreGeneratorState(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, functionMetadata.className, PythonGeneratorTranslator.GENERATOR_STACK, - Type.getDescriptor(List.class)); - - for (int i = stackMetadata.getStackSize() - 1; i >= 0; i--) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, stackMetadata.getTypeAtStackIndex(i).getJavaTypeInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - } - - private static void saveGeneratorState(PythonBytecodeInstruction instruction, FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Store stack in generatorStack - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(ArrayList.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(stackMetadata.getStackSize()); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.GENERATOR_STACK, - Type.getDescriptor(List.class)); - - for (int i = 0; i < stackMetadata.getStackSize() - 1; i++) { - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true); - methodVisitor.visitInsn(Opcodes.POP); // Do not use return value of add - } - methodVisitor.visitInsn(Opcodes.POP); - - // Set the generator state - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitLdcInsn(instruction.offset() + 1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.GENERATOR_STATE, - Type.INT_TYPE.getDescriptor()); - } - - public static void yieldValue(PythonBytecodeInstruction instruction, FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // First, store TOS in yieldedValue - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - - // Next, save stack and generator position - saveGeneratorState(instruction, functionMetadata, stackMetadata); - - // return control to the caller - methodVisitor.visitInsn(Opcodes.RETURN); - } - - public static void yieldFrom(PythonBytecodeInstruction instruction, FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // TODO: Find out what TOS, which is usually None, is used for - - // Pop TOS (Unknown what is used for) - methodVisitor.visitInsn(Opcodes.POP); - - // Store the subiterator into yieldFromIterator - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - // Save stack and position - // Store stack in both locals and fields, just in case the iterator stops iteration immediately - int[] storedStack = StackManipulationImplementor.storeStack(methodVisitor, stackMetadata.pop(2)); - saveGeneratorState(instruction, functionMetadata, stackMetadata.pop(2)); - - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label catchStartLabel = new Label(); - Label catchEndLabel = new Label(); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, catchStartLabel, - Type.getInternalName(StopIteration.class)); - - methodVisitor.visitLabel(tryStartLabel); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, functionMetadata.className, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - methodVisitor.visitInsn(Opcodes.RETURN); // subiterator yielded something; return control to caller - - methodVisitor.visitLabel(tryEndLabel); - - methodVisitor.visitLabel(catchStartLabel); - methodVisitor.visitInsn(Opcodes.POP); // pop the StopIteration exception - methodVisitor.visitLabel(catchEndLabel); - - // Set yieldFromIterator to null since it is finished - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - // Restore the stack, since subiterator was empty, and resume execution - StackManipulationImplementor.restoreStack(methodVisitor, stackMetadata.pop(2), storedStack); - - // Since the subiterator was empty, push None to TOS - PythonConstantsImplementor.loadNone(methodVisitor); - } - - public static void progressSubgenerator(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - int[] stackVariables = StackManipulationImplementor.storeStack(methodVisitor, stackMetadata); - - Label wasNotSentValue = new Label(); - Label wasNotThrownValue = new Label(); - Label iterateSubiterator = new Label(); - - // Stack is subgenerator, sentValue - // Duplicate subgenerator - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Duplicate sent value - methodVisitor.visitInsn(Opcodes.DUP); - - // Check if sent a value - PythonConstantsImplementor.loadNone(methodVisitor); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, wasNotSentValue); - - methodVisitor.visitLdcInsn(1); - methodVisitor.visitJumpInsn(Opcodes.GOTO, iterateSubiterator); - - methodVisitor.visitLabel(wasNotSentValue); - - // Check if thrown a value - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonGenerator.class), "thrownValue", - Type.getDescriptor(Throwable.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, wasNotThrownValue); - - methodVisitor.visitLdcInsn(2); - methodVisitor.visitJumpInsn(Opcodes.GOTO, iterateSubiterator); - - methodVisitor.visitLabel(wasNotThrownValue); - - // Else, should call next so it will also works with iterables - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitLdcInsn(0); - - methodVisitor.visitLabel(iterateSubiterator); - // Stack is subgenerator, sent/thrownValue, switchCaseLabel - - Label tryStartLabel = new Label(); - Label tryEndLabel = new Label(); - Label catchStartLabel = new Label(); - Label catchEndLabel = new Label(); - - methodVisitor.visitTryCatchBlock(tryStartLabel, tryEndLabel, catchStartLabel, - Type.getInternalName(StopIteration.class)); - - methodVisitor.visitLabel(tryStartLabel); - BytecodeSwitchImplementor.createIntSwitch(methodVisitor, List.of(0, 1, 2), - key -> { - Label generatorOperationDone = new Label(); - switch (key) { - case 0: { // next - methodVisitor.visitLdcInsn("next"); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitInsn(Opcodes.POP); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.NEXT); - break; - } - case 1: { // send - methodVisitor.visitLdcInsn("send"); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonGenerator.class), - "sentValue", - Type.getDescriptor(PythonLikeObject.class)); - FunctionImplementor.callBinaryMethod(methodVisitor, - PythonBinaryOperator.SEND.dunderMethod); - break; - } - case 2: { // throw - methodVisitor.visitLdcInsn("throw"); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Throwable.class)); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonGenerator.class), - "thrownValue", - Type.getDescriptor(Throwable.class)); - - methodVisitor.visitInsn(Opcodes.SWAP); - // Stack is now Throwable, Generator - - // Check if the subgenerator has a "throw" method - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitLdcInsn("throw"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrNull", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(String.class)), - true); - - // Stack is now Throwable, Generator, maybeMethod - Label ifThrowMethodPresent = new Label(); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifThrowMethodPresent); - - // does not have a throw method - // Set yieldFromIterator to null since it is finished - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, - PythonGeneratorTranslator.YIELD_FROM_ITERATOR, - Type.getDescriptor(PythonLikeObject.class)); - - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(ifThrowMethodPresent); - - // Swap so it Generator, Throwable instead of Throwable, Generator - methodVisitor.visitInsn(Opcodes.SWAP); - FunctionImplementor.callBinaryMethod(methodVisitor, - PythonBinaryOperator.THROW.dunderMethod); - break; - } - } - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - }, () -> { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalStateException.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, - Type.getInternalName(IllegalStateException.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitInsn(Opcodes.ATHROW); - }, false); - - methodVisitor.visitLabel(tryEndLabel); - methodVisitor.visitJumpInsn(Opcodes.GOTO, catchEndLabel); - - methodVisitor.visitLabel(catchStartLabel); - - methodVisitor.visitInsn(Opcodes.POP); // pop the StopIteration exception - StackManipulationImplementor.restoreStack(methodVisitor, stackMetadata, stackVariables); - JumpImplementor.jumpAbsolute(functionMetadata, stackMetadata, jumpTarget); - - methodVisitor.visitLabel(catchEndLabel); - } - - public static void getYieldFromIter(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - Label isGeneratorOrCoroutine = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(PythonGenerator.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, isGeneratorOrCoroutine); - - // not a generator/coroutine - DunderOperatorImplementor.unaryOperator(methodVisitor, stackMetadata, PythonUnaryOperator.ITERATOR); - - methodVisitor.visitLabel(isGeneratorOrCoroutine); - } - - public static void endGenerator(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // First, store TOS in yieldedValue - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.YIELDED_VALUE, - Type.getDescriptor(PythonLikeObject.class)); - - // Next, set generatorStack to null - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.GENERATOR_STACK, - Type.getDescriptor(List.class)); - - // Set the generator state - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitLdcInsn(-1); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, functionMetadata.className, PythonGeneratorTranslator.GENERATOR_STATE, - Type.INT_TYPE.getDescriptor()); - - methodVisitor.visitInsn(Opcodes.RETURN); - } - - public static void generatorStart(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitInsn(Opcodes.POP); // Despite stackMetadata says it empty, the stack actually has - // one item: the first sent item, which MUST BE None - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaComparableImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaComparableImplementor.java deleted file mode 100644 index fec494b4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaComparableImplementor.java +++ /dev/null @@ -1,192 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Modifier; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledClass; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.NotImplementedError; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class JavaComparableImplementor extends JavaInterfaceImplementor { - final String internalClassName; - final CompareOp compareOp; - - public JavaComparableImplementor(String internalClassName, String method) { - this.internalClassName = internalClassName; - this.compareOp = CompareOp.getOpForDunderMethod(method); - switch (compareOp) { - case LESS_THAN: - case LESS_THAN_OR_EQUALS: - case GREATER_THAN: - case GREATER_THAN_OR_EQUALS: - break; - default: - throw new IllegalStateException("Cannot use " + method + " for comparisons"); - } - } - - @Override - public Class getInterfaceClass() { - return Comparable.class; - } - - private void typeCheck(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, internalClassName); - - Label isInstanceOfClass = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, isInstanceOfClass); - - // Throw an exception since the argument is not a Python Like Object - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(NotImplementedError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn("compareTo arg 0 is not an instance of " + internalClassName); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(NotImplementedError.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - - methodVisitor.visitLabel(isInstanceOfClass); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, internalClassName); - } - - @Override - public void implement(ClassWriter classWriter, PythonCompiledClass compiledClass) { - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "compareTo", - Type.getMethodDescriptor(Type.INT_TYPE, - Type.getType(Object.class)), - null, - null); - - methodVisitor.visitParameter("other", 0); - methodVisitor.visitCode(); - - switch (compareOp) { - case LESS_THAN: - implementCompareToWithLessThan(methodVisitor, compiledClass); - break; - case LESS_THAN_OR_EQUALS: - implementCompareToWithLessThanOrEqual(methodVisitor, compiledClass); - break; - case GREATER_THAN: - implementCompareToWithGreaterThan(methodVisitor, compiledClass); - break; - case GREATER_THAN_OR_EQUALS: - implementCompareToWithGreaterThanOrEqual(methodVisitor, compiledClass); - break; - default: - throw new IllegalStateException("Impossible state: " + compareOp + " is not a comparison operator"); - } - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } - - // The following code exploit these fact: - // a < b == a < b - // a > b == b < a - // !(a >= b) == a < b - // !(a <= b) == a > b == b < a - private void implementCompareToWithLessThan(MethodVisitor methodVisitor, PythonCompiledClass pythonCompiledClass) { - PythonCompiledFunction comparisonFunction = pythonCompiledClass.instanceFunctionNameToPythonBytecode.get("__lt__"); - String comparisonMethodName = PythonClassTranslator.getJavaMethodName("__lt__"); - - implementCompareTo(methodVisitor, comparisonFunction, comparisonMethodName, false, true); - } - - private void implementCompareToWithGreaterThan(MethodVisitor methodVisitor, PythonCompiledClass pythonCompiledClass) { - PythonCompiledFunction comparisonFunction = pythonCompiledClass.instanceFunctionNameToPythonBytecode.get("__gt__"); - String comparisonMethodName = PythonClassTranslator.getJavaMethodName("__gt__"); - - implementCompareTo(methodVisitor, comparisonFunction, comparisonMethodName, false, false); - } - - private void implementCompareToWithLessThanOrEqual(MethodVisitor methodVisitor, PythonCompiledClass pythonCompiledClass) { - PythonCompiledFunction comparisonFunction = pythonCompiledClass.instanceFunctionNameToPythonBytecode.get("__le__"); - String comparisonMethodName = PythonClassTranslator.getJavaMethodName("__le__"); - - implementCompareTo(methodVisitor, comparisonFunction, comparisonMethodName, true, false); - } - - private void implementCompareToWithGreaterThanOrEqual(MethodVisitor methodVisitor, - PythonCompiledClass pythonCompiledClass) { - PythonCompiledFunction comparisonFunction = pythonCompiledClass.instanceFunctionNameToPythonBytecode.get("__ge__"); - String comparisonMethodName = PythonClassTranslator.getJavaMethodName("__ge__"); - - implementCompareTo(methodVisitor, comparisonFunction, comparisonMethodName, true, true); - } - - /** - * Create this code: - * - *
-     * if (self < other) == not negateComparisionResult:
-     *     return isLessThan? -1 : 1
-     * elif (other < self) == True:
-     *     return isLessThan? 1 : -1
-     * else:
-     *     return 0
-     * 
- * - * The negateComparisonResult turns __ge__ and __le__ into __lt__ and __gt__ respectively. - * isLessThan reverses the comparison if false. - * - */ - private void implementCompareTo(MethodVisitor methodVisitor, PythonCompiledFunction comparisonFunction, - String comparisonMethodName, boolean negateComparisionResult, - boolean isLessThan) { - PythonLikeType parameterType = comparisonFunction.getParameterTypes().get(1); - Type returnType = PythonClassTranslator.getVirtualFunctionReturnType(comparisonFunction); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - typeCheck(methodVisitor); - - methodVisitor.visitInsn(Opcodes.DUP2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, comparisonMethodName, - Type.getMethodDescriptor(returnType, Type.getType(parameterType.getJavaTypeDescriptor())), - false); - - Label ifSelfNotLessThanOther = new Label(); - - if (negateComparisionResult) { - PythonConstantsImplementor.loadFalse(methodVisitor); - } else { - PythonConstantsImplementor.loadTrue(methodVisitor); - } - - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifSelfNotLessThanOther); - - methodVisitor.visitInsn(isLessThan ? Opcodes.ICONST_M1 : Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitLabel(ifSelfNotLessThanOther); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, comparisonMethodName, - Type.getMethodDescriptor(returnType, Type.getType(parameterType.getJavaTypeDescriptor())), - false); - - Label ifOtherNotLessThanSelf = new Label(); - if (negateComparisionResult) { - PythonConstantsImplementor.loadFalse(methodVisitor); - } else { - PythonConstantsImplementor.loadTrue(methodVisitor); - } - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifOtherNotLessThanSelf); - - methodVisitor.visitInsn(isLessThan ? Opcodes.ICONST_1 : Opcodes.ICONST_M1); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitLabel(ifOtherNotLessThanSelf); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaEqualsImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaEqualsImplementor.java deleted file mode 100644 index b3f5959d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaEqualsImplementor.java +++ /dev/null @@ -1,81 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Modifier; - -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledClass; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class JavaEqualsImplementor extends JavaInterfaceImplementor { - final String internalClassName; - - public JavaEqualsImplementor(String internalClassName) { - this.internalClassName = internalClassName; - } - - @Override - public Class getInterfaceClass() { - return Object.class; - } - - private void typeCheck(MethodVisitor methodVisitor, PythonLikeType parameterType) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, parameterType.getJavaTypeInternalName()); - - Label isInstanceOfClass = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, isInstanceOfClass); - - // Return false since the objects cannot be compared for equals - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); - - // Cast - methodVisitor.visitLabel(isInstanceOfClass); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, parameterType.getJavaTypeInternalName()); - } - - @Override - public void implement(ClassWriter classWriter, PythonCompiledClass compiledClass) { - PythonCompiledFunction comparisonFunction = compiledClass.instanceFunctionNameToPythonBytecode.get("__eq__"); - PythonLikeType parameterType = comparisonFunction.getParameterTypes().get(1); - String methodName = PythonClassTranslator.getJavaMethodName("__eq__"); - Type returnType = PythonClassTranslator.getVirtualFunctionReturnType(comparisonFunction); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "equals", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, - Type.getType(Object.class)), - null, - null); - - methodVisitor.visitParameter("other", 0); - methodVisitor.visitCode(); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - typeCheck(methodVisitor, parameterType); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, methodName, - Type.getMethodDescriptor(returnType, Type.getType(parameterType.getJavaTypeDescriptor())), - false); - - Label ifNotEquals = new Label(); - PythonConstantsImplementor.loadTrue(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifNotEquals); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitLabel(ifNotEquals); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaHashCodeImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaHashCodeImplementor.java deleted file mode 100644 index eeaa4dbc..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaHashCodeImplementor.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Modifier; - -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledClass; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class JavaHashCodeImplementor extends JavaInterfaceImplementor { - final String internalClassName; - - public JavaHashCodeImplementor(String internalClassName) { - this.internalClassName = internalClassName; - } - - @Override - public Class getInterfaceClass() { - return Object.class; - } - - @Override - public void implement(ClassWriter classWriter, PythonCompiledClass compiledClass) { - PythonCompiledFunction comparisonFunction = compiledClass.instanceFunctionNameToPythonBytecode.get("__hash__"); - String methodName = PythonClassTranslator.getJavaMethodName("__hash__"); - Type returnType = PythonClassTranslator.getVirtualFunctionReturnType(comparisonFunction); - - MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "hashCode", - Type.getMethodDescriptor(Type.INT_TYPE), - null, - null); - - methodVisitor.visitCode(); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, methodName, - Type.getMethodDescriptor(returnType), - false); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), "hashCode", - Type.getMethodDescriptor(Type.INT_TYPE), - false); - - methodVisitor.visitInsn(Opcodes.IRETURN); - - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaInterfaceImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaInterfaceImplementor.java deleted file mode 100644 index 440d02d4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaInterfaceImplementor.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import ai.timefold.jpyinterpreter.PythonCompiledClass; - -import org.objectweb.asm.ClassWriter; - -public abstract class JavaInterfaceImplementor { - public abstract Class getInterfaceClass(); - - public abstract void implement(ClassWriter classWriter, PythonCompiledClass compiledClass); - - @Override - public final boolean equals(Object o) { - if (!(o instanceof JavaInterfaceImplementor)) { - return false; - } - - if (getInterfaceClass().equals(Object.class)) { - return getClass().equals(o.getClass()); - } - return getInterfaceClass().equals(((JavaInterfaceImplementor) o).getInterfaceClass()); - } - - @Override - public final int hashCode() { - return getInterfaceClass().hashCode(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java deleted file mode 100644 index ba302add..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java +++ /dev/null @@ -1,571 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.lang.reflect.Field; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.Coercible; -import ai.timefold.jpyinterpreter.types.PythonByteArray; -import ai.timefold.jpyinterpreter.types.PythonBytes; -import ai.timefold.jpyinterpreter.types.PythonCode; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeFrozenSet; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.types.wrappers.JavaObjectWrapper; -import ai.timefold.jpyinterpreter.types.wrappers.OpaqueJavaReference; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; -import ai.timefold.jpyinterpreter.types.wrappers.PythonObjectWrapper; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes and operations that require Java to Python or Python to Java conversions. - */ -public class JavaPythonTypeConversionImplementor { - - /** - * Wraps {@code object} to a PythonLikeObject. - */ - public static PythonLikeObject wrapJavaObject(Object object) { - return wrapJavaObject(object, new IdentityHashMap<>()); - } - - public static PythonLikeObject wrapJavaObject(Object object, Map createdObjectMap) { - if (object == null) { - return PythonNone.INSTANCE; - } - - var existingObject = createdObjectMap.get(object); - if (existingObject != null) { - return existingObject; - } - - if (object instanceof OpaqueJavaReference opaqueJavaReference) { - return opaqueJavaReference.proxy(); - } - - if (object instanceof PythonLikeObject instance) { - // Object already a PythonLikeObject; need to do nothing - return instance; - } - - if (object instanceof Byte || object instanceof Short || object instanceof Integer || object instanceof Long) { - return PythonInteger.valueOf(((Number) object).longValue()); - } - - if (object instanceof BigInteger integer) { - return PythonInteger.valueOf(integer); - } - - if (object instanceof BigDecimal decimal) { - return new PythonDecimal(decimal); - } - - if (object instanceof Float || object instanceof Double) { - return PythonFloat.valueOf(((Number) object).doubleValue()); - } - - if (object instanceof Boolean booleanValue) { - return PythonBoolean.valueOf(booleanValue); - } - - if (object instanceof String string) { - return PythonString.valueOf(string); - } - - if (object instanceof Iterator iterator) { - return new DelegatePythonIterator<>(iterator); - } - - if (object instanceof List list) { - PythonLikeList out = new PythonLikeList<>(); - createdObjectMap.put(object, out); - for (Object item : list) { - out.add(wrapJavaObject(item)); - } - return out; - } - - if (object instanceof Set set) { - PythonLikeSet out = new PythonLikeSet<>(); - createdObjectMap.put(object, out); - for (Object item : set) { - out.add(wrapJavaObject(item)); - } - return out; - } - - if (object instanceof Map map) { - PythonLikeDict out = new PythonLikeDict<>(); - createdObjectMap.put(object, out); - var entrySet = map.entrySet(); - for (var entry : entrySet) { - out.put(wrapJavaObject(entry.getKey()), wrapJavaObject(entry.getValue())); - } - return out; - } - - if (object instanceof Class maybeFunctionClass && - Set.of(maybeFunctionClass.getInterfaces()).contains(PythonLikeFunction.class)) { - return new PythonCode((Class) maybeFunctionClass); - } - - if (object instanceof OpaquePythonReference opaquePythonReference) { - return new PythonObjectWrapper(opaquePythonReference); - } - - // Default: return a JavaObjectWrapper - return new JavaObjectWrapper(object, createdObjectMap); - } - - /** - * Get the {@link PythonLikeType} of a java {@link Class}. - */ - public static PythonLikeType getPythonLikeType(Class javaClass) { - if (PythonNone.class.equals(javaClass)) { - return BuiltinTypes.NONE_TYPE; - } - - if (PythonLikeObject.class.equals(javaClass)) { - return BuiltinTypes.BASE_TYPE; - } - - if (byte.class.equals(javaClass) || short.class.equals(javaClass) || int.class.equals(javaClass) - || long.class.equals(javaClass) || - Byte.class.equals(javaClass) || Short.class.equals(javaClass) || Integer.class.equals(javaClass) - || Long.class.equals(javaClass) || BigInteger.class.equals(javaClass) || - PythonInteger.class.equals(javaClass)) { - return BuiltinTypes.INT_TYPE; - } - - if (BigDecimal.class.equals(javaClass) || PythonDecimal.class.equals(javaClass)) { - return BuiltinTypes.DECIMAL_TYPE; - } - - if (float.class.equals(javaClass) || double.class.equals(javaClass) || - Float.class.equals(javaClass) || Double.class.equals(javaClass) || - PythonFloat.class.equals(javaClass)) { - return BuiltinTypes.FLOAT_TYPE; - } - - if (PythonNumber.class.equals(javaClass)) { - return BuiltinTypes.NUMBER_TYPE; - } - - if (boolean.class.equals(javaClass) || - Boolean.class.equals(javaClass) || - PythonBoolean.class.equals(javaClass)) { - return BuiltinTypes.BOOLEAN_TYPE; - } - - if (String.class.equals(javaClass) || - PythonString.class.equals(javaClass)) { - return BuiltinTypes.STRING_TYPE; - } - - if (PythonBytes.class.equals(javaClass)) { - return BuiltinTypes.BYTES_TYPE; - } - - if (PythonByteArray.class.equals(javaClass)) { - return BuiltinTypes.BYTE_ARRAY_TYPE; - } - - if (Iterator.class.equals(javaClass) || - PythonIterator.class.equals(javaClass)) { - return BuiltinTypes.ITERATOR_TYPE; - } - - if (List.class.equals(javaClass) || - PythonLikeList.class.equals(javaClass)) { - return BuiltinTypes.LIST_TYPE; - } - - if (PythonLikeTuple.class.equals(javaClass)) { - return BuiltinTypes.TUPLE_TYPE; - } - - if (Set.class.equals(javaClass) || - PythonLikeSet.class.equals(javaClass)) { - return BuiltinTypes.SET_TYPE; - } - - if (PythonLikeFrozenSet.class.equals(javaClass)) { - return BuiltinTypes.FROZEN_SET_TYPE; - } - - if (Map.class.equals(javaClass) || - PythonLikeDict.class.equals(javaClass)) { - return BuiltinTypes.DICT_TYPE; - } - - if (PythonLikeType.class.equals(javaClass)) { - return BuiltinTypes.TYPE_TYPE; - } - - try { - Field typeField = javaClass.getField(PythonClassTranslator.TYPE_FIELD_NAME); - Object maybeType = typeField.get(null); - if (maybeType instanceof PythonLikeType) { - return (PythonLikeType) maybeType; - } - if (PythonLikeFunction.class.isAssignableFrom(javaClass)) { - return PythonLikeFunction.getFunctionType(); - } - return JavaObjectWrapper.getPythonTypeForClass(javaClass); - } catch (NoSuchFieldException | IllegalAccessException e) { - if (PythonLikeFunction.class.isAssignableFrom(javaClass)) { - return PythonLikeFunction.getFunctionType(); - } - return JavaObjectWrapper.getPythonTypeForClass(javaClass); - } - } - - /** - * Converts a {@code PythonLikeObject} to the given {@code type}. - */ - @SuppressWarnings("unchecked") - public static T convertPythonObjectToJavaType(Class type, PythonLikeObject object) { - if (object == null || type.isAssignableFrom(object.getClass())) { - // Can directly assign; no modification needed - return (T) object; - } - - if (object instanceof PythonNone) { - return null; - } - - if (object instanceof JavaObjectWrapper wrappedObject) { - Object javaObject = wrappedObject.getWrappedObject(); - if (!type.isAssignableFrom(javaObject.getClass())) { - throw new TypeError("Cannot convert from (" + getPythonLikeType(javaObject.getClass()) + ") to (" - + getPythonLikeType(type) + ")."); - } - return (T) javaObject; - } - - if (type.equals(byte.class) || type.equals(short.class) || type.equals(int.class) || type.equals(long.class) || - type.equals(float.class) || type.equals(double.class) || Number.class.isAssignableFrom(type)) { - if (!(object instanceof PythonNumber pythonNumber)) { - throw new TypeError("Cannot convert from (" + getPythonLikeType(object.getClass()) + ") to (" - + getPythonLikeType(type) + ")."); - } - Number value = pythonNumber.getValue(); - - if (type.equals(BigInteger.class) || type.equals(BigDecimal.class)) { - return (T) value; - } - - if (type.equals(byte.class) || type.equals(Byte.class)) { - return (T) (Byte) value.byteValue(); - } - - if (type.equals(short.class) || type.equals(Short.class)) { - return (T) (Short) value.shortValue(); - } - - if (type.equals(int.class) || type.equals(Integer.class)) { - return (T) (Integer) value.intValue(); - } - - if (type.equals(long.class) || type.equals(Long.class)) { - return (T) (Long) value.longValue(); - } - - if (type.equals(float.class) || type.equals(Float.class)) { - return (T) (Float) value.floatValue(); - } - - if (type.equals(double.class) || type.equals(Double.class)) { - return (T) (Double) value.doubleValue(); - } - } - - if (type.equals(boolean.class) || type.equals(Boolean.class)) { - if (!(object instanceof PythonBoolean pythonBoolean)) { - throw new TypeError("Cannot convert from (" + getPythonLikeType(object.getClass()) + ") to (" - + getPythonLikeType(type) + ")."); - } - return (T) (Boolean) pythonBoolean.getBooleanValue(); - } - - if (type.equals(String.class)) { - PythonString pythonString = (PythonString) object; - return (T) pythonString.getValue(); - } - - // TODO: List, Map, Set - - throw new TypeError( - "Cannot convert from (" + getPythonLikeType(object.getClass()) + ") to (" + getPythonLikeType(type) + ")."); - } - - /** - * Loads a String and push it onto the stack - * - * @param name The name to load - */ - public static void loadName(MethodVisitor methodVisitor, String name) { - methodVisitor.visitLdcInsn(name); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), - "valueOf", - Type.getMethodDescriptor(Type.getType(PythonString.class), Type.getType(PythonString.class)), - false); - } - - private record ReturnValueOpDescriptor( - String wrapperClassName, - String methodName, - String methodDescriptor, - int opcode, - boolean noConversionNeeded) { - public static ReturnValueOpDescriptor noConversion() { - return new ReturnValueOpDescriptor("", "", "", - Opcodes.ARETURN, true); - } - - public static ReturnValueOpDescriptor forNumeric(String methodName, - String methodDescriptor, - int opcode) { - return new ReturnValueOpDescriptor(Type.getInternalName(Number.class), methodName, methodDescriptor, opcode, - false); - } - } - - private static final Map numericReturnValueOpDescriptorMap = Map.of( - Type.BYTE_TYPE, ReturnValueOpDescriptor.forNumeric( - "byteValue", - Type.getMethodDescriptor(Type.BYTE_TYPE), - Opcodes.IRETURN), - Type.SHORT_TYPE, ReturnValueOpDescriptor.forNumeric( - "shortValue", - Type.getMethodDescriptor(Type.SHORT_TYPE), - Opcodes.IRETURN), - Type.INT_TYPE, ReturnValueOpDescriptor.forNumeric( - "intValue", - Type.getMethodDescriptor(Type.INT_TYPE), - Opcodes.IRETURN), - Type.LONG_TYPE, ReturnValueOpDescriptor.forNumeric( - "longValue", - Type.getMethodDescriptor(Type.LONG_TYPE), - Opcodes.LRETURN), - Type.FLOAT_TYPE, ReturnValueOpDescriptor.forNumeric( - "floatValue", - Type.getMethodDescriptor(Type.FLOAT_TYPE), - Opcodes.FRETURN), - Type.DOUBLE_TYPE, ReturnValueOpDescriptor.forNumeric( - "doubleValue", - Type.getMethodDescriptor(Type.DOUBLE_TYPE), - Opcodes.DRETURN), - Type.getType(BigInteger.class), ReturnValueOpDescriptor.noConversion(), - Type.getType(BigDecimal.class), ReturnValueOpDescriptor.noConversion()); - - /** - * If {@code method} return type is not void, convert TOS into its Java equivalent and return it. - * If {@code method} return type is void, immediately return. - * - * @param method The method that is being implemented. - */ - public static void returnValue(MethodVisitor methodVisitor, MethodDescriptor method, StackMetadata stackMetadata) { - Type returnAsmType = method.getReturnType(); - - if (Type.CHAR_TYPE.equals(returnAsmType)) { - throw new IllegalStateException("Unhandled case for primitive type (char)."); - } - - if (Type.VOID_TYPE.equals(returnAsmType)) { - methodVisitor.visitInsn(Opcodes.RETURN); - return; - } - - if (numericReturnValueOpDescriptorMap.containsKey(returnAsmType)) { - var returnValueOpDescriptor = numericReturnValueOpDescriptorMap.get(returnAsmType); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonNumber.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, - Type.getInternalName(PythonNumber.class), - "getValue", - Type.getMethodDescriptor(Type.getType(Number.class)), - true); - - if (returnValueOpDescriptor.noConversionNeeded) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnAsmType.getInternalName()); - methodVisitor.visitInsn(Opcodes.ARETURN); - return; - } - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - returnValueOpDescriptor.wrapperClassName, - returnValueOpDescriptor.methodName, - returnValueOpDescriptor.methodDescriptor, - false); - methodVisitor.visitInsn(returnValueOpDescriptor.opcode); - return; - } - - if (Type.BOOLEAN_TYPE.equals(returnAsmType)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonBoolean.class)); - String wrapperClassName = Type.getInternalName(PythonBoolean.class); - String methodName = "getBooleanValue"; - String methodDescriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE); - int returnOpcode = Opcodes.IRETURN; - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - wrapperClassName, methodName, methodDescriptor, - false); - methodVisitor.visitInsn(returnOpcode); - return; - } - - try { - Class returnTypeClass = - Class.forName(returnAsmType.getClassName(), true, BuiltinTypes.asmClassLoader); - - if (stackMetadata.getTOSType() == null) { - throw new IllegalStateException("Cannot return a deleted or undefined value"); - } - Class tosTypeClass = stackMetadata.getTOSType().getJavaClass(); - if (returnTypeClass.isAssignableFrom(tosTypeClass)) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnAsmType.getInternalName()); - methodVisitor.visitInsn(Opcodes.ARETURN); - return; - } - } catch (ClassNotFoundException e) { - // Do nothing; default case is below - } - - methodVisitor.visitLdcInsn(returnAsmType); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "convertPythonObjectToJavaType", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Class.class), - Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnAsmType.getInternalName()); - methodVisitor.visitInsn(Opcodes.ARETURN); - } - - /** - * Coerce a value to a given type - */ - public static T coerceToType(PythonLikeObject value, Class type) { - if (value == null) { - return null; - } - - if (type.isAssignableFrom(value.getClass())) { - return (T) value; - } - - if (value instanceof Coercible coercible) { - var out = coercible.coerce(type); - if (out == null) { - throw new TypeError("%s cannot be coerced to %s." - .formatted(value.$getType(), type)); - } - return out; - } - - throw new TypeError("%s cannot be coerced to %s." - .formatted(value.$getType(), type)); - } - - /** - * Convert the {@code parameterIndex} Java parameter to its Python equivalent and store it into - * the corresponding Python parameter local variable slot. - */ - public static void copyParameter(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper, - int parameterIndex) { - Type parameterType = localVariableHelper.parameters[parameterIndex]; - if (parameterType.getSort() != Type.OBJECT && parameterType.getSort() != Type.ARRAY) { - int loadOpcode; - String valueOfOwner; - String valueOfDescriptor; - - if (Type.BOOLEAN_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.ILOAD; - valueOfOwner = Type.getInternalName(PythonBoolean.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonBoolean.class), Type.getType(boolean.class)); - } else if (Type.CHAR_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.ILOAD; - throw new IllegalStateException("Unhandled case for primitive type (" + parameterType + ")."); - } else if (Type.BYTE_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.ILOAD; - valueOfOwner = Type.getInternalName(PythonInteger.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonInteger.class), Type.getType(byte.class)); - } else if (Type.SHORT_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.ILOAD; - valueOfOwner = Type.getInternalName(PythonInteger.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonInteger.class), Type.getType(short.class)); - } else if (Type.INT_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.ILOAD; - valueOfOwner = Type.getInternalName(PythonInteger.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonInteger.class), Type.getType(int.class)); - } else if (Type.FLOAT_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.FLOAD; - valueOfOwner = Type.getInternalName(PythonFloat.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonFloat.class), Type.getType(float.class)); - } else if (Type.LONG_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.LLOAD; - valueOfOwner = Type.getInternalName(PythonInteger.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonInteger.class), Type.getType(long.class)); - } else if (Type.DOUBLE_TYPE.equals(parameterType)) { - loadOpcode = Opcodes.DLOAD; - valueOfOwner = Type.getInternalName(PythonFloat.class); - valueOfDescriptor = Type.getMethodDescriptor(Type.getType(PythonFloat.class), Type.getType(double.class)); - } else { - throw new IllegalStateException("Unhandled case for primitive type (" + parameterType + ")."); - } - - methodVisitor.visitVarInsn(loadOpcode, localVariableHelper.getParameterSlot(parameterIndex)); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, valueOfOwner, "valueOf", - valueOfDescriptor, false); - localVariableHelper.writeLocal(methodVisitor, parameterIndex); - } else { - try { - Class typeClass = Class.forName(parameterType.getClassName(), false, - BuiltinTypes.asmClassLoader); - if (!PythonLikeObject.class.isAssignableFrom(typeClass)) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, localVariableHelper.getParameterSlot(parameterIndex)); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "wrapJavaObject", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(Object.class)), - false); - localVariableHelper.writeLocal(methodVisitor, parameterIndex); - } else { - methodVisitor.visitVarInsn(Opcodes.ALOAD, localVariableHelper.getParameterSlot(parameterIndex)); - localVariableHelper.writeLocal(methodVisitor, parameterIndex); - } - } catch (ClassNotFoundException e) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, localVariableHelper.getParameterSlot(parameterIndex)); - localVariableHelper.writeLocal(methodVisitor, parameterIndex); - } - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JumpImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JumpImplementor.java deleted file mode 100644 index c37ab3e2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JumpImplementor.java +++ /dev/null @@ -1,141 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of jump opcodes - */ -public class JumpImplementor { - /** - * Set the bytecode counter to the {@code instruction} argument. - */ - public static void jumpAbsolute(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - methodVisitor.visitJumpInsn(Opcodes.GOTO, jumpLocation); - } - - /** - * Pops TOS. If TOS is true, set the bytecode counter to the {@code instruction} argument. - */ - public static void popAndJumpIfTrue(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - if (stackMetadata.getTOSType() != BuiltinTypes.BOOLEAN_TYPE) { - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.AS_BOOLEAN); - } - PythonConstantsImplementor.loadTrue(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, jumpLocation); - } - - /** - * Pops TOS. If TOS is false, set the bytecode counter to the {@code instruction} argument. - */ - public static void popAndJumpIfFalse(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - if (stackMetadata.getTOSType() != BuiltinTypes.BOOLEAN_TYPE) { - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.AS_BOOLEAN); - } - PythonConstantsImplementor.loadFalse(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, jumpLocation); - } - - /** - * Pops TOS. If TOS is not None, set the bytecode counter to {@code jumpTarget}. - */ - public static void popAndJumpIfIsNotNone(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, jumpLocation); - } - - /** - * Pops TOS. If TOS is None, set the bytecode counter to {@code jumpTarget}. - */ - public static void popAndJumpIfIsNone(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, jumpLocation); - } - - /** - * TOS is an exception type and TOS1 is an exception. - * If TOS1 is not an instance of TOS, set the bytecode counter to the - * {@code instruction} argument. - * Pop TOS and TOS1 off the stack. - */ - public static void popAndJumpIfExceptionDoesNotMatch(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeType.class)); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), - "isSubclassOf", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(PythonLikeType.class)), - false); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, jumpLocation); - } - - /** - * If TOS is true, keep TOS on the stack and set the bytecode counter to the {@code instruction} argument. - * Otherwise, pop TOS. - */ - public static void jumpIfTrueElsePop(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - methodVisitor.visitInsn(Opcodes.DUP); - if (stackMetadata.getTOSType() != BuiltinTypes.BOOLEAN_TYPE) { - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.AS_BOOLEAN); - } - PythonConstantsImplementor.loadTrue(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, jumpLocation); - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * If TOS is false, keep TOS on the stack and set the bytecode counter to the {@code instruction} argument. - * Otherwise, pop TOS. - */ - public static void jumpIfFalseElsePop(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int jumpTarget) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Map bytecodeCounterToLabelMap = functionMetadata.bytecodeCounterToLabelMap; - Label jumpLocation = bytecodeCounterToLabelMap.computeIfAbsent(jumpTarget, key -> new Label()); - methodVisitor.visitInsn(Opcodes.DUP); - if (stackMetadata.getTOSType() != BuiltinTypes.BOOLEAN_TYPE) { - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.AS_BOOLEAN); - } - PythonConstantsImplementor.loadFalse(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPEQ, jumpLocation); - methodVisitor.visitInsn(Opcodes.POP); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/KnownCallImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/KnownCallImplementor.java deleted file mode 100644 index b2ad3179..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/KnownCallImplementor.java +++ /dev/null @@ -1,514 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonDefaultArgumentImplementor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.BoundPythonLikeFunction; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implements function calls when the function being called is known. - */ -public class KnownCallImplementor { - - static void unwrapBoundMethod(PythonFunctionSignature pythonFunctionSignature, FunctionMetadata functionMetadata, - StackMetadata stackMetadata, int posFromTOS) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - if (pythonFunctionSignature.getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.VIRTUAL || - pythonFunctionSignature.getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.INTERFACE) { - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, posFromTOS); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - pythonFunctionSignature.getMethodDescriptor().getDeclaringClassInternalName()); - StackManipulationImplementor.shiftTOSDownBy(functionMetadata, stackMetadata, posFromTOS); - } - } - - public static void callMethod(PythonFunctionSignature pythonFunctionSignature, MethodVisitor methodVisitor, - LocalVariableHelper localVariableHelper, int argumentCount) { - if (pythonFunctionSignature.isClassMethod()) { - // Class methods will also have their type/instance on the stack, but it not in argumentCount - argumentCount++; - } - - int specPositionalArgumentCount = pythonFunctionSignature.getArgumentSpec().getAllowPositionalArgumentCount(); - int missingValues = Math.max(0, specPositionalArgumentCount - argumentCount); - - int[] argumentLocals = new int[specPositionalArgumentCount]; - int capturedExtraPositionalArgumentsLocal = localVariableHelper.newLocal(); - - // Create temporary variables for each argument - for (int i = 0; i < argumentLocals.length; i++) { - argumentLocals[i] = localVariableHelper.newLocal(); - } - - if (pythonFunctionSignature.getArgumentSpec().hasExtraPositionalArgumentsCapture()) { - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, - Math.max(0, argumentCount - specPositionalArgumentCount)); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeTuple.class), - capturedExtraPositionalArgumentsLocal); - } else if (argumentCount > specPositionalArgumentCount) { - throw new IllegalStateException( - "Too many positional arguments given for argument spec " + pythonFunctionSignature.getArgumentSpec()); - } - - // Call stack is in reverse, so TOS = argument (specPositionalArgumentCount - missingValues - 1) - // First store the variables into temporary local variables since we need to typecast them all - for (int i = specPositionalArgumentCount - missingValues - 1; i >= 0; i--) { - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), - argumentLocals[i]); - } - - if (pythonFunctionSignature.isVirtualMethod()) { - // If it is a virtual method, there will be self here, which we need to cast to the declaring class - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - pythonFunctionSignature.getMethodDescriptor().getDeclaringClassInternalName()); - } - - if (pythonFunctionSignature.isClassMethod()) { - // If it is a class method, argument 0 need to be converted to a type if it not a type - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), - argumentLocals[0]); - methodVisitor.visitInsn(Opcodes.DUP); - Label ifIsBoundFunction = new Label(); - Label doneGettingType = new Label(); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, ifIsBoundFunction); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(PythonLikeType.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, doneGettingType); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitJumpInsn(Opcodes.GOTO, doneGettingType); - methodVisitor.visitLabel(ifIsBoundFunction); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitLabel(doneGettingType); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), argumentLocals[0]); - } - - // Now load and typecheck the local variables - for (int i = 0; i < Math.min(specPositionalArgumentCount, argumentCount); i++) { - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), argumentLocals[i]); - methodVisitor.visitLdcInsn( - Type.getType("L" + pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(i) + ";")); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(i)); - } - - // Load any arguments missing values - for (int i = specPositionalArgumentCount - missingValues; i < specPositionalArgumentCount; i++) { - if (pythonFunctionSignature.getArgumentSpec().isArgumentNullable(i)) { - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } else { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - pythonFunctionSignature.getDefaultArgumentHolderClassInternalName(), - PythonDefaultArgumentImplementor.getConstantName(i), - "L" + pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(i) + ";"); - } - } - - // Load *vargs and **kwargs if the function has them - if (pythonFunctionSignature.getArgumentSpec().hasExtraPositionalArgumentsCapture()) { - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeTuple.class), - capturedExtraPositionalArgumentsLocal); - } - - if (pythonFunctionSignature.getArgumentSpec().hasExtraKeywordArgumentsCapture()) { - // No kwargs for call method, so just load an empty map - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - } - - // Call the method - pythonFunctionSignature.getMethodDescriptor().callMethod(methodVisitor); - - // Free temporary locals for arguments - for (int i = 0; i < argumentLocals.length; i++) { - localVariableHelper.freeLocal(); - } - // Free temporary local for vargs - localVariableHelper.freeLocal(); - } - - public static void callPython311andAbove(PythonFunctionSignature pythonFunctionSignature, FunctionMetadata functionMetadata, - StackMetadata stackMetadata, int argumentCount, - List keywordArgumentNameList) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - int specTotalArgumentCount = pythonFunctionSignature.getArgumentSpec().getTotalArgumentCount(); - int positionalArgumentCount = argumentCount - keywordArgumentNameList.size(); - int[] argumentLocals = new int[specTotalArgumentCount]; - - // Create temporary variables for each argument - for (int i = 0; i < argumentLocals.length; i++) { - argumentLocals[i] = localVariableHelper.newLocal(); - } - int extraKeywordArgumentsLocal = (pythonFunctionSignature.getArgumentSpec().getExtraKeywordsArgumentIndex().isPresent()) - ? argumentLocals[pythonFunctionSignature.getArgumentSpec().getExtraKeywordsArgumentIndex().get()] - : -1; - int extraPositionalArgumentsLocal = - (pythonFunctionSignature.getArgumentSpec().getExtraPositionalsArgumentIndex().isPresent()) - ? argumentLocals[pythonFunctionSignature.getArgumentSpec().getExtraPositionalsArgumentIndex().get()] - : -1; - - // Read keyword arguments - if (extraKeywordArgumentsLocal != -1) { - CollectionImplementor.buildMap(PythonLikeDict.class, methodVisitor, 0); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeDict.class), - extraKeywordArgumentsLocal); - } - - // Read positional arguments - int positionalArgumentStart = (pythonFunctionSignature.isClassMethod()) ? 1 : 0; - - for (int keywordArgumentNameIndex = - keywordArgumentNameList.size() - 1; keywordArgumentNameIndex >= 0; keywordArgumentNameIndex--) { - // Need to iterate keyword name tuple in reverse (since last element of the tuple correspond to TOS) - String keywordArgument = keywordArgumentNameList.get(keywordArgumentNameIndex); - int argumentIndex = pythonFunctionSignature.getArgumentSpec().getArgumentIndex(keywordArgument); - if (argumentIndex == -1) { - // Unknown keyword argument; put it into the extraKeywordArguments dict - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeDict.class), - extraKeywordArgumentsLocal); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeDict.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitLdcInsn(keywordArgument); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), - "valueOf", Type.getMethodDescriptor(Type.getType(PythonString.class), - Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeDict.class), - "put", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeObject.class)), - false); - } else { - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), - argumentLocals[argumentIndex]); - } - } - - if (extraPositionalArgumentsLocal != -1) { - CollectionImplementor.buildCollection(PythonLikeTuple.class, - methodVisitor, - Math.max(0, - positionalArgumentCount - - pythonFunctionSignature.getArgumentSpec().getAllowPositionalArgumentCount() - + positionalArgumentStart)); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeTuple.class), - extraPositionalArgumentsLocal); - } - - for (int i = Math.min(positionalArgumentCount + positionalArgumentStart, - pythonFunctionSignature.getArgumentSpec().getAllowPositionalArgumentCount()) - - 1; i >= positionalArgumentStart; i--) { - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), - argumentLocals[i]); - } - - // Load missing arguments with default values - int defaultOffset = pythonFunctionSignature.getArgumentSpec().getTotalArgumentCount() - - pythonFunctionSignature.getDefaultArgumentList().size(); - for (int argumentIndex : pythonFunctionSignature.getArgumentSpec().getUnspecifiedArgumentSet( - positionalArgumentCount + positionalArgumentStart, - keywordArgumentNameList)) { - if (pythonFunctionSignature.getArgumentSpec().isArgumentNullable(argumentIndex)) { - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } else { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - pythonFunctionSignature.getDefaultArgumentHolderClassInternalName(), - PythonDefaultArgumentImplementor.getConstantName(argumentIndex - defaultOffset), - "L" + pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(argumentIndex) + ";"); - } - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), - argumentLocals[argumentIndex]); - } - - if (pythonFunctionSignature.isVirtualMethod()) { - // If it is a virtual method, there will be self here, which we need to cast to the declaring class - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - pythonFunctionSignature.getMethodDescriptor().getDeclaringClassInternalName()); - } - - if (pythonFunctionSignature.isClassMethod()) { - // If it is a class method, argument 0 need to be converted to a type if it not a type - methodVisitor.visitInsn(Opcodes.DUP); - Label ifIsBoundFunction = new Label(); - Label doneGettingType = new Label(); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, ifIsBoundFunction); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(PythonLikeType.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, doneGettingType); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitJumpInsn(Opcodes.GOTO, doneGettingType); - methodVisitor.visitLabel(ifIsBoundFunction); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitLabel(doneGettingType); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), argumentLocals[0]); - } - - // Load arguments in proper order and typecast them - for (int i = 0; i < specTotalArgumentCount; i++) { - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), argumentLocals[i]); - methodVisitor.visitLdcInsn( - Type.getType("L" + pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(i) + ";")); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - pythonFunctionSignature.getArgumentSpec().getArgumentTypeInternalName(i)); - } - - pythonFunctionSignature.getMethodDescriptor().callMethod(methodVisitor); - - // If it not a CLASS method, pop off the function object - // CLASS method consume the function object; Static and Virtual do not - if (!pythonFunctionSignature.isClassMethod()) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - - // Pop off NULL if it on the stack - if (stackMetadata.getTypeAtStackIndex(argumentCount + 1) == BuiltinTypes.NULL_TYPE) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.POP); - } - - // Free temporary locals for arguments - for (int i = 0; i < argumentLocals.length; i++) { - localVariableHelper.freeLocal(); - } - } - - public static void callWithoutKeywords(PythonFunctionSignature pythonFunctionSignature, FunctionMetadata functionMetadata, - StackMetadata stackMetadata, int argumentCount) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 0); - callWithKeywordsAndUnwrapSelf(pythonFunctionSignature, functionMetadata, stackMetadata, argumentCount); - } - - public static void callWithKeywordsAndUnwrapSelf(PythonFunctionSignature pythonFunctionSignature, - FunctionMetadata functionMetadata, StackMetadata stackMetadata, - int argumentCount) { - callWithKeywords(pythonFunctionSignature, functionMetadata, stackMetadata, argumentCount); - } - - private static void callWithKeywords(PythonFunctionSignature pythonFunctionSignature, FunctionMetadata functionMetadata, - StackMetadata stackMetadata, - int argumentCount) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - Type[] descriptorParameterTypes = pythonFunctionSignature.getMethodDescriptor().getParameterTypes(); - - if (argumentCount < descriptorParameterTypes.length - && pythonFunctionSignature.getDefaultArgumentHolderClassInternalName() == null) { - throw new IllegalStateException( - "Cannot call " + pythonFunctionSignature + " because there are not enough arguments"); - } - - if (argumentCount > descriptorParameterTypes.length - && pythonFunctionSignature.getExtraPositionalArgumentsVariableIndex().isEmpty() - && pythonFunctionSignature.getExtraKeywordArgumentsVariableIndex().isEmpty()) { - throw new IllegalStateException("Cannot call " + pythonFunctionSignature + " because there are too many arguments"); - } - - unwrapBoundMethod(pythonFunctionSignature, functionMetadata, stackMetadata, argumentCount + 1); - - if (pythonFunctionSignature.isClassMethod()) { - argumentCount++; - } - - // TOS is a tuple of keys - methodVisitor.visitTypeInsn(Opcodes.NEW, pythonFunctionSignature.getDefaultArgumentHolderClassInternalName()); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Stack is defaults (uninitialized), keys - - // Get position of last positional arg (= argumentCount - len(keys) - 1 ) - methodVisitor.visitInsn(Opcodes.DUP); // dup keys - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeTuple.class), "size", - Type.getMethodDescriptor(Type.INT_TYPE), false); - methodVisitor.visitLdcInsn(argumentCount); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.ISUB); - - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.ISUB); - - // Stack is defaults (uninitialized), keys, positional arguments - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, - pythonFunctionSignature.getDefaultArgumentHolderClassInternalName(), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeTuple.class), Type.INT_TYPE), - false); - - for (int i = 0; i < argumentCount; i++) { - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.SWAP); - if (pythonFunctionSignature.isClassMethod() && i == argumentCount - 1) { - methodVisitor.visitInsn(Opcodes.DUP); - Label ifIsBoundFunction = new Label(); - Label doneGettingType = new Label(); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, ifIsBoundFunction); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(PythonLikeType.class)); - methodVisitor.visitJumpInsn(Opcodes.IFNE, doneGettingType); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), - true); - methodVisitor.visitJumpInsn(Opcodes.GOTO, doneGettingType); - methodVisitor.visitLabel(ifIsBoundFunction); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(BoundPythonLikeFunction.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - methodVisitor.visitLabel(doneGettingType); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - pythonFunctionSignature.getDefaultArgumentHolderClassInternalName(), - "addArgument", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class)), - false); - } - - for (int i = 0; i < descriptorParameterTypes.length; i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, - pythonFunctionSignature.getDefaultArgumentHolderClassInternalName(), - PythonDefaultArgumentImplementor.getArgumentName(i), - descriptorParameterTypes[i].getDescriptor()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - - pythonFunctionSignature.getMethodDescriptor().callMethod(methodVisitor); - } - - public static void callUnpackListAndMap(String defaultArgumentHolderClassInternalName, MethodDescriptor methodDescriptor, - MethodVisitor methodVisitor) { - Type[] descriptorParameterTypes = methodDescriptor.getParameterTypes(); - - // TOS2 is the function to call, TOS1 is positional arguments, TOS is keyword arguments - if (methodDescriptor.getMethodType() == MethodDescriptor.MethodType.CLASS) { - // stack is bound-method, pos, keywords - StackManipulationImplementor.rotateThree(methodVisitor); - // stack is keywords, bound-method, pos - StackManipulationImplementor.swap(methodVisitor); - - // stack is keywords, pos, bound-method - methodVisitor.visitInsn(Opcodes.DUP_X2); - - // stack is bound-method, keywords, pos, bound-method - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeType.class)); - // stack is bound-method, keywords, pos, type - - methodVisitor.visitInsn(Opcodes.DUP2); - - // stack is bound-method, keywords, pos, type, pos, type - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(Object.class)), - true); - // stack is bound-method, keywords, pos, type - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitInsn(Opcodes.SWAP); - - // stack is bound-method, pos, keywords - } - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, defaultArgumentHolderClassInternalName, - PythonDefaultArgumentImplementor.ARGUMENT_SPEC_STATIC_FIELD_NAME, - Type.getDescriptor(ArgumentSpec.class)); - - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ArgumentSpec.class), - "extractArgumentList", Type.getMethodDescriptor(Type.getType(List.class), - Type.getType(List.class), Type.getType(Map.class)), - false); - - // Stack is function to call, argument list - // Unwrap the bound method - if (methodDescriptor.getMethodType() == MethodDescriptor.MethodType.VIRTUAL || - methodDescriptor.getMethodType() == MethodDescriptor.MethodType.INTERFACE) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BoundPythonLikeFunction.class), - "getInstance", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), - false); - - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, methodDescriptor.getDeclaringClassInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - - // Stack is method, boundedInstance?, default - - // Read the parameters - for (int i = 0; i < descriptorParameterTypes.length; i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - methodVisitor.visitLdcInsn(descriptorParameterTypes[i]); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, descriptorParameterTypes[i].getInternalName()); - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is method, boundedInstance?, arg0, arg1, ... - - methodDescriptor.callMethod(methodVisitor); - - // Stack is method, result - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementor.java deleted file mode 100644 index 10a07dff..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementor.java +++ /dev/null @@ -1,129 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.PythonModule; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class ModuleImplementor { - - /** - * TOS is from_list (list or None); TOS1 is level. Imports co_names[instruction.arg] using __import__ with - * the given from_list and level (using the function globals and locals). TOS and TOS1 are popped, - * and the imported module is pushed. - * - * @see PythonInterpreter#importModule(PythonInteger, List, Map, Map, String) - */ - public static void importName(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Stack is level, from_list - // We need it to be interpreter, from_list, level, globals, locals, name - // (to call interpreter.importModule) - - // check if from_list is None - Label fromListSet = new Label(); - - methodVisitor.visitInsn(Opcodes.DUP); - PythonConstantsImplementor.loadNone(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, fromListSet); - // Is None; change it to a list - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), - "emptyList", Type.getMethodDescriptor(Type.getType(List.class)), - false); - - // typecast from_list to List - methodVisitor.visitLabel(fromListSet); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(List.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // typecast level to PythonInteger - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonInteger.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - - // Get the current function's interpreter - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, functionMetadata.className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - - // Stack is level, from_list, interpreter - // Duplicate interpreter BEFORE from_list and level - methodVisitor.visitInsn(Opcodes.DUP_X2); - - // Stack is interpreter, level, from_list, interpreter - - // Remove the interpreter from TOS - methodVisitor.visitInsn(Opcodes.POP); - - // Stack is interpreter, level, from_list - - // Get the globals and the locals from the function - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, functionMetadata.className, - PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - - // TODO: Create Map of local variables which is stored in a constant slot? - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), - "emptyMap", Type.getMethodDescriptor(Type.getType(Map.class)), - false); - - // Stack is interpreter, level, from_list, globals_map, locals_map - - // Finally, push the name of the module to load - methodVisitor.visitLdcInsn(functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg())); - - // Now call the interpreter's importModule function - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "importModule", Type.getMethodDescriptor(Type.getType(PythonModule.class), - Type.getType(PythonInteger.class), Type.getType(List.class), - Type.getType(Map.class), Type.getType(Map.class), Type.getType(String.class)), - true); - } - - /** - * TOS is a module; Push the attribute co_names[instruction.arg] from module onto the stack. TOS is NOT popped. - * (i.e. after this instruction, stack is module, attribute) - * - * @see PythonInterpreter#importModule(PythonInteger, List, Map, Map, String) - */ - public static void importFrom(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Stack is module - - // Duplicate module - methodVisitor.visitInsn(Opcodes.DUP); - - // Stack is module, module - - // Push the attribute name to load - methodVisitor.visitLdcInsn(functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg())); - - // Stack is module, module, attribute_name - - // Get the attribute - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$getAttributeOrError", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), - true); - - // Stack is module, attribute - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java deleted file mode 100644 index e8b7df1e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java +++ /dev/null @@ -1,202 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.Optional; - -import ai.timefold.jpyinterpreter.FieldDescriptor; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -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; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes related to objects - */ -public class ObjectImplementor { - - /** - * Replaces TOS with getattr(TOS, co_names[instruction.arg]) - */ - 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(nameIndex); - Optional maybeFieldDescriptor = tosType.getInstanceFieldDescriptor(name); - - if (maybeFieldDescriptor.isPresent()) { - FieldDescriptor fieldDescriptor = maybeFieldDescriptor.get(); - if (fieldDescriptor.isTrueFieldDescriptor()) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, fieldDescriptor.declaringClassInternalName()); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fieldDescriptor.declaringClassInternalName(), - fieldDescriptor.javaFieldName(), - fieldDescriptor.javaFieldTypeDescriptor()); - - // Check if field is null. If it is null, then it was deleted, so we should raise an AttributeError - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - - Label ifNotNull = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IF_ACMPNE, ifNotNull); - - // Throw attribute error - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(AttributeError.class)); - methodVisitor.visitInsn(Opcodes.DUP); - - if (fieldDescriptor.fieldPythonLikeType().isInstance(PythonNone.INSTANCE)) { - methodVisitor.visitLdcInsn("'" + tosType.getTypeName() + "' object has no attribute '" + name + "'."); - } else { - // None cannot be assigned to the field, meaning it will delete the attribute instead - methodVisitor.visitLdcInsn("'" + tosType.getTypeName() + "' object has no attribute '" + name + "'. " + - "It might of been deleted because None cannot be assigned to it; either use " + - "hasattr(obj, '" + name + "') or change the typing to allow None (ex: typing.Optional[" + - tosType.getTypeName() + "])."); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(AttributeError.class), - "", - Type.getMethodDescriptor(Type.getType(void.class), Type.getType(String.class)), - false); - methodVisitor.visitInsn(Opcodes.ATHROW); - - // The attribute was not null - if (fieldDescriptor.isJavaType()) { - // Need to wrap the object with JavaObjectWrapper - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(JavaObjectWrapper.class)); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.DUP_X1); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(JavaObjectWrapper.class), - "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), false); - } - methodVisitor.visitLabel(ifNotNull); - } else { - // 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, nameIndex); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$method$__getattribute__", Type.getMethodDescriptor( - Type.getType(PythonLikeObject.class), - Type.getType(PythonString.class)), - true); - } - } else { - PythonConstantsImplementor.loadName(methodVisitor, className, nameIndex); - DunderOperatorImplementor.binaryOperator(methodVisitor, - stackMetadata.pushTemp(BuiltinTypes.STRING_TYPE), - PythonBinaryOperator.GET_ATTRIBUTE); - } - } - - /** - * Deletes co_names[instruction.arg] of TOS - */ - public static void deleteAttribute(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, - StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { - PythonLikeType tosType = stackMetadata.getTOSType(); - String name = functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg()); - Optional maybeFieldDescriptor = tosType.getInstanceFieldDescriptor(name); - if (maybeFieldDescriptor.isPresent()) { - FieldDescriptor fieldDescriptor = maybeFieldDescriptor.get(); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, fieldDescriptor.declaringClassInternalName()); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, fieldDescriptor.declaringClassInternalName(), - fieldDescriptor.javaFieldName(), - fieldDescriptor.javaFieldTypeDescriptor()); - } else { - PythonConstantsImplementor.loadName(methodVisitor, className, instruction.arg()); - DunderOperatorImplementor.binaryOperator(methodVisitor, - stackMetadata.pushTemp(BuiltinTypes.STRING_TYPE), - PythonBinaryOperator.DELETE_ATTRIBUTE); - } - } - - /** - * Implement TOS.name = TOS1, where name is co_names[instruction.arg]. TOS and TOS1 are popped. - */ - public static void setAttribute(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, - StackMetadata stackMetadata, - PythonBytecodeInstruction instruction, LocalVariableHelper localVariableHelper) { - PythonLikeType tosType = stackMetadata.getTOSType(); - String name = functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg()); - Optional maybeFieldDescriptor = tosType.getInstanceFieldDescriptor(name); - if (maybeFieldDescriptor.isPresent()) { - FieldDescriptor fieldDescriptor = maybeFieldDescriptor.get(); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, fieldDescriptor.declaringClassInternalName()); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitLdcInsn(Type.getType(fieldDescriptor.fieldPythonLikeType().getJavaTypeDescriptor())); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JavaPythonTypeConversionImplementor.class), - "coerceToType", Type.getMethodDescriptor(Type.getType(Object.class), - Type.getType(PythonLikeObject.class), - Type.getType(Class.class)), - false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, fieldDescriptor.fieldPythonLikeType().getJavaTypeInternalName()); - if (fieldDescriptor.isJavaType()) { - // Need to unwrap the object - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(JavaObjectWrapper.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(JavaObjectWrapper.class), - "getWrappedObject", Type.getMethodDescriptor(Type.getType(Object.class)), false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - Type.getType(fieldDescriptor.javaFieldTypeDescriptor()).getInternalName()); - } - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, fieldDescriptor.declaringClassInternalName(), - fieldDescriptor.javaFieldName(), - fieldDescriptor.javaFieldTypeDescriptor()); - } else { - StackManipulationImplementor.swap(methodVisitor); - PythonConstantsImplementor.loadName(methodVisitor, className, instruction.arg()); - StackManipulationImplementor.swap(methodVisitor); - DunderOperatorImplementor.ternaryOperator(functionMetadata, stackMetadata.pop(2) - .push(stackMetadata.getValueSourceForStackIndex(0)) - .pushTemp(BuiltinTypes.STRING_TYPE) - .push(stackMetadata.getValueSourceForStackIndex(1)), - 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/implementors/PythonBuiltinOperatorImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/PythonBuiltinOperatorImplementor.java deleted file mode 100644 index 0a6ec8fe..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/PythonBuiltinOperatorImplementor.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of opcodes/operations that do not use dunder methods / are builtin. - */ -public class PythonBuiltinOperatorImplementor { - - /** - * Replace TOS with not TOS. - */ - public static void performNotOnTOS(MethodVisitor methodVisitor) { - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonBoolean.class)); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonBoolean.class), - "not", Type.getMethodDescriptor(Type.getType(PythonBoolean.class)), - false); - } - - /** - * Perform TOS is TOS1. If {@code instruction} argument is 1, perform TOS is not TOS1 instead. - */ - public static void isOperator(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - int opcode = (instruction.arg() == 0) ? Opcodes.IF_ACMPEQ : Opcodes.IF_ACMPNE; - Label trueBranchLabel = new Label(); - Label endLabel = new Label(); - - methodVisitor.visitJumpInsn(opcode, trueBranchLabel); - PythonConstantsImplementor.loadFalse(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.GOTO, endLabel); - - methodVisitor.visitLabel(trueBranchLabel); - PythonConstantsImplementor.loadTrue(methodVisitor); - methodVisitor.visitLabel(endLabel); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/PythonConstantsImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/PythonConstantsImplementor.java deleted file mode 100644 index 225573c6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/PythonConstantsImplementor.java +++ /dev/null @@ -1,73 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of loading Python constants - */ -public class PythonConstantsImplementor { - - /** - * Pushes None onto the stack. The same instance is pushed on each call. - */ - public static void loadNone(MethodVisitor methodVisitor) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonNone.class), "INSTANCE", - Type.getDescriptor(PythonNone.class)); - } - - /** - * Pushes True onto the stack. The same instance is pushed on each call. - */ - public static void loadTrue(MethodVisitor methodVisitor) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonBoolean.class), "TRUE", - Type.getDescriptor(PythonBoolean.class)); - } - - /** - * Pushes False onto the stack. The same instance is pushed on each call. - */ - public static void loadFalse(MethodVisitor methodVisitor) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonBoolean.class), "FALSE", - Type.getDescriptor(PythonBoolean.class)); - } - - /** - * Gets the {@code constantIndex} constant from the class constant list - * - * @param className The class currently being defined by the methodVisitor - * @param constantIndex The index of the constant to load in the class constant list - */ - public static void loadConstant(MethodVisitor methodVisitor, String className, int constantIndex) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.CONSTANTS_STATIC_FIELD_NAME, Type.getDescriptor(List.class)); - methodVisitor.visitLdcInsn(constantIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - } - - /** - * Gets the {@code nameIndex} name from the class name list - * - * @param className The class currently being defined by the methodVisitor - * @param nameIndex The index of the name to load in the class name list - */ - public static void loadName(MethodVisitor methodVisitor, String className, int nameIndex) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.NAMES_STATIC_FIELD_NAME, Type.getDescriptor(List.class)); - methodVisitor.visitLdcInsn(nameIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), - "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.INT_TYPE), - true); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java deleted file mode 100644 index bd81556b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java +++ /dev/null @@ -1,320 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.jpyinterpreter.ExceptionBlock; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of stack manipulation opcodes (rotations, pop, duplication, etc.) - */ -public class StackManipulationImplementor { - - /** - * Swaps TOS and TOS1: - * - * (i.e. ..., TOS1, TOS -> ..., TOS, TOS1) - */ - public static void swap(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.SWAP); - } - - /** - * Move TOS down two places, and pushes TOS1 and TOS2 up one: - * - * (i.e. ..., TOS2, TOS1, TOS -> ..., TOS, TOS2, TOS1) - */ - public static void rotateThree(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * Move TOS down three places, and pushes TOS1, TOS2 and TOS3 up one: - * - * (i.e. ..., TOS3, TOS2, TOS1, TOS -> ..., TOS, TOS3, TOS2, TOS1) - */ - public static void rotateFour(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - int secondFromStack = localVariableHelper.newLocal(); - int thirdFromStack = localVariableHelper.newLocal(); - - methodVisitor.visitInsn(Opcodes.DUP_X2); - methodVisitor.visitInsn(Opcodes.POP); - - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(1).getJavaTypeDescriptor()), - secondFromStack); - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(2).getJavaTypeDescriptor()), - thirdFromStack); - - methodVisitor.visitInsn(Opcodes.SWAP); - - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(2).getJavaTypeDescriptor()), - thirdFromStack); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(1).getJavaTypeDescriptor()), - secondFromStack); - - localVariableHelper.freeLocal(); - localVariableHelper.freeLocal(); - } - - /** - * Pops TOS. - * - * (i.e. ..., TOS -> ...) - */ - public static void popTOS(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.POP); - } - - /** - * Duplicates TOS. - * - * (i.e. ..., TOS -> ..., TOS, TOS) - */ - public static void duplicateTOS(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP); - } - - /** - * Duplicates TOS and TOS1. - * - * (i.e. ..., TOS1, TOS -> ..., TOS1, TOS, TOS1, TOS) - */ - public static void duplicateTOSAndTOS1(MethodVisitor methodVisitor) { - methodVisitor.visitInsn(Opcodes.DUP2); - } - - /** - * Copies TOS[posFromTOS] to TOS, leaving other stack elements in their original place - * - * (i.e. ..., TOS[posFromTOS], ..., TOS2, TOS1, TOS -> ..., TOS[posFromTOS], ..., TOS2, TOS1, TOS, TOS[posFromTOS]) - */ - public static void duplicateToTOS(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int posFromTOS) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - List localList = new ArrayList<>(posFromTOS); - - // Store TOS...TOS[posFromTOS - 1] into local variables - for (int i = 0; i < posFromTOS; i++) { - int local = localVariableHelper.newLocal(); - localList.add(local); - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - local); - } - - // Duplicate TOS[posFromTOS] - methodVisitor.visitInsn(Opcodes.DUP); - - // Restore TOS...TOS[posFromTOS - 1] from local variables, swaping the duplicated value to keep it on TOS - for (int i = posFromTOS - 1; i >= 0; i--) { - int local = localList.get(i); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - local); - methodVisitor.visitInsn(Opcodes.SWAP); - localVariableHelper.freeLocal(); - } - } - - /** - * Copies TOS to TOS[posFromTOS], moving other stack elements up by one - * - * (i.e. ..., TOS[posFromTOS], ..., TOS2, TOS1, TOS -> ..., TOS, TOS[posFromTOS] ..., TOS2, TOS1) - */ - public static StackMetadata shiftTOSDownBy(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int posFromTOS) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - List localList = new ArrayList<>(posFromTOS + 1); - - if (posFromTOS == 0) { - // A rotation of 0 is a no-op - return stackMetadata; - } - - // Store TOS...TOS[posFromTOS - 1] into local variables - for (int i = 0; i < posFromTOS + 1; i++) { - int local = localVariableHelper.newLocal(); - localList.add(local); - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), local); - } - - // Copy TOS to this position - localVariableHelper.readTemp(methodVisitor, Type.getType(stackMetadata.getTypeAtStackIndex(0).getJavaTypeDescriptor()), - localList.get(0)); - - // Restore TOS[1]...TOS[posFromTOS] from local variables - for (int i = posFromTOS; i > 0; i--) { - int local = localList.get(i); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), local); - localVariableHelper.freeLocal(); - } - - ValueSourceInfo top = stackMetadata.getTOSValueSource(); - StackMetadata out = stackMetadata; - out = out.pop(posFromTOS + 1); - out = out.push(top); - for (int i = posFromTOS; i > 0; i--) { - out = out.push(stackMetadata.getValueSourceForStackIndex(i)); - } - return out; - } - - /** - * Swaps TOS with TOS[posFromTOS] - * - * (i.e. ..., TOS[posFromTOS], ..., TOS2, TOS1, TOS -> ..., TOS, ..., TOS2, TOS1, TOS[posFromTOS]) - */ - public static StackMetadata swapTOSWithIndex(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - int posFromTOS) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - List localList = new ArrayList<>(posFromTOS + 1); - - if (posFromTOS == 0) { - // A rotation of 0 is a no-op - return stackMetadata; - } - - // Store TOS...TOS[posFromTOS - 1] into local variables - for (int i = 0; i < posFromTOS + 1; i++) { - int local = localVariableHelper.newLocal(); - localList.add(local); - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), local); - } - - // Copy TOS to this position - localVariableHelper.readTemp(methodVisitor, Type.getType(stackMetadata.getTypeAtStackIndex(0).getJavaTypeDescriptor()), - localList.get(0)); - - // Restore TOS[1]...TOS[posFromTOS - 1] from local variables - for (int i = posFromTOS - 1; i > 0; i--) { - int local = localList.get(i); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), local); - } - int local = localList.get(posFromTOS); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(posFromTOS).getJavaTypeDescriptor()), local); - - // Free locals - for (int i = posFromTOS; i > 0; i--) { - localVariableHelper.freeLocal(); - } - - return stackMetadata - .set(posFromTOS, stackMetadata.getTOSValueSource()) - .set(0, stackMetadata.getValueSourceForStackIndex(posFromTOS)); - } - - public static int[] storeStack(MethodVisitor methodVisitor, StackMetadata stackMetadata) { - int[] stackLocalVariables = new int[stackMetadata.getStackSize()]; - - for (int i = stackLocalVariables.length - 1; i >= 0; i--) { - stackLocalVariables[i] = stackMetadata.localVariableHelper.newLocal(); - stackMetadata.localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - } - - for (int i = 0; i < stackLocalVariables.length; i++) { - stackMetadata.localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - } - - return stackLocalVariables; - } - - public static void restoreStack(MethodVisitor methodVisitor, StackMetadata stackMetadata, int[] stackLocalVariables) { - for (int i = 0; i < stackLocalVariables.length; i++) { - stackMetadata.localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - } - } - - public static void storeExceptionTableStack(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - ExceptionBlock exceptionBlock) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - int[] stackLocalVariables = new int[stackMetadata.getStackSize()]; - - for (int i = stackLocalVariables.length - 1; i >= 0; i--) { - stackLocalVariables[i] = localVariableHelper.newLocal(); - localVariableHelper.writeTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - } - - methodVisitor.visitLdcInsn(exceptionBlock.getStackDepth()); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(PythonLikeObject.class)); - - for (int i = 0; i < exceptionBlock.getStackDepth(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - methodVisitor.visitInsn(Opcodes.AASTORE); - } - localVariableHelper.writeExceptionTableTargetStack(methodVisitor, exceptionBlock.getTargetInstruction()); - - for (int i = 0; i < stackLocalVariables.length; i++) { - localVariableHelper.readTemp(methodVisitor, - Type.getType(stackMetadata.getTypeAtStackIndex(i).getJavaTypeDescriptor()), - stackLocalVariables[i]); - } - - for (int i = 0; i < stackLocalVariables.length; i++) { - localVariableHelper.freeLocal(); - } - } - - public static void restoreExceptionTableStack(FunctionMetadata functionMetadata, StackMetadata stackMetadata, - ExceptionBlock exceptionBlock) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - localVariableHelper.readExceptionTableTargetStack(methodVisitor, exceptionBlock.getTargetInstruction()); - - for (int i = 0; i < exceptionBlock.getStackDepth(); i++) { - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(i); - methodVisitor.visitInsn(Opcodes.AALOAD); - - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11) || - functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_12)) { - // Not a 3.11 python version - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - stackMetadata.getTypeAtStackIndex(exceptionBlock.getStackDepth() - i - 1).getJavaTypeInternalName()); - } else { - // A 3.11 python version - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - Type.getInternalName(PythonLikeObject.class)); - } - methodVisitor.visitInsn(Opcodes.SWAP); - } - methodVisitor.visitInsn(Opcodes.POP); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StringImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StringImplementor.java deleted file mode 100644 index d7b7f3e5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StringImplementor.java +++ /dev/null @@ -1,157 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StringOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class StringImplementor { - - /** - * Constructs a string from the top {@code itemCount} on the stack. - * Basically generate the following code: - * - * - *
-     *     StringBuilder builder = new StringBuilder();
-     *     builder.insert(0, TOS);
-     *     builder.insert(0, TOS1);
-     *     ...
-     *     builder.insert(0, TOS(itemCount - 1));
-     *     TOS' = PythonString.valueOf(builder.toString())
-     * 
- *
- * - * @param itemCount The number of items to put into collection from the stack - */ - public static void buildString(MethodVisitor methodVisitor, - int itemCount) { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - - for (int i = 0; i < itemCount; i++) { - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.SWAP); - - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), - "insert", - Type.getMethodDescriptor(Type.getType(StringBuilder.class), - Type.INT_TYPE, - Type.getType(Object.class)), - false); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), - "toString", - Type.getMethodDescriptor(Type.getType(String.class)), false); - - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(PythonString.class), - "valueOf", - Type.getMethodDescriptor(Type.getType(PythonString.class), - Type.getType(String.class)), - false); - } - - /** - * TOS1 is a value and TOS is an optional format string (either PythonNone or PythonString). - * Depending on {@code instruction.arg}, does one of several things to TOS1 before formatting it - * (as specified by {@link StringOpDescriptor#FORMAT_VALUE}: - * - * arg & 3 == 0: Do nothing - * arg & 3 == 1: Call str() on value before formatting it - * arg & 3 == 2: Call repr() on value before formatting it - * arg & 3 == 3: Call ascii() on value before formatting it - * - * if arg & 4 == 0, TOS is the value to format, so push PythonNone before calling format - * if arg & 4 == 4, TOS is a format string, use it in the call - */ - public static void formatValue(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction) { - if ((instruction.arg() & 4) == 0) { - // No format string on stack; push None - PythonConstantsImplementor.loadNone(methodVisitor); - } - - switch (instruction.arg() & 3) { - case 0 -> { - // Do Nothing - } - case 1 -> { - // Call str() - StackManipulationImplementor.swap(methodVisitor); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.AS_STRING); - StackManipulationImplementor.swap(methodVisitor); - } - case 2 -> { - // Call repr() - StackManipulationImplementor.swap(methodVisitor); - DunderOperatorImplementor.unaryOperator(methodVisitor, PythonUnaryOperator.REPRESENTATION); - StackManipulationImplementor.swap(methodVisitor); - } - case 3 -> { - // Call ascii() - StackManipulationImplementor.swap(methodVisitor); - // TODO: Create method that calls repr and convert non-ascii character to ascii and call it - StackManipulationImplementor.swap(methodVisitor); - } - default -> throw new IllegalStateException("Invalid flag: %d; & did not produce a value in range 0-3: %d" - .formatted(instruction.arg(), instruction.arg() & 3)); - } - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), - "$method$__format__", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * TOS is an PythonLikeObject to be printed. Pop TOS off the stack and print it. - */ - public static void print(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - String className = functionMetadata.className; - String globalName = "print"; - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, className); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - methodVisitor.visitLdcInsn(globalName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "getGlobal", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(Map.class), - Type.getType(String.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); - methodVisitor.visitInsn(Opcodes.SWAP); - CollectionImplementor.buildCollection(PythonLikeTuple.class, methodVisitor, 1); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "emptyMap", - Type.getMethodDescriptor(Type.getType(Map.class)), - false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeFunction.class), "$call", - Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(List.class), - Type.getType(Map.class), Type.getType(PythonLikeObject.class)), - true); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/VariableImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/VariableImplementor.java deleted file mode 100644 index 95a76953..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/VariableImplementor.java +++ /dev/null @@ -1,235 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.opcodes.descriptor.VariableOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonCell; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -/** - * Implementations of local variable manipulation opcodes. - * See https://tenthousandmeters.com/blog/python-behind-the-scenes-5-how-variables-are-implemented-in-cpython/ - * for a detailed explanation of the differences between LOAD_FAST, LOAD_GLOBAL, LOAD_DEREF, etc. - */ -public class VariableImplementor { - - /** - * Loads the local variable or parameter indicated by the {@code instruction} argument onto the stack. - */ - public static void loadLocalVariable(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction, - LocalVariableHelper localVariableHelper) { - localVariableHelper.readLocal(methodVisitor, instruction.arg()); - } - - /** - * Stores TOS into the local variable or parameter indicated by the {@code instruction} argument. - */ - public static void storeInLocalVariable(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction, - LocalVariableHelper localVariableHelper) { - localVariableHelper.writeLocal(methodVisitor, instruction.arg()); - } - - /** - * Deletes the global variable or parameter indicated by the {@code instruction} argument. - */ - public static void deleteGlobalVariable(MethodVisitor methodVisitor, String className, - PythonCompiledFunction pythonCompiledFunction, - PythonBytecodeInstruction instruction) { - String globalName = pythonCompiledFunction.co_names.get(instruction.arg()); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, className); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - methodVisitor.visitLdcInsn(globalName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "deleteGlobal", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class), - Type.getType(String.class)), - true); - } - - /** - * Loads the global variable or parameter indicated by the {@code instruction} argument onto the stack. - */ - public static void loadGlobalVariable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int globalIndex, - PythonLikeType globalType) { - PythonCompiledFunction pythonCompiledFunction = functionMetadata.pythonCompiledFunction; - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - String className = functionMetadata.className; - - String globalName = pythonCompiledFunction.co_names.get(globalIndex); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, className); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - methodVisitor.visitLdcInsn(globalName); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "getGlobal", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), - Type.getType(Map.class), - Type.getType(String.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, globalType.getJavaTypeInternalName()); - } - - /** - * Stores TOS into the global variable or parameter indicated by the {@code instruction} argument. - */ - public static void storeInGlobalVariable(MethodVisitor methodVisitor, String className, - PythonCompiledFunction pythonCompiledFunction, - PythonBytecodeInstruction instruction) { - String globalName = pythonCompiledFunction.co_names.get(instruction.arg()); - - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, className); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, className, - PythonBytecodeToJavaBytecodeTranslator.INTERPRETER_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonInterpreter.class)); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, className, - PythonBytecodeToJavaBytecodeTranslator.GLOBALS_MAP_STATIC_FIELD_NAME, - Type.getDescriptor(Map.class)); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitLdcInsn(globalName); - StackManipulationImplementor.swap(methodVisitor); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonInterpreter.class), - "setGlobal", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class), - Type.getType(String.class), - Type.getType(PythonLikeObject.class)), - true); - } - - /** - * Deletes the local variable or parameter indicated by the {@code instruction} argument. - */ - public static void deleteLocalVariable(MethodVisitor methodVisitor, PythonBytecodeInstruction instruction, - LocalVariableHelper localVariableHelper) { - // Deleting is implemented as setting the value to null - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - localVariableHelper.writeLocal(methodVisitor, instruction.arg()); - } - - public static int getCellIndex(FunctionMetadata functionMetadata, int instructionArg) { - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - // free variables are offset by co_varnames.size(), bound variables are not - if (instructionArg >= functionMetadata.pythonCompiledFunction.co_cellvars.size()) { - // it a free variable - return instructionArg - functionMetadata.pythonCompiledFunction.co_varnames.size(); - } - return instructionArg; // it a bound variable - } else { - return instructionArg; // Python 3.10 and below, we don't need to do anything - } - } - - /** - * Loads the cell indicated by the {@code instruction} argument onto the stack. - * This is used by {@link VariableOpDescriptor#LOAD_CLOSURE} when creating a closure - * for a dependent function. - */ - public static void createCell(MethodVisitor methodVisitor, LocalVariableHelper localVariableHelper, int cellIndex) { - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonCell.class)); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonCell.class), "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - methodVisitor.visitInsn(Opcodes.DUP); - localVariableHelper.readCellInitialValue(methodVisitor, cellIndex); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeObject.class)); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonCell.class), "cellValue", - Type.getDescriptor(PythonLikeObject.class)); - localVariableHelper.writeCell(methodVisitor, cellIndex); - } - - /** - * Moves the {@code cellIndex} free variable (stored in the - * {@link PythonBytecodeToJavaBytecodeTranslator#CELLS_INSTANCE_FIELD_NAME} field - * to its corresponding local variable. - */ - public static void setupFreeVariableCell(MethodVisitor methodVisitor, String internalClassName, - LocalVariableHelper localVariableHelper, int cellIndex) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, - PythonBytecodeToJavaBytecodeTranslator.CELLS_INSTANCE_FIELD_NAME, - Type.getDescriptor(PythonLikeTuple.class)); - methodVisitor.visitLdcInsn(cellIndex); - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "get", - Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(int.class)), - true); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonCell.class)); - localVariableHelper.writeFreeCell(methodVisitor, cellIndex); - } - - /** - * Loads the cell indicated by the {@code instruction} argument onto the stack. - * This is used by {@link VariableOpDescriptor#LOAD_CLOSURE} when creating a closure - * for a dependent function. - */ - public static void loadCell(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int cellIndex) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; - - localVariableHelper.readCell(methodVisitor, cellIndex); - } - - /** - * Loads the cell variable/free variable indicated by the {@code instruction} argument onto the stack. - * (which is an {@link PythonCell}, so it can see changes from the parent function). - */ - public static void loadCellVariable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int cellIndex) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - loadCell(functionMetadata, stackMetadata, cellIndex); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(PythonCell.class), "cellValue", - Type.getDescriptor(PythonLikeObject.class)); - } - - /** - * Stores TOS into the cell variable or parameter indicated by the {@code instruction} argument - * (which is an {@link PythonCell}, so changes in the parent function affect the variable in dependent functions). - */ - public static void storeInCellVariable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int cellIndex) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - loadCell(functionMetadata, stackMetadata, cellIndex); - methodVisitor.visitInsn(Opcodes.SWAP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonCell.class), "cellValue", - Type.getDescriptor(PythonLikeObject.class)); - } - - /** - * Deletes the cell variable or parameter indicated by the {@code instruction} argument - * (which is an {@link PythonCell}, so changes in the parent function affect the variable in dependent functions). - */ - public static void deleteCellVariable(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int cellIndex) { - MethodVisitor methodVisitor = functionMetadata.methodVisitor; - - // Deleting is implemented as setting the value to null - loadCell(functionMetadata, stackMetadata, cellIndex); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(PythonCell.class), "cellValue", - Type.getDescriptor(PythonLikeObject.class)); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/AbstractOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/AbstractOpcode.java deleted file mode 100644 index e73c6245..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/AbstractOpcode.java +++ /dev/null @@ -1,76 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.opcodes.descriptor.OpcodeDescriptor; - -public abstract class AbstractOpcode implements Opcode { - protected PythonBytecodeInstruction instruction; - private static final Map opcodeNameToInstructionMap = getOpcodeNameToInstructionMap(); - - private static Map getOpcodeNameToInstructionMap() { - Map out = new HashMap<>(); - for (Class subclass : OpcodeDescriptor.class.getPermittedSubclasses()) { - if (!subclass.isEnum()) { - throw new IllegalStateException("%s is a subclass of %s and is not an enum." - .formatted(subclass, OpcodeDescriptor.class.getSimpleName())); - } - @SuppressWarnings("unchecked") - Class> enumSubclass = (Class>) subclass; - for (Enum constant : enumSubclass.getEnumConstants()) { - String name = constant.name(); - if (out.containsKey(name)) { - throw new IllegalStateException("Duplicate identifier %s present in both %s and %s." - .formatted(name, out.get(name).getClass(), enumSubclass)); - } - out.put(name, (OpcodeDescriptor) constant); - } - } - return out; - } - - public AbstractOpcode(PythonBytecodeInstruction instruction) { - this.instruction = instruction; - } - - public PythonBytecodeInstruction getInstruction() { - return instruction; - } - - @Override - public int getBytecodeIndex() { - return instruction.offset(); - } - - @Override - public boolean isJumpTarget() { - return instruction.isJumpTarget(); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(getStackMetadataAfterInstruction(functionMetadata, stackMetadata)); - } - - protected abstract StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata); - - public static OpcodeDescriptor lookupInstruction(String name) { - OpcodeDescriptor out = opcodeNameToInstructionMap.get(name); - if (out == null) { - throw new IllegalArgumentException("Invalid opcode identifier %s.".formatted(name)); - } - return out; - } - - @Override - public String toString() { - return instruction.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java deleted file mode 100644 index 3d09b337..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java +++ /dev/null @@ -1,71 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; - -public interface Opcode { - - /** - * Return the bytecode index of the instruction, which can be used - * to identify the instruction as the target of a jump. - * - * @return The bytecode index of the instruction, which is defined - * as the number of instructions before it in the instruction - * listing. - */ - int getBytecodeIndex(); - - /** - * Return the possible next bytecode index after this instruction is executed. - * The default simply return [getBytecodeIndex() + 1], but is - * typically overwritten in jump instructions. - * - * @return the possible next bytecode index after this instruction is executed - */ - default List getPossibleNextBytecodeIndexList() { - return List.of(getBytecodeIndex() + 1); - } - - /** - * Return a list of {@link StackMetadata} corresponding to each branch returned by - * {@link #getPossibleNextBytecodeIndexList()}. - * - * @param functionMetadata Metadata about the function being compiled. - * @param stackMetadata the StackMetadata just before this instruction is executed. - * @return a new List, the same size as {@link #getPossibleNextBytecodeIndexList()}, - * containing the StackMetadata after this instruction is executed for the given branch - * in {@link #getPossibleNextBytecodeIndexList()}. - */ - List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata); - - /** - * Implements the opcode. - * - * @param functionMetadata Metadata about the function being compiled. - * @param stackMetadata Metadata about the state of the stack when this instruction is executed. - */ - void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata); - - /** - * @return true if this opcode the target of a jump - */ - boolean isJumpTarget(); - - /** - * @return true if this opcode is a forced jump (i.e. goto) - */ - default boolean isForcedJump() { - return false; - } - - static Opcode lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return AbstractOpcode.lookupInstruction(instruction.opname()) - .getVersionMapping() - .getOpcodeForVersion(instruction, pythonVersion); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/OpcodeWithoutSource.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/OpcodeWithoutSource.java deleted file mode 100644 index 248ebfdc..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/OpcodeWithoutSource.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.StackMetadata; - -public class OpcodeWithoutSource implements Opcode { - - @Override - public int getBytecodeIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - throw new UnsupportedOperationException(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isJumpTarget() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - return other.getClass() == getClass(); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/SelfOpcodeWithoutSource.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/SelfOpcodeWithoutSource.java deleted file mode 100644 index 8eb0aa53..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/SelfOpcodeWithoutSource.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.StackMetadata; - -public class SelfOpcodeWithoutSource implements Opcode { - - @Override - public int getBytecodeIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - throw new UnsupportedOperationException(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isJumpTarget() { - throw new UnsupportedOperationException(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildConstantKeyMapOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildConstantKeyMapOpcode.java deleted file mode 100644 index 552e5125..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildConstantKeyMapOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; - -public class BuildConstantKeyMapOpcode extends AbstractOpcode { - - public BuildConstantKeyMapOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg() + 1).push(ValueSourceInfo.of(this, - BuiltinTypes.DICT_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.buildConstKeysMap(PythonLikeDict.class, functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildListOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildListOpcode.java deleted file mode 100644 index bb4780cf..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildListOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; - -public class BuildListOpcode extends AbstractOpcode { - - public BuildListOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg()).push(ValueSourceInfo.of(this, BuiltinTypes.LIST_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.buildCollection(PythonLikeList.class, functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildMapOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildMapOpcode.java deleted file mode 100644 index aefe471a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildMapOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; - -public class BuildMapOpcode extends AbstractOpcode { - - public BuildMapOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2 * instruction.arg()).push(ValueSourceInfo.of(this, BuiltinTypes.DICT_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2 * instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.buildMap(PythonLikeDict.class, functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSetOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSetOpcode.java deleted file mode 100644 index 2eec0517..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSetOpcode.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; - -public class BuildSetOpcode extends AbstractOpcode { - - public BuildSetOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg()).push(ValueSourceInfo.of(this, BuiltinTypes.SET_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - // TODO: either modify reverseAdd for PythonLikeSet so it replaces already encountered elements - // or store the top count items in local variables and do it in forward order. - CollectionImplementor.buildCollection(PythonLikeSet.class, functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSliceOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSliceOpcode.java deleted file mode 100644 index c9f0da91..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildSliceOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonSlice; - -public class BuildSliceOpcode extends AbstractOpcode { - - public BuildSliceOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg()).push(ValueSourceInfo.of(this, PythonSlice.SLICE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.buildSlice(functionMetadata, stackMetadata, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildTupleOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildTupleOpcode.java deleted file mode 100644 index cb057f95..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/BuildTupleOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; - -public class BuildTupleOpcode extends AbstractOpcode { - - public BuildTupleOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg()).push(ValueSourceInfo.of(this, BuiltinTypes.TUPLE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.buildCollection(PythonLikeTuple.class, functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddAllOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddAllOpcode.java deleted file mode 100644 index 88bac720..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddAllOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class CollectionAddAllOpcode extends AbstractOpcode { - - public CollectionAddAllOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.collectionAddAll(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddOpcode.java deleted file mode 100644 index 09edd11b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/CollectionAddOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class CollectionAddOpcode extends AbstractOpcode { - - public CollectionAddOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.collectionAdd(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ContainsOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ContainsOpcode.java deleted file mode 100644 index 6183bbc9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ContainsOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class ContainsOpcode extends AbstractOpcode { - - public ContainsOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2).push(ValueSourceInfo.of(this, BuiltinTypes.BOOLEAN_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.containsOperator(functionMetadata.methodVisitor, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/DeleteItemOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/DeleteItemOpcode.java deleted file mode 100644 index ee0dad2d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/DeleteItemOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.DunderOperatorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -import org.objectweb.asm.Opcodes; - -public class DeleteItemOpcode extends AbstractOpcode { - - public DeleteItemOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.binaryOperator(functionMetadata.methodVisitor, stackMetadata, - PythonBinaryOperator.DELETE_ITEM); - functionMetadata.methodVisitor.visitInsn(Opcodes.POP); // DELETE_ITEM ignore results of delete function - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/GetIterOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/GetIterOpcode.java deleted file mode 100644 index 87ef4d07..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/GetIterOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -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 GetIterOpcode extends AbstractOpcode { - - public GetIterOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.ITERATOR_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.unaryOperator(functionMetadata.methodVisitor, PythonUnaryOperator.ITERATOR); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ListToTupleOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ListToTupleOpcode.java deleted file mode 100644 index edcadb4e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/ListToTupleOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class ListToTupleOpcode extends AbstractOpcode { - - public ListToTupleOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.LIST_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.convertListToTuple(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapMergeOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapMergeOpcode.java deleted file mode 100644 index f6b054fd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapMergeOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class MapMergeOpcode extends AbstractOpcode { - - public MapMergeOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.mapPutAllOnlyIfAllNewElseThrow(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutAllOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutAllOpcode.java deleted file mode 100644 index ed7ef405..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutAllOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class MapPutAllOpcode extends AbstractOpcode { - - public MapPutAllOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.mapPutAllOnlyIfAllNewElseThrow(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutOpcode.java deleted file mode 100644 index b092fb05..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/MapPutOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class MapPutOpcode extends AbstractOpcode { - - public MapPutOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.mapPut(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/SetItemOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/SetItemOpcode.java deleted file mode 100644 index 5fddf5fb..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/SetItemOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class SetItemOpcode extends AbstractOpcode { - - public SetItemOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(3); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.setItem(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceOpcode.java deleted file mode 100644 index c12970dd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceOpcode.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class UnpackSequenceOpcode extends AbstractOpcode { - - public UnpackSequenceOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackMetadata newStackMetadata = stackMetadata.pop(); - for (int i = 0; i < instruction.arg(); i++) { - newStackMetadata = newStackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - return newStackMetadata; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.unpackSequence(functionMetadata.methodVisitor, instruction.arg(), - stackMetadata.localVariableHelper); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceWithTailOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceWithTailOpcode.java deleted file mode 100644 index 1f21fe6d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/collection/UnpackSequenceWithTailOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.collection; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class UnpackSequenceWithTailOpcode extends AbstractOpcode { - - public UnpackSequenceWithTailOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - // TODO: Correctly handle when high byte is set - StackMetadata newStackMetadata = stackMetadata.pop(); - - newStackMetadata = newStackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.LIST_TYPE, stackMetadata.getValueSourcesUpToStackIndex(1))); - for (int i = 0; i < instruction.arg(); i++) { - newStackMetadata = newStackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - return newStackMetadata; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.unpackSequenceWithTail(functionMetadata.methodVisitor, instruction.arg(), - stackMetadata.localVariableHelper); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/AbstractControlFlowOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/AbstractControlFlowOpcode.java deleted file mode 100644 index 168a909c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/AbstractControlFlowOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; - -public abstract class AbstractControlFlowOpcode implements Opcode { - protected PythonBytecodeInstruction instruction; - - public AbstractControlFlowOpcode(PythonBytecodeInstruction instruction) { - this.instruction = instruction; - } - - @Override - public boolean isJumpTarget() { - return instruction.isJumpTarget(); - } - - @Override - public int getBytecodeIndex() { - return instruction.offset(); - } - - @Override - public String toString() { - return instruction.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ForIterOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ForIterOpcode.java deleted file mode 100644 index a1157dca..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ForIterOpcode.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -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.CollectionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class ForIterOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public ForIterOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - CollectionImplementor.iterateIterator(functionMetadata.methodVisitor, jumpTarget, - stackMetadata, functionMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpAbsoluteOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpAbsoluteOpcode.java deleted file mode 100644 index 3bd2efd2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpAbsoluteOpcode.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class JumpAbsoluteOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public JumpAbsoluteOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of(jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.copy()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.jumpAbsolute(functionMetadata, stackMetadata, jumpTarget); - } - - @Override - public boolean isForcedJump() { - return true; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfFalseOrPopOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfFalseOrPopOpcode.java deleted file mode 100644 index fd975a37..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfFalseOrPopOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class JumpIfFalseOrPopOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public JumpIfFalseOrPopOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.copy()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.jumpIfFalseElsePop(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfNotExcMatchOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfNotExcMatchOpcode.java deleted file mode 100644 index c680785a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfNotExcMatchOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class JumpIfNotExcMatchOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public JumpIfNotExcMatchOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(2), - stackMetadata.pop(2)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.popAndJumpIfExceptionDoesNotMatch(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfTrueOrPopOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfTrueOrPopOpcode.java deleted file mode 100644 index dd21d2e5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/JumpIfTrueOrPopOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class JumpIfTrueOrPopOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public JumpIfTrueOrPopOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.copy()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.jumpIfTrueElsePop(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfFalseOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfFalseOpcode.java deleted file mode 100644 index b18686af..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfFalseOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class PopJumpIfFalseOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public PopJumpIfFalseOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.popAndJumpIfFalse(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNoneOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNoneOpcode.java deleted file mode 100644 index a34744c5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNoneOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class PopJumpIfIsNoneOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public PopJumpIfIsNoneOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.popAndJumpIfIsNone(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNotNoneOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNotNoneOpcode.java deleted file mode 100644 index f1bb280e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfIsNotNoneOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class PopJumpIfIsNotNoneOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public PopJumpIfIsNotNoneOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.popAndJumpIfIsNotNone(functionMetadata, stackMetadata, jumpTarget); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfTrueOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfTrueOpcode.java deleted file mode 100644 index e79ea6cf..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/PopJumpIfTrueOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.controlflow; - -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.JumpImplementor; - -public class PopJumpIfTrueOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public PopJumpIfTrueOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.pop(), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - JumpImplementor.popAndJumpIfTrue(functionMetadata, stackMetadata, jumpTarget); - } -} 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 deleted file mode 100644 index 568b0874..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnConstantValueOpcode.java +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index 0c223bd4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnValueOpcode.java +++ /dev/null @@ -1,44 +0,0 @@ -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.PythonFunctionType; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.GeneratorImplementor; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; - -public class ReturnValueOpcode extends AbstractControlFlowOpcode { - - public ReturnValueOpcode(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; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - if (functionMetadata.functionType == PythonFunctionType.GENERATOR) { - GeneratorImplementor.endGenerator(functionMetadata, stackMetadata); - } else { - JavaPythonTypeConversionImplementor.returnValue(functionMetadata.methodVisitor, functionMetadata.method, - stackMetadata); - } - } -} 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 deleted file mode 100644 index e2e60ba8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/AsyncOpDescriptor.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -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 - * object with the CO_ITERABLE_COROUTINE flag, or resolves o.__await__. - */ - GET_AWAITABLE, - - /** - * Implements TOS = TOS.__aiter__(). - */ - GET_AITER, - - /** - * Implements PUSH(get_awaitable(TOS.__anext__())). See {@link #GET_AWAITABLE} for details about get_awaitable - */ - GET_ANEXT, - - /** - * Terminates an async for loop. Handles an exception raised when awaiting a next item. If TOS is StopAsyncIteration - * pop 7 values from the stack and restore the exception state using the second three of them. Otherwise re-raise the - * exception using the three values from the stack. An exception handler block is removed from the block stack. - */ - END_ASYNC_FOR, - - /** - * Resolves __aenter__ and __aexit__ from the object on top of the stack. - * Pushes __aexit__ and result of __aenter__() to the stack. - */ - BEFORE_ASYNC_WITH, - - /** - * Creates a new frame object. - */ - SETUP_ASYNC_WITH; - - @Override - public VersionMapping getVersionMapping() { - // TODO - 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 deleted file mode 100644 index 5f225175..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/CollectionOpDescriptor.java +++ /dev/null @@ -1,98 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildConstantKeyMapOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildListOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildMapOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildSetOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildSliceOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.BuildTupleOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.CollectionAddAllOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.CollectionAddOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.ContainsOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.DeleteItemOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.GetIterOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.ListToTupleOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.MapMergeOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.MapPutAllOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.MapPutOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.SetItemOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.UnpackSequenceOpcode; -import ai.timefold.jpyinterpreter.opcodes.collection.UnpackSequenceWithTailOpcode; - -public enum CollectionOpDescriptor implements OpcodeDescriptor { - /** - * Implements TOS = iter(TOS). - */ - GET_ITER(GetIterOpcode::new), - - /** - * Implements TOS1[TOS] = TOS2. - */ - STORE_SUBSCR(SetItemOpcode::new), - - /** - * Implements del TOS1[TOS]. - */ - DELETE_SUBSCR(DeleteItemOpcode::new), - CONTAINS_OP(ContainsOpcode::new), - UNPACK_SEQUENCE(UnpackSequenceOpcode::new), - UNPACK_EX(UnpackSequenceWithTailOpcode::new), - - // ************************************************** - // Collection Construction Operations - // ************************************************** - BUILD_SLICE(BuildSliceOpcode::new), - BUILD_TUPLE(BuildTupleOpcode::new), - BUILD_LIST(BuildListOpcode::new), - BUILD_SET(BuildSetOpcode::new), - BUILD_MAP(BuildMapOpcode::new), - BUILD_CONST_KEY_MAP(BuildConstantKeyMapOpcode::new), - - // ************************************************** - // Collection Edit Operations - // ************************************************** - LIST_TO_TUPLE(ListToTupleOpcode::new), - - /** - * Calls set.add(TOS1[-i], TOS). Used to implement set comprehensions. - *

- * The added value is popped off, the container object remains on the stack so that it is available for further - * iterations of the loop. - */ - SET_ADD(CollectionAddOpcode::new), - - /** - * Calls list.append(TOS1[-i], TOS). Used to implement list comprehensions. - *

- * The added value is popped off, the container object remains on the stack so that it is available for further - * iterations of the loop. - */ - LIST_APPEND(CollectionAddOpcode::new), - - /** - * Calls dict.__setitem__(TOS1[-i], TOS1, TOS). Used to implement dict comprehensions. - *

- * The key/value pair is popped off, the container object remains on the stack so that it is available for further - * iterations of the loop. - */ - MAP_ADD(MapPutOpcode::new), - LIST_EXTEND(CollectionAddAllOpcode::new), - SET_UPDATE(CollectionAddAllOpcode::new), - DICT_UPDATE(MapPutAllOpcode::new), - DICT_MERGE(MapMergeOpcode::new); - - final VersionMapping versionLookup; - - CollectionOpDescriptor(Function instructionToOpcode) { - this.versionLookup = VersionMapping.constantMapping(instructionToOpcode); - } - - @Override - 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 deleted file mode 100644 index cb5951a5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ControlOpDescriptor.java +++ /dev/null @@ -1,77 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -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; -import ai.timefold.jpyinterpreter.opcodes.controlflow.ForIterOpcode; -import ai.timefold.jpyinterpreter.opcodes.controlflow.JumpAbsoluteOpcode; -import ai.timefold.jpyinterpreter.opcodes.controlflow.JumpIfFalseOrPopOpcode; -import ai.timefold.jpyinterpreter.opcodes.controlflow.JumpIfNotExcMatchOpcode; -import ai.timefold.jpyinterpreter.opcodes.controlflow.JumpIfTrueOrPopOpcode; -import ai.timefold.jpyinterpreter.opcodes.controlflow.PopJumpIfFalseOpcode; -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 { - /** - * 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), - POP_JUMP_IF_TRUE(PopJumpIfTrueOpcode::new, JumpUtils::getAbsoluteTarget), - POP_JUMP_FORWARD_IF_TRUE(PopJumpIfTrueOpcode::new, JumpUtils::getRelativeTarget), - POP_JUMP_BACKWARD_IF_TRUE(PopJumpIfTrueOpcode::new, JumpUtils::getBackwardRelativeTarget), - 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(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), - END_FOR(NopOpcode::new); - - final VersionMapping versionLookup; - - ControlOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - ControlOpDescriptor(BiFunction opcodeFunction, - ToIntBiFunction labelFunction) { - this.versionLookup = VersionMapping.constantMapping((instruction, version) -> opcodeFunction.apply(instruction, - labelFunction.applyAsInt(instruction, version))); - } - - ControlOpDescriptor(VersionMapping versionLookup) { - this.versionLookup = versionLookup; - } - - @Override - 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 deleted file mode 100644 index f0b5150c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/DunderOpDescriptor.java +++ /dev/null @@ -1,211 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -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 { - COMPARE_OP(CompareOpcode::new), - - /** - * Implements TOS = not TOS. - */ - UNARY_NOT(NotOpcode::new), - - /** - * Implements any binary op. Its argument represent the arg to implement, which - * may or may not be inplace. - */ - BINARY_OP(PythonBinaryOperator::getBinaryOpcode), - /** - * Implements TOS = +TOS. - */ - UNARY_POSITIVE(PythonUnaryOperator.POSITIVE), - - /** - * Implements TOS = -TOS. - */ - UNARY_NEGATIVE(PythonUnaryOperator.NEGATIVE), - - /** - * Implements TOS = ~TOS. - */ - UNARY_INVERT(PythonUnaryOperator.INVERT), - - /** - * Implements TOS = TOS1 ** TOS. - */ - BINARY_POWER(PythonBinaryOperator.POWER), - - /** - * Implements TOS = TOS1 * TOS. - */ - BINARY_MULTIPLY(PythonBinaryOperator.MULTIPLY), - - /** - * Implements TOS = TOS1 @ TOS. - */ - BINARY_MATRIX_MULTIPLY(PythonBinaryOperator.MATRIX_MULTIPLY), - - /** - * Implements TOS = TOS1 // TOS. - */ - BINARY_FLOOR_DIVIDE(PythonBinaryOperator.FLOOR_DIVIDE), - - /** - * Implements TOS = TOS1 / TOS. - */ - BINARY_TRUE_DIVIDE(PythonBinaryOperator.TRUE_DIVIDE), - - /** - * Implements TOS = TOS1 % TOS. - */ - BINARY_MODULO(PythonBinaryOperator.MODULO), - - /** - * Implements TOS = TOS1 + TOS. - */ - BINARY_ADD(PythonBinaryOperator.ADD), - - /** - * Implements TOS = TOS1 - TOS. - */ - BINARY_SUBTRACT(PythonBinaryOperator.SUBTRACT), - - /** - * Implements TOS = TOS1[TOS]. - */ - 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. - */ - BINARY_LSHIFT(PythonBinaryOperator.LSHIFT), - - /** - * Implements TOS = TOS1 >> TOS. - */ - BINARY_RSHIFT(PythonBinaryOperator.RSHIFT), - - /** - * Implements TOS = TOS1 & TOS. - */ - BINARY_AND(PythonBinaryOperator.AND), - - /** - * Implements TOS = TOS1 ^ TOS. - */ - BINARY_XOR(PythonBinaryOperator.XOR), - - /** - * Implements TOS = TOS1 | TOS. - */ - BINARY_OR(PythonBinaryOperator.OR), - - // ************************************************** - // In-place Dunder Operations - // ************************************************** - - /** - * Implements in-place TOS = TOS1 ** TOS. - */ - INPLACE_POWER(PythonBinaryOperator.INPLACE_POWER), - - /** - * Implements in-place TOS = TOS1 * TOS. - */ - INPLACE_MULTIPLY(PythonBinaryOperator.INPLACE_MULTIPLY), - - /** - * Implements in-place TOS = TOS1 @ TOS. - */ - INPLACE_MATRIX_MULTIPLY(PythonBinaryOperator.INPLACE_MATRIX_MULTIPLY), - - /** - * Implements in-place TOS = TOS1 // TOS. - */ - INPLACE_FLOOR_DIVIDE(PythonBinaryOperator.INPLACE_FLOOR_DIVIDE), - - /** - * Implements in-place TOS = TOS1 / TOS. - */ - INPLACE_TRUE_DIVIDE(PythonBinaryOperator.INPLACE_TRUE_DIVIDE), - - /** - * Implements in-place TOS = TOS1 % TOS. - */ - INPLACE_MODULO(PythonBinaryOperator.INPLACE_MODULO), - - /** - * Implements in-place TOS = TOS1 + TOS. - */ - INPLACE_ADD(PythonBinaryOperator.INPLACE_ADD), - - /** - * Implements in-place TOS = TOS1 - TOS. - */ - INPLACE_SUBTRACT(PythonBinaryOperator.INPLACE_SUBTRACT), - - /** - * Implements in-place TOS = TOS1 << TOS. - */ - INPLACE_LSHIFT(PythonBinaryOperator.INPLACE_LSHIFT), - - /** - * Implements in-place TOS = TOS1 >> TOS. - */ - INPLACE_RSHIFT(PythonBinaryOperator.INPLACE_RSHIFT), - - /** - * Implements in-place TOS = TOS1 & TOS. - */ - INPLACE_AND(PythonBinaryOperator.INPLACE_AND), - - /** - * Implements in-place TOS = TOS1 ^ TOS. - */ - INPLACE_XOR(PythonBinaryOperator.INPLACE_XOR), - - /** - * Implements in-place TOS = TOS1 | TOS. - */ - INPLACE_OR(PythonBinaryOperator.INPLACE_OR); - - final VersionMapping versionLookup; - - DunderOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - DunderOpDescriptor(PythonUnaryOperator binaryOperator) { - this((instruction) -> new UniDunerOpcode(instruction, binaryOperator)); - } - - DunderOpDescriptor(PythonBinaryOperator binaryOperator) { - this((instruction) -> new BinaryDunderOpcode(instruction, binaryOperator)); - } - - @Override - 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 deleted file mode 100644 index 96d9b507..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java +++ /dev/null @@ -1,88 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -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; -import ai.timefold.jpyinterpreter.opcodes.exceptions.BeforeWithOpcode; -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; -import ai.timefold.jpyinterpreter.opcodes.exceptions.PushExcInfoOpcode; -import ai.timefold.jpyinterpreter.opcodes.exceptions.RaiseVarargsOpcode; -import ai.timefold.jpyinterpreter.opcodes.exceptions.ReraiseOpcode; -import ai.timefold.jpyinterpreter.opcodes.exceptions.SetupFinallyOpcode; -import ai.timefold.jpyinterpreter.opcodes.exceptions.SetupWithOpcode; -import ai.timefold.jpyinterpreter.opcodes.exceptions.WithExceptStartOpcode; -import ai.timefold.jpyinterpreter.util.JumpUtils; - -public enum ExceptionOpDescriptor implements OpcodeDescriptor { - /** - * Pushes AssertionError onto the stack. Used by the assert statement. - */ - LOAD_ASSERTION_ERROR(LoadAssertionErrorOpcode::new), - /** - * Removes one block from the block stack. Per frame, there is a stack of blocks, - * denoting try statements, and such. - */ - POP_BLOCK(PopBlockOpcode::new), - - /** - * Removes one block from the block stack. The popped block must be an exception handler block, as implicitly created - * when entering an except handler. In addition to popping extraneous values from the frame stack, the last three - * popped values are used to restore the exception state. - */ - POP_EXCEPT(PopExceptOpcode::new), - - /** - * Re-raises the exception currently on top of the stack. - */ - RERAISE(ReraiseOpcode::new), - - /** - * Performs exception matching for except. Tests whether the TOS1 is an exception matching TOS. - * Pops TOS and pushes the boolean result of the test. - */ - CHECK_EXC_MATCH(CheckExcMatchOpcode::new), - - /** - * Pops a value from the stack. Pushes the current exception to the top of the stack. - * Pushes the value originally popped back to the stack. Used in exception handlers. - */ - PUSH_EXC_INFO(PushExcInfoOpcode::new), - - RAISE_VARARGS(RaiseVarargsOpcode::new), - - /** - * Calls the function in position 7 on the stack with the top three items on the stack as arguments. Used to implement - * the call context_manager.__exit__(*exc_info()) when an exception has occurred in a with statement. - */ - WITH_EXCEPT_START(WithExceptStartOpcode::new), - SETUP_FINALLY(SetupFinallyOpcode::new, JumpUtils::getRelativeTarget), - - BEFORE_WITH(BeforeWithOpcode::new), - SETUP_WITH(SetupWithOpcode::new, JumpUtils::getRelativeTarget), - CLEANUP_THROW(CleanupThrowOpcode::new); - - final VersionMapping versionLookup; - - ExceptionOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - ExceptionOpDescriptor(BiFunction opcodeFunction, - ToIntBiFunction jumpFunction) { - this.versionLookup = VersionMapping.constantMapping((instruction, pythonVersion) -> opcodeFunction.apply(instruction, - jumpFunction.applyAsInt(instruction, pythonVersion))); - } - - @Override - 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 deleted file mode 100644 index 9a4949a9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/FunctionOpDescriptor.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.function.CallFunctionKeywordOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.CallFunctionOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.CallFunctionUnpackOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.CallMethodOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.CallOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.LoadMethodOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.MakeFunctionOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.PushNullOpcode; -import ai.timefold.jpyinterpreter.opcodes.function.SetCallKeywordNameTupleOpcode; - -public enum FunctionOpDescriptor implements OpcodeDescriptor { - PUSH_NULL(PushNullOpcode::new), - KW_NAMES(SetCallKeywordNameTupleOpcode::new), - CALL(CallOpcode::new), - CALL_FUNCTION(CallFunctionOpcode::new), - CALL_FUNCTION_KW(CallFunctionKeywordOpcode::new), - CALL_FUNCTION_EX(CallFunctionUnpackOpcode::new), - LOAD_METHOD(LoadMethodOpcode::new), - CALL_METHOD(CallMethodOpcode::new), - MAKE_FUNCTION(MakeFunctionOpcode::new); - - final VersionMapping versionLookup; - - FunctionOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - @Override - 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 deleted file mode 100644 index 051383e3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/GeneratorOpDescriptor.java +++ /dev/null @@ -1,83 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -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; -import ai.timefold.jpyinterpreter.opcodes.generator.GeneratorStartOpcode; -import ai.timefold.jpyinterpreter.opcodes.generator.GetYieldFromIterOpcode; -import ai.timefold.jpyinterpreter.opcodes.generator.ResumeOpcode; -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; - -public enum GeneratorOpDescriptor implements OpcodeDescriptor { - /** - * Another do nothing code. Performs internal tracing, debugging and optimization checks in CPython - */ - RESUME(ResumeOpcode::new), - - /** - * Pops TOS and yields it from a generator. - */ - YIELD_VALUE(YieldValueOpcode::new), - - /** - * Pops TOS and delegates to it as a subiterator from a generator. - */ - YIELD_FROM(YieldFromOpcode::new), - - /** - * If TOS is a generator iterator or coroutine object it is left as is. - * Otherwise, implements TOS = iter(TOS). - */ - GET_YIELD_FROM_ITER(GetYieldFromIterOpcode::new), - - /** - * Pops TOS. The kind operand corresponds to the type of generator or coroutine. - * The legal kinds are 0 for generator, 1 for coroutine, and 2 for async generator. - */ - GEN_START(GeneratorStartOpcode::new), - - /** - * TOS1 is a subgenerator, TOS is a value. Calls TOS1.send(TOS) if self.thrownValue is null. - * Otherwise, set self.thrownValue to null and call TOS1.throwValue(TOS) instead. TOS is replaced by the subgenerator - * 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 - * the code represent a generator (and if so, generate a wrapper function for it that act like - * RETURN_GENERATOR) before interpreting it - */ - RETURN_GENERATOR(ReturnGeneratorOpcode::new); - - final VersionMapping versionLookup; - - GeneratorOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - GeneratorOpDescriptor(BiFunction opcodeConstructor, - ToIntBiFunction labelFunction) { - this.versionLookup = VersionMapping.constantMapping( - (instruction, pythonVersion) -> opcodeConstructor.apply( - instruction, - labelFunction.applyAsInt(instruction, pythonVersion))); - } - - @Override - 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 deleted file mode 100644 index 9762327e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/MetaOpDescriptor.java +++ /dev/null @@ -1,61 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -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 { - /** - * Do nothing code. Used as a placeholder by the bytecode optimizer. - */ - NOP(NopOpcode::new), - - /** - * A no-op code used by CPython to hold arbitrary data for JIT. - */ - CACHE(NopOpcode::new), - - /** - * Prefixes {@link FunctionOpDescriptor#CALL}. - * Logically this is a no op. - * It exists to enable effective specialization of calls. argc is the number of arguments as described in CALL. - */ - PRECALL(NopOpcode::new), - MAKE_CELL(NopOpcode::new), - COPY_FREE_VARS(NopOpcode::new), - CALL_INTRINSIC_1(UnaryIntrinsicFunction::lookup), - - // TODO - EXTENDED_ARG(ignored -> { - throw new UnsupportedOperationException("EXTENDED_ARG"); - }), - - /** - * Pushes builtins.__build_class__() onto the stack. - * It is later called by CALL_FUNCTION to construct a class. - */ - LOAD_BUILD_CLASS(ignored -> { - throw new UnsupportedOperationException("LOAD_BUILD_CLASS"); - }), - - /** - * Checks whether __annotations__ is defined in locals(), if not it is set up to an empty dict. This opcode is only - * emitted if a class or module body contains variable annotations statically. - * TODO: Properly implement this - */ - SETUP_ANNOTATIONS(NopOpcode::new); - - private final VersionMapping versionLookup; - - MetaOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - @Override - 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 deleted file mode 100644 index 89cfe2dd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ModuleOpDescriptor.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.module.ImportFromOpcode; -import ai.timefold.jpyinterpreter.opcodes.module.ImportNameOpcode; - -public enum ModuleOpDescriptor implements OpcodeDescriptor { - IMPORT_NAME(ImportNameOpcode::new), - IMPORT_FROM(ImportFromOpcode::new), - - /** - * Loads all symbols not starting with '_' directly from the module TOS to the local namespace. The module is popped - * after - * loading all names. This opcode implements from module import *. - */ - IMPORT_STAR(instruction -> { - // From https://docs.python.org/3/reference/simple_stmts.html#the-import-statement , - // Import * is only allowed at the module level and as such WILL never appear - // in functions. - throw new UnsupportedOperationException( - "Impossible state/invalid bytecode: import * only allowed at module level"); - }); - - final VersionMapping versionLookup; - - ModuleOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - @Override - 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 deleted file mode 100644 index 5aa0286f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ObjectOpDescriptor.java +++ /dev/null @@ -1,41 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -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(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 VersionMapping versionLookup; - - ObjectOpDescriptor(Function opcodeFunction) { - this(VersionMapping.constantMapping(opcodeFunction)); - } - - ObjectOpDescriptor(VersionMapping versionLookup) { - this.versionLookup = versionLookup; - } - - @Override - 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 deleted file mode 100644 index d179bc1e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/OpcodeDescriptor.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -public sealed interface OpcodeDescriptor permits AsyncOpDescriptor, - CollectionOpDescriptor, - ControlOpDescriptor, - DunderOpDescriptor, - ExceptionOpDescriptor, - FunctionOpDescriptor, - GeneratorOpDescriptor, - MetaOpDescriptor, - ModuleOpDescriptor, - ObjectOpDescriptor, - StackOpDescriptor, - StringOpDescriptor, - VariableOpDescriptor { - String name(); - - 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 deleted file mode 100644 index 9ec5fc2c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StackOpDescriptor.java +++ /dev/null @@ -1,68 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.stack.CopyOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.DupOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.DupTwoOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.PopOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.RotateFourOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.RotateThreeOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.RotateTwoOpcode; -import ai.timefold.jpyinterpreter.opcodes.stack.SwapOpcode; - -public enum StackOpDescriptor implements OpcodeDescriptor { - /** - * Removes the top-of-stack (TOS) item. - */ - POP_TOP(PopOpcode::new), - - /** - * Swaps the two top-most stack items. - */ - ROT_TWO(RotateTwoOpcode::new), - - /** - * Lifts second and third stack item one position up, moves top down to position three. - */ - ROT_THREE(RotateThreeOpcode::new), - - /** - * Lifts second, third and fourth stack items one position up, moves top down to position four. - */ - ROT_FOUR(RotateFourOpcode::new), - - /** - * Push the i-th item to the top of the stack. The item is not removed from its original location. - * Uses 1-based indexing (TOS is 1, TOS1 is 2, ...) instead of 0-based indexing. - */ - COPY(CopyOpcode::new), - - /** - * Swap TOS with the item at position i. Uses 1-based indexing (TOS is 1, TOS1 is 2, ...) instead of 0-based indexing. - */ - SWAP(SwapOpcode::new), - - /** - * Duplicates the reference on top of the stack. - */ - DUP_TOP(DupOpcode::new), - - /** - * Duplicates the two references on top of the stack, leaving them in the same order. - */ - DUP_TOP_TWO(DupTwoOpcode::new); - - final VersionMapping versionLookup; - - StackOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - @Override - 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 deleted file mode 100644 index 0d571740..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StringOpDescriptor.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.string.BuildStringOpcode; -import ai.timefold.jpyinterpreter.opcodes.string.FormatValueOpcode; -import ai.timefold.jpyinterpreter.opcodes.string.PrintExprOpcode; - -public enum StringOpDescriptor implements OpcodeDescriptor { - /** - * Implements the expression statement for the interactive mode. TOS is removed from the stack and printed. - * In non-interactive mode, an expression statement is terminated with POP_TOP. - */ - PRINT_EXPR(PrintExprOpcode::new), - FORMAT_VALUE(FormatValueOpcode::new), - BUILD_STRING(BuildStringOpcode::new); - - final VersionMapping versionLookup; - - StringOpDescriptor(Function opcodeFunction) { - this.versionLookup = VersionMapping.constantMapping(opcodeFunction); - } - - @Override - 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 deleted file mode 100644 index 0316f3c1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VariableOpDescriptor.java +++ /dev/null @@ -1,58 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.variable.DeleteDerefOpcode; -import ai.timefold.jpyinterpreter.opcodes.variable.DeleteFastOpcode; -import ai.timefold.jpyinterpreter.opcodes.variable.DeleteGlobalOpcode; -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; -import ai.timefold.jpyinterpreter.opcodes.variable.StoreFastOpcode; -import ai.timefold.jpyinterpreter.opcodes.variable.StoreGlobalOpcode; - -public enum VariableOpDescriptor implements OpcodeDescriptor { - LOAD_CONST(LoadConstantOpcode::new), - - 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(VersionMapping.unimplemented()); - - final VersionMapping versionLookup; - - VariableOpDescriptor(Function opcodeFunction) { - this(VersionMapping.constantMapping(opcodeFunction)); - } - - VariableOpDescriptor(VersionMapping lookup) { - this.versionLookup = lookup; - } - - @Override - 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 deleted file mode 100644 index 49dfed4a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VersionMapping.java +++ /dev/null @@ -1,71 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.descriptor; - -import java.util.NavigableMap; -import java.util.Objects; -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, Objects.requireNonNull(mapper)); - } - - public static VersionMapping constantMapping(BiFunction mapper) { - return new VersionMapping() - .map(PythonVersion.MINIMUM_PYTHON_VERSION, Objects.requireNonNull(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/BinaryDunderOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/BinaryDunderOpcode.java deleted file mode 100644 index 0acc766e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/BinaryDunderOpcode.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.dunder; - -import java.util.Optional; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -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; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class BinaryDunderOpcode extends AbstractOpcode { - - final PythonBinaryOperator operator; - - public BinaryDunderOpcode(PythonBytecodeInstruction instruction, PythonBinaryOperator operator) { - super(instruction); - this.operator = operator; - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - PythonLikeType leftOperand = - Optional.ofNullable(stackMetadata.getTypeAtStackIndex(1)).orElse(BuiltinTypes.BASE_TYPE); - PythonLikeType rightOperand = - Optional.ofNullable(stackMetadata.getTypeAtStackIndex(0)).orElse(BuiltinTypes.BASE_TYPE); - - Optional maybeKnownFunctionType = leftOperand.getMethodType(operator.getDunderMethod()); - if (maybeKnownFunctionType.isPresent()) { - PythonKnownFunctionType knownFunctionType = maybeKnownFunctionType.get(); - Optional maybeFunctionSignature = knownFunctionType.getFunctionForParameters(rightOperand); - if (maybeFunctionSignature.isPresent()) { - PythonFunctionSignature functionSignature = maybeFunctionSignature.get(); - return stackMetadata.pop().pop().push(ValueSourceInfo.of(this, functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - } - - // TODO: Right dunder method - return stackMetadata.pop().pop() - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.binaryOperator(functionMetadata.methodVisitor, stackMetadata, operator); - } -} 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 deleted file mode 100644 index 48f5fa42..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/CompareOpcode.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.dunder; - -import ai.timefold.jpyinterpreter.CompareOp; -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 CompareOpcode extends AbstractOpcode { - - public CompareOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.pop(2).push(ValueSourceInfo.of(this, BuiltinTypes.BOOLEAN_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.compareValues(functionMetadata.methodVisitor, stackMetadata, - 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 deleted file mode 100644 index 3b510c19..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/GetSliceOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -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/NotOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/NotOpcode.java deleted file mode 100644 index 45fc0221..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/NotOpcode.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.dunder; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.DunderOperatorImplementor; -import ai.timefold.jpyinterpreter.implementors.PythonBuiltinOperatorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class NotOpcode extends AbstractOpcode { - - public NotOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.BOOLEAN_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.unaryOperator(functionMetadata.methodVisitor, stackMetadata, - PythonUnaryOperator.AS_BOOLEAN); - PythonBuiltinOperatorImplementor.performNotOnTOS(functionMetadata.methodVisitor); - } -} 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 deleted file mode 100644 index d697fed8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/StoreSliceOpcode.java +++ /dev/null @@ -1,23 +0,0 @@ -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/dunder/TernaryDunderOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/TernaryDunderOpcode.java deleted file mode 100644 index db8619f3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/TernaryDunderOpcode.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.dunder; - -public class TernaryDunderOpcode { - -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/UniDunerOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/UniDunerOpcode.java deleted file mode 100644 index 34beded8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/UniDunerOpcode.java +++ /dev/null @@ -1,51 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.dunder; - -import java.util.Optional; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -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; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UniDunerOpcode extends AbstractOpcode { - - final PythonUnaryOperator operator; - - public UniDunerOpcode(PythonBytecodeInstruction instruction, PythonUnaryOperator operator) { - super(instruction); - this.operator = operator; - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - PythonLikeType operand = - Optional.ofNullable(stackMetadata.getTOSType()).orElse(BuiltinTypes.BASE_TYPE); - - Optional maybeKnownFunctionType = operand.getMethodType(operator.getDunderMethod()); - if (maybeKnownFunctionType.isPresent()) { - PythonKnownFunctionType knownFunctionType = maybeKnownFunctionType.get(); - Optional maybeFunctionSignature = knownFunctionType.getFunctionForParameters(); - if (maybeFunctionSignature.isPresent()) { - PythonFunctionSignature functionSignature = maybeFunctionSignature.get(); - return stackMetadata.pop().push(ValueSourceInfo.of(this, functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - } - - return stackMetadata.pop() - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - DunderOperatorImplementor.unaryOperator(functionMetadata.methodVisitor, stackMetadata, operator); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java deleted file mode 100644 index 33db6439..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java +++ /dev/null @@ -1,30 +0,0 @@ -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 ai.timefold.jpyinterpreter.types.PythonLikeFunction; - -public class BeforeWithOpcode extends AbstractOpcode { - - public BeforeWithOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata - .pop() - .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), stackMetadata.getTOSValueSource())) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.beforeWith(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CheckExcMatchOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CheckExcMatchOpcode.java deleted file mode 100644 index 97b37f30..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CheckExcMatchOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -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; - -public class CheckExcMatchOpcode extends AbstractOpcode { - - public CheckExcMatchOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata - .pop() - .push(ValueSourceInfo.of(this, BuiltinTypes.BOOLEAN_TYPE, stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.checkExcMatch(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 deleted file mode 100644 index ed8ed639..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CleanupThrowOpcode.java +++ /dev/null @@ -1,30 +0,0 @@ -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/LoadAssertionErrorOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/LoadAssertionErrorOpcode.java deleted file mode 100644 index 1f08bdd1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/LoadAssertionErrorOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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.errors.PythonAssertionError; - -public class LoadAssertionErrorOpcode extends AbstractOpcode { - - public LoadAssertionErrorOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.push(ValueSourceInfo.of(this, PythonAssertionError.ASSERTION_ERROR_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.createAssertionError(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopBlockOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopBlockOpcode.java deleted file mode 100644 index 763b1795..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopBlockOpcode.java +++ /dev/null @@ -1,23 +0,0 @@ -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.opcodes.AbstractOpcode; - -public class PopBlockOpcode extends AbstractOpcode { - - public PopBlockOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.copy(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - // Pop block has a stack effect of 0 (does nothing); ASM take care of popping blocks for us via computeFrames - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopExceptOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopExceptOpcode.java deleted file mode 100644 index 16765f0c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PopExceptOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class PopExceptOpcode extends AbstractOpcode { - - public PopExceptOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - return stackMetadata.pop(1); - } else { - return stackMetadata.pop(3); - } - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.startExceptOrFinally(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PushExcInfoOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PushExcInfoOpcode.java deleted file mode 100644 index e15267c3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/PushExcInfoOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -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.errors.PythonBaseException; - -public class PushExcInfoOpcode extends AbstractOpcode { - - public PushExcInfoOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata - .pop() - .push(ValueSourceInfo.of(this, PythonBaseException.BASE_EXCEPTION_TYPE)) - .push(stackMetadata.getTOSValueSource()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.pushExcInfo(functionMetadata, stackMetadata); - } -} 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 deleted file mode 100644 index 71687493..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/RaiseVarargsOpcode.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import java.util.Collections; -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; -import ai.timefold.jpyinterpreter.opcodes.controlflow.AbstractControlFlowOpcode; - -public class RaiseVarargsOpcode extends AbstractControlFlowOpcode { - - public RaiseVarargsOpcode(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; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.raiseWithOptionalExceptionAndCause(functionMetadata.methodVisitor, instruction, - stackMetadata.localVariableHelper); - } -} 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 deleted file mode 100644 index 329a20ba..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/ReraiseOpcode.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import java.util.Collections; -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; -import ai.timefold.jpyinterpreter.opcodes.controlflow.AbstractControlFlowOpcode; - -public class ReraiseOpcode extends AbstractControlFlowOpcode { - - public ReraiseOpcode(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; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.reraise(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupFinallyOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupFinallyOpcode.java deleted file mode 100644 index 11f270e2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupFinallyOpcode.java +++ /dev/null @@ -1,52 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; -import ai.timefold.jpyinterpreter.opcodes.controlflow.AbstractControlFlowOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; -import ai.timefold.jpyinterpreter.types.errors.PythonTraceback; - -public class SetupFinallyOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public SetupFinallyOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of(getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of(stackMetadata.copy(), - stackMetadata.copy() - .pushTemp(BuiltinTypes.NONE_TYPE) - .pushTemp(BuiltinTypes.INT_TYPE) - .pushTemp(BuiltinTypes.NONE_TYPE) - .pushTemp(PythonTraceback.TRACEBACK_TYPE) - .pushTemp(PythonBaseException.BASE_EXCEPTION_TYPE) - .pushTemp(PythonBaseException.BASE_EXCEPTION_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.createTryFinallyBlock(functionMetadata, stackMetadata, - jumpTarget, - functionMetadata.bytecodeCounterToLabelMap, - (bytecodeIndex, runnable) -> { - functionMetadata.bytecodeCounterToCodeArgumenterList - .computeIfAbsent(bytecodeIndex, key -> new ArrayList<>()).add(runnable); - }); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java deleted file mode 100644 index c67007cc..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import java.util.List; - -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.controlflow.AbstractControlFlowOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; -import ai.timefold.jpyinterpreter.types.errors.PythonTraceback; - -public class SetupWithOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public SetupWithOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of( - stackMetadata - .pop() - .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), stackMetadata.getTOSValueSource())) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())), - stackMetadata - .pop() - .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), stackMetadata.getTOSValueSource())) - .pushTemp(BuiltinTypes.NONE_TYPE) - .pushTemp(BuiltinTypes.INT_TYPE) - .pushTemp(BuiltinTypes.NONE_TYPE) - .pushTemp(PythonTraceback.TRACEBACK_TYPE) - .pushTemp(PythonBaseException.BASE_EXCEPTION_TYPE) - .pushTemp(BuiltinTypes.TYPE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.setupWith(jumpTarget, functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java deleted file mode 100644 index 8193716a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.exceptions; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -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 WithExceptStartOpcode extends AbstractOpcode { - - public WithExceptStartOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { - return stackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourceForStackIndex(1))); - } - return stackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourceForStackIndex(6))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.handleExceptionInWith(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionKeywordOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionKeywordOpcode.java deleted file mode 100644 index 54fdcf4b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionKeywordOpcode.java +++ /dev/null @@ -1,45 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -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.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeGenericType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class CallFunctionKeywordOpcode extends AbstractOpcode { - - public CallFunctionKeywordOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg() + 1); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - return knownFunctionType.getDefaultFunctionSignature() - .map(functionSignature -> stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, - functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))); - } - return stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.callFunctionWithKeywords(functionMetadata, stackMetadata, functionMetadata.methodVisitor, - instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionOpcode.java deleted file mode 100644 index 8cbe7adc..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionOpcode.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -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.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeGenericType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class CallFunctionOpcode extends AbstractOpcode { - - public CallFunctionOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg()); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - return knownFunctionType.getDefaultFunctionSignature() - .map(functionSignature -> stackMetadata.pop(instruction.arg() + 1).push(ValueSourceInfo.of(this, - functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 1)))) - .orElseGet(() -> stackMetadata.pop(instruction.arg() + 1) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 1)))); - } - return stackMetadata.pop(instruction.arg() + 1).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 1))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.callFunction(functionMetadata, stackMetadata, functionMetadata.methodVisitor, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionUnpackOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionUnpackOpcode.java deleted file mode 100644 index b1674bed..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallFunctionUnpackOpcode.java +++ /dev/null @@ -1,47 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class CallFunctionUnpackOpcode extends AbstractOpcode { - - public CallFunctionUnpackOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { - if ((instruction.arg() & 1) == 1) { - // Stack is callable, iterable, map - return stackMetadata.pop(3).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(3))); - } else { - // Stack is callable, iterable - return stackMetadata.pop(2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - } else { - if ((instruction.arg() & 1) == 1) { - // Stack is null, callable, iterable, map - return stackMetadata.pop(4).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(3))); - } else { - // Stack is null, callable, iterable - return stackMetadata.pop(3).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - } - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.callFunctionUnpack(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallMethodOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallMethodOpcode.java deleted file mode 100644 index 397d5c7f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallMethodOpcode.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -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.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class CallMethodOpcode extends AbstractOpcode { - - public CallMethodOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg() + 1); - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - PythonLikeType[] parameterTypes = - new PythonLikeType[instruction.arg()]; - for (int i = 0; i < parameterTypes.length; i++) { - parameterTypes[parameterTypes.length - i - 1] = stackMetadata.getTypeAtStackIndex(i); - } - return knownFunctionType.getFunctionForParameters(parameterTypes) - .map(functionSignature -> stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, - functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))); - } - return stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.callMethod(functionMetadata, stackMetadata, functionMetadata.methodVisitor, instruction, - stackMetadata.localVariableHelper); - } -} 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 deleted file mode 100644 index 02c97fb4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallOpcode.java +++ /dev/null @@ -1,79 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -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.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; -import ai.timefold.jpyinterpreter.types.PythonLikeGenericType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class CallOpcode extends AbstractOpcode { - - public CallOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType functionType = stackMetadata.getTypeAtStackIndex(instruction.arg() + 1); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - List keywordArgumentNameList = stackMetadata.getCallKeywordNameList(); - List callStackParameterTypes = stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()) - .stream().map(ValueSourceInfo::getValueType).collect(Collectors.toList()); - - return knownFunctionType.getFunctionForParameters(instruction.arg() - keywordArgumentNameList.size(), - keywordArgumentNameList, - callStackParameterTypes) - .map(functionSignature -> stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, - functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .setCallKeywordNameList(Collections.emptyList()); - } - - functionType = stackMetadata.getTypeAtStackIndex(instruction.arg()); - if (functionType instanceof PythonLikeGenericType) { - functionType = ((PythonLikeGenericType) functionType).getOrigin().getConstructorType().orElse(null); - } - if (functionType instanceof PythonKnownFunctionType) { - PythonKnownFunctionType knownFunctionType = (PythonKnownFunctionType) functionType; - List keywordArgumentNameList = stackMetadata.getCallKeywordNameList(); - List callStackParameterTypes = stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()) - .stream().map(ValueSourceInfo::getValueType).collect(Collectors.toList()); - - return knownFunctionType.getFunctionForParameters(instruction.arg() - keywordArgumentNameList.size(), - keywordArgumentNameList, - callStackParameterTypes) - .map(functionSignature -> stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, - functionSignature.getReturnType(), - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .setCallKeywordNameList(Collections.emptyList()); - } - - return stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2))) - .setCallKeywordNameList(Collections.emptyList()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.call(functionMetadata, stackMetadata, instruction.arg()); - } -} 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 deleted file mode 100644 index ad4c0ae2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/LoadMethodOpcode.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -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.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeGenericType; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class LoadMethodOpcode extends AbstractOpcode { - - public LoadMethodOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType stackTosType = stackMetadata.getTOSType(); - PythonLikeType tosType; - if (stackTosType instanceof PythonLikeGenericType) { - tosType = ((PythonLikeGenericType) stackTosType).getOrigin(); - } else { - tosType = stackTosType; - } - - return tosType.getMethodType(functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg())) - .map(knownFunction -> stackMetadata.pop() - .push(ValueSourceInfo.of(this, knownFunction, stackMetadata.getValueSourcesUpToStackIndex(1))) - .push(ValueSourceInfo.of(this, tosType, stackMetadata.getValueSourcesUpToStackIndex(1))) // TOS, since we know the function exists - ) - .orElseGet(() -> stackMetadata.pop() - .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), - stackMetadata.getValueSourcesUpToStackIndex(1))) - .push(ValueSourceInfo.of(this, BuiltinTypes.NULL_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1)))); // either TOS or NULL - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.loadMethod(functionMetadata, stackMetadata, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/MakeFunctionOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/MakeFunctionOpcode.java deleted file mode 100644 index e6994a3d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/MakeFunctionOpcode.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; - -public class MakeFunctionOpcode extends AbstractOpcode { - - public MakeFunctionOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - int stackElements = - (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) ? 1 : 2; - return stackMetadata.pop(stackElements + Integer.bitCount(instruction.arg())) - .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), - stackMetadata.getValueSourcesUpToStackIndex(stackElements + Integer.bitCount(instruction.arg())))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.createFunction(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/PushNullOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/PushNullOpcode.java deleted file mode 100644 index af2eb2fd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/PushNullOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -import org.objectweb.asm.Opcodes; - -public class PushNullOpcode extends AbstractOpcode { - - public PushNullOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - functionMetadata.methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/SetCallKeywordNameTupleOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/SetCallKeywordNameTupleOpcode.java deleted file mode 100644 index c2d4a1a9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/SetCallKeywordNameTupleOpcode.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.function; - -import java.util.List; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.FunctionImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonString; - -public class SetCallKeywordNameTupleOpcode extends AbstractOpcode { - - public SetCallKeywordNameTupleOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.setCallKeywordNameList( - ((List) functionMetadata.pythonCompiledFunction.co_constants.get(instruction.arg())) - .stream().map(PythonString::getValue).collect(Collectors.toList())); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.setCallKeywordNameTuple(functionMetadata, stackMetadata, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GeneratorStartOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GeneratorStartOpcode.java deleted file mode 100644 index 26433b01..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GeneratorStartOpcode.java +++ /dev/null @@ -1,24 +0,0 @@ -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.implementors.GeneratorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class GeneratorStartOpcode extends AbstractOpcode { - - public GeneratorStartOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata; - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - GeneratorImplementor.generatorStart(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GetYieldFromIterOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GetYieldFromIterOpcode.java deleted file mode 100644 index 66ab604f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/GetYieldFromIterOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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.GeneratorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class GetYieldFromIterOpcode extends AbstractOpcode { - - public GetYieldFromIterOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - GeneratorImplementor.getYieldFromIter(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/ResumeOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/ResumeOpcode.java deleted file mode 100644 index f45bb820..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/ResumeOpcode.java +++ /dev/null @@ -1,56 +0,0 @@ -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.opcodes.AbstractOpcode; - -import org.objectweb.asm.Opcodes; - -public class ResumeOpcode extends AbstractOpcode { - - public ResumeOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - public static ResumeType getResumeType(int arg) { - switch (arg) { - case 0: - return ResumeType.START; - - case 1: - return ResumeType.YIELD; - - case 2: - return ResumeType.YIELD_FROM; - - case 3: - return ResumeType.AWAIT; - - default: - throw new IllegalArgumentException("Invalid RESUME opcode argument: " + arg); - } - } - - public ResumeType getResumeType() { - return getResumeType(instruction.arg()); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.copy(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - functionMetadata.methodVisitor.visitInsn(Opcodes.NOP); - } - - public enum ResumeType { - START, - YIELD, - YIELD_FROM, - AWAIT - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/SendOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/SendOpcode.java deleted file mode 100644 index caf80274..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/SendOpcode.java +++ /dev/null @@ -1,41 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.generator; - -import java.util.List; - -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.GeneratorImplementor; -import ai.timefold.jpyinterpreter.opcodes.controlflow.AbstractControlFlowOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class SendOpcode extends AbstractControlFlowOpcode { - int jumpTarget; - - public SendOpcode(PythonBytecodeInstruction instruction, int jumpTarget) { - super(instruction); - this.jumpTarget = jumpTarget; - } - - @Override - public List getPossibleNextBytecodeIndexList() { - return List.of( - getBytecodeIndex() + 1, - jumpTarget); - } - - @Override - public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return List.of( - stackMetadata.pop() - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(2))), - stackMetadata.pop()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - GeneratorImplementor.progressSubgenerator(functionMetadata, stackMetadata, jumpTarget); - } -} 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 deleted file mode 100644 index 75d44512..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/StopIteratorErrorOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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/generator/YieldFromOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/YieldFromOpcode.java deleted file mode 100644 index 24471881..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/YieldFromOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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.GeneratorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class YieldFromOpcode extends AbstractOpcode { - - public YieldFromOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - GeneratorImplementor.yieldFrom(instruction, functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/YieldValueOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/YieldValueOpcode.java deleted file mode 100644 index bb9995e8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/YieldValueOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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.GeneratorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class YieldValueOpcode extends AbstractOpcode { - - public YieldValueOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - GeneratorImplementor.yieldValue(instruction, functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/NopOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/NopOpcode.java deleted file mode 100644 index 56596680..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/NopOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.meta; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -import org.objectweb.asm.Opcodes; - -public class NopOpcode extends AbstractOpcode { - - public NopOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.copy(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - functionMetadata.methodVisitor.visitInsn(Opcodes.NOP); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/ReturnGeneratorOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/ReturnGeneratorOpcode.java deleted file mode 100644 index 092cf201..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/ReturnGeneratorOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.meta; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -import org.objectweb.asm.Opcodes; - -public class ReturnGeneratorOpcode extends AbstractOpcode { - - public ReturnGeneratorOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - // Although this opcode does nothing, it is followed by a POP_TOP, which need something to pop - return stackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - functionMetadata.methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } -} 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 deleted file mode 100644 index 860f0f3c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/UnaryIntrinsicFunction.java +++ /dev/null @@ -1,57 +0,0 @@ -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/module/ImportFromOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/module/ImportFromOpcode.java deleted file mode 100644 index 155bd184..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/module/ImportFromOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.module; - -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.ModuleImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class ImportFromOpcode extends AbstractOpcode { - - public ImportFromOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ModuleImplementor.importFrom(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/module/ImportNameOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/module/ImportNameOpcode.java deleted file mode 100644 index 08f46db4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/module/ImportNameOpcode.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.module; - -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.ModuleImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonModule; - -public class ImportNameOpcode extends AbstractOpcode { - - public ImportNameOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2) - .push(ValueSourceInfo.of(this, PythonModule.MODULE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ModuleImplementor.importName(functionMetadata, stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/DeleteAttrOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/DeleteAttrOpcode.java deleted file mode 100644 index 5d6430e0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/DeleteAttrOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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.implementors.ObjectImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class DeleteAttrOpcode extends AbstractOpcode { - - public DeleteAttrOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(1); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ObjectImplementor.deleteAttribute(functionMetadata, functionMetadata.methodVisitor, functionMetadata.className, - stackMetadata, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/IsOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/IsOpcode.java deleted file mode 100644 index ec807e02..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/IsOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -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.PythonBuiltinOperatorImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class IsOpcode extends AbstractOpcode { - - public IsOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2).push(ValueSourceInfo.of(this, BuiltinTypes.BOOLEAN_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonBuiltinOperatorImplementor.isOperator(functionMetadata.methodVisitor, 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 deleted file mode 100644 index 09ce0ee4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadAttrOpcode.java +++ /dev/null @@ -1,35 +0,0 @@ -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; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class LoadAttrOpcode extends AbstractOpcode { - public LoadAttrOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeType tosType = stackMetadata.getTOSType(); - 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)))) - .orElseGet(() -> stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1)))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ObjectImplementor.getAttribute(functionMetadata, - stackMetadata, - 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 deleted file mode 100644 index f327440c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadSuperAttrOpcode.java +++ /dev/null @@ -1,41 +0,0 @@ -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/object/StoreAttrOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/StoreAttrOpcode.java deleted file mode 100644 index 60efea38..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/StoreAttrOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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.implementors.ObjectImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class StoreAttrOpcode extends AbstractOpcode { - - public StoreAttrOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(2); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ObjectImplementor.setAttribute(functionMetadata, functionMetadata.methodVisitor, functionMetadata.className, - stackMetadata, - instruction, stackMetadata.localVariableHelper); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/CopyOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/CopyOpcode.java deleted file mode 100644 index 2683ae8d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/CopyOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class CopyOpcode extends AbstractOpcode { - - public CopyOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.push(stackMetadata.getValueSourceForStackIndex(instruction.arg() - 1)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.duplicateToTOS(functionMetadata, stackMetadata, instruction.arg() - 1); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupOpcode.java deleted file mode 100644 index 80974867..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class DupOpcode extends AbstractOpcode { - - public DupOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.push(stackMetadata.getTOSValueSource()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.duplicateTOS(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupTwoOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupTwoOpcode.java deleted file mode 100644 index d84ba313..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/DupTwoOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class DupTwoOpcode extends AbstractOpcode { - - public DupTwoOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackTypesBeforeInstruction) { - return stackTypesBeforeInstruction - .push(stackTypesBeforeInstruction.getValueSourceForStackIndex(1)) - .push(stackTypesBeforeInstruction.getValueSourceForStackIndex(0)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.duplicateTOSAndTOS1(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/PopOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/PopOpcode.java deleted file mode 100644 index 42251a23..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/PopOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class PopOpcode extends AbstractOpcode { - - public PopOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.popTOS(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateFourOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateFourOpcode.java deleted file mode 100644 index 9faff388..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateFourOpcode.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class RotateFourOpcode extends AbstractOpcode { - - public RotateFourOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata - .pop() - .pop() - .pop() - .pop() - .push(stackMetadata.getValueSourceForStackIndex(0)) - .push(stackMetadata.getValueSourceForStackIndex(3)) - .push(stackMetadata.getValueSourceForStackIndex(2)) - .push(stackMetadata.getValueSourceForStackIndex(1)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.rotateFour(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateThreeOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateThreeOpcode.java deleted file mode 100644 index 7492dbfe..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateThreeOpcode.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class RotateThreeOpcode extends AbstractOpcode { - - public RotateThreeOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata - .pop() - .pop() - .pop() - .push(stackMetadata.getValueSourceForStackIndex(0)) - .push(stackMetadata.getValueSourceForStackIndex(2)) - .push(stackMetadata.getValueSourceForStackIndex(1)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.rotateThree(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateTwoOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateTwoOpcode.java deleted file mode 100644 index efb5aff8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/RotateTwoOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class RotateTwoOpcode extends AbstractOpcode { - - public RotateTwoOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata - .pop() - .pop() - .push(stackMetadata.getValueSourceForStackIndex(0)) - .push(stackMetadata.getValueSourceForStackIndex(1)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.swap(functionMetadata.methodVisitor); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/SwapOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/SwapOpcode.java deleted file mode 100644 index c24bd697..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/stack/SwapOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.stack; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StackManipulationImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class SwapOpcode extends AbstractOpcode { - - public SwapOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata - .set(instruction.arg() - 1, stackMetadata.getTOSValueSource()) - .set(0, stackMetadata.getValueSourceForStackIndex(instruction.arg() - 1)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StackManipulationImplementor.swapTOSWithIndex(functionMetadata, stackMetadata, instruction.arg() - 1); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/BuildStringOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/BuildStringOpcode.java deleted file mode 100644 index 84fdf243..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/BuildStringOpcode.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.string; - -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.StringImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class BuildStringOpcode extends AbstractOpcode { - - public BuildStringOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.pop(instruction.arg()).push( - ValueSourceInfo.of(this, BuiltinTypes.STRING_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StringImplementor.buildString(functionMetadata.methodVisitor, instruction.arg()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/FormatValueOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/FormatValueOpcode.java deleted file mode 100644 index 052c5119..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/FormatValueOpcode.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.string; - -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.StringImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; - -public class FormatValueOpcode extends AbstractOpcode { - - public FormatValueOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - if ((instruction.arg() & 4) == 4) { - // There is a format argument above the value - return stackMetadata.pop(2) - .push(ValueSourceInfo.of(this, BuiltinTypes.STRING_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(2))); - } else { - // There is no format argument above the value - return stackMetadata.pop() - .push(ValueSourceInfo.of(this, BuiltinTypes.STRING_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(1))); - } - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StringImplementor.formatValue(functionMetadata.methodVisitor, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/PrintExprOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/PrintExprOpcode.java deleted file mode 100644 index 4bc8e109..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/string/PrintExprOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.string; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.implementors.StringImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; - -public class PrintExprOpcode extends AbstractOpcode { - - public PrintExprOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, - StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - StringImplementor.print(functionMetadata, stackMetadata); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteDerefOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteDerefOpcode.java deleted file mode 100644 index be0dda9b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteDerefOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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 DeleteDerefOpcode extends AbstractOpcode { - - public DeleteDerefOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.setCellVariableValueSource(VariableImplementor.getCellIndex(functionMetadata, instruction.arg()), - null); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.deleteCellVariable(functionMetadata, stackMetadata, - VariableImplementor.getCellIndex(functionMetadata, instruction.arg())); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteFastOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteFastOpcode.java deleted file mode 100644 index c42cbeb8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteFastOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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 DeleteFastOpcode extends AbstractOpcode { - - public DeleteFastOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.setLocalVariableValueSource(instruction.arg(), null); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.deleteLocalVariable(functionMetadata.methodVisitor, instruction, - stackMetadata.localVariableHelper); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteGlobalOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteGlobalOpcode.java deleted file mode 100644 index 243aa0c9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/DeleteGlobalOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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 DeleteGlobalOpcode extends AbstractOpcode { - - public DeleteGlobalOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.copy(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.deleteGlobalVariable(functionMetadata.methodVisitor, functionMetadata.className, - functionMetadata.pythonCompiledFunction, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadClosureOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadClosureOpcode.java deleted file mode 100644 index 97fbff06..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadClosureOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -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.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.VariableImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonCell; - -public class LoadClosureOpcode extends AbstractOpcode { - - public LoadClosureOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.push(ValueSourceInfo.of(this, PythonCell.CELL_TYPE)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.loadCell(functionMetadata, stackMetadata, - VariableImplementor.getCellIndex(functionMetadata, instruction.arg())); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadConstantOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadConstantOpcode.java deleted file mode 100644 index 1871404b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadConstantOpcode.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.variable; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.PythonConstantsImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -import org.objectweb.asm.Opcodes; - -public class LoadConstantOpcode extends AbstractOpcode { - - public LoadConstantOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeObject constant = functionMetadata.pythonCompiledFunction.co_constants.get(instruction.arg()); - PythonLikeType constantType = constant.$getGenericType(); - return stackMetadata.push(ValueSourceInfo.of(this, constantType)); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - PythonLikeObject constant = functionMetadata.pythonCompiledFunction.co_constants.get(instruction.arg()); - PythonLikeType constantType = constant.$getGenericType(); - - PythonConstantsImplementor.loadConstant(functionMetadata.methodVisitor, functionMetadata.className, - instruction.arg()); - functionMetadata.methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, constantType.getJavaTypeInternalName()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadDerefOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadDerefOpcode.java deleted file mode 100644 index bf29f1cb..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadDerefOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -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 LoadDerefOpcode extends AbstractOpcode { - - public LoadDerefOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.push( - stackMetadata - .getCellVariableValueSource(VariableImplementor.getCellIndex(functionMetadata, instruction.arg()))); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.loadCellVariable(functionMetadata, stackMetadata, - VariableImplementor.getCellIndex(functionMetadata, instruction.arg())); - } -} 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 deleted file mode 100644 index d8d5afa5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastAndClearOpcode.java +++ /dev/null @@ -1,27 +0,0 @@ -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/opcodes/variable/LoadFastOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastOpcode.java deleted file mode 100644 index 260b5a6e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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 LoadFastOpcode extends AbstractOpcode { - - public LoadFastOpcode(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); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadGlobalOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadGlobalOpcode.java deleted file mode 100644 index f6f745e8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadGlobalOpcode.java +++ /dev/null @@ -1,82 +0,0 @@ -package ai.timefold.jpyinterpreter.opcodes.variable; - -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.implementors.VariableImplementor; -import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.wrappers.CPythonType; -import ai.timefold.jpyinterpreter.types.wrappers.PythonObjectWrapper; - -import org.objectweb.asm.Opcodes; - -public class LoadGlobalOpcode extends AbstractOpcode { - - public LoadGlobalOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - private int getGlobalIndex(FunctionMetadata functionMetadata) { - return (functionMetadata.pythonCompiledFunction.pythonVersion.compareTo(PythonVersion.PYTHON_3_11) >= 0) - ? instruction.arg() >> 1 - : instruction.arg(); - } - - private boolean pushNullBeforeGlobal(FunctionMetadata functionMetadata) { - return functionMetadata.pythonCompiledFunction.pythonVersion.compareTo(PythonVersion.PYTHON_3_11) >= 0 - && ((instruction.arg() & 1) == 1); - } - - private PythonLikeObject getGlobal(FunctionMetadata functionMetadata) { - return functionMetadata.pythonCompiledFunction.globalsMap - .get(functionMetadata.pythonCompiledFunction.co_names.get(getGlobalIndex(functionMetadata))); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - boolean pushNull = pushNullBeforeGlobal(functionMetadata); - PythonLikeObject global = getGlobal(functionMetadata); - - if (pushNull) { - if (global != null) { - return stackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.NULL_TYPE)) - .push(ValueSourceInfo.of(this, global.$getGenericType())); - } else { - return stackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.NULL_TYPE)) - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - } else { - if (global != null) { - return stackMetadata - .push(ValueSourceInfo.of(this, global.$getGenericType())); - } else { - return stackMetadata - .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE)); - } - } - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - int globalIndex = getGlobalIndex(functionMetadata); - boolean pushNull = pushNullBeforeGlobal(functionMetadata); - - PythonLikeObject global = getGlobal(functionMetadata); - - if (global instanceof CPythonType || global instanceof PythonObjectWrapper) { - // TODO: note native objects are used somewhere - } - - if (pushNull) { - functionMetadata.methodVisitor.visitInsn(Opcodes.ACONST_NULL); - } - VariableImplementor.loadGlobalVariable(functionMetadata, stackMetadata, globalIndex, - (global != null) ? global.$getGenericType() : BuiltinTypes.BASE_TYPE); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreDerefOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreDerefOpcode.java deleted file mode 100644 index 91884459..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreDerefOpcode.java +++ /dev/null @@ -1,26 +0,0 @@ -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 StoreDerefOpcode extends AbstractOpcode { - - public StoreDerefOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().setCellVariableValueSource( - VariableImplementor.getCellIndex(functionMetadata, instruction.arg()), stackMetadata.getTOSValueSource()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.storeInCellVariable(functionMetadata, stackMetadata, - VariableImplementor.getCellIndex(functionMetadata, instruction.arg())); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreFastOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreFastOpcode.java deleted file mode 100644 index f73c6532..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreFastOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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 StoreFastOpcode extends AbstractOpcode { - - public StoreFastOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop().setLocalVariableValueSource(instruction.arg(), stackMetadata.getTOSValueSource()); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.storeInLocalVariable(functionMetadata.methodVisitor, instruction, - stackMetadata.localVariableHelper); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreGlobalOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreGlobalOpcode.java deleted file mode 100644 index 3d67230d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/StoreGlobalOpcode.java +++ /dev/null @@ -1,25 +0,0 @@ -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 StoreGlobalOpcode extends AbstractOpcode { - - public StoreGlobalOpcode(PythonBytecodeInstruction instruction) { - super(instruction); - } - - @Override - protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return stackMetadata.pop(); - } - - @Override - public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - VariableImplementor.storeInGlobalVariable(functionMetadata.methodVisitor, functionMetadata.className, - functionMetadata.pythonCompiledFunction, instruction); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java deleted file mode 100644 index 56b73d47..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java +++ /dev/null @@ -1,61 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.HashMap; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; - -public abstract class AbstractPythonLikeObject implements PythonLikeObject { - - public static final PythonLikeType OBJECT_TYPE = new PythonLikeType("object", AbstractPythonLikeObject.class); - - private final PythonLikeType __type__; - private final Map __dir__; - - public AbstractPythonLikeObject(PythonLikeType __type__) { - this(__type__, new HashMap<>()); - } - - public AbstractPythonLikeObject(PythonLikeType __type__, Map __dir__) { - this.__type__ = __type__; - this.__dir__ = __dir__; - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - return __dir__.get(attributeName); - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - __dir__.put(attributeName, value); - } - - @Override - public void $deleteAttribute(String attributeName) { - // TODO: Descriptors: https://docs.python.org/3/howto/descriptor.html - if (!__dir__.containsKey(attributeName)) { - throw new AttributeError("'" + $getType().getTypeName() + "' object has no attribute '" + attributeName + "'"); - } - __dir__.remove(attributeName); - } - - @Override - public PythonLikeType $getType() { - return __type__; - } - - public void setAttribute(String attributeName, PythonLikeObject value) { - __dir__.put(attributeName, value); - } - - public Map getExtraAttributeMap() { - return __dir__; - } - - @Override - public String toString() { - return $method$__str__().toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BoundPythonLikeFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BoundPythonLikeFunction.java deleted file mode 100644 index 60d50131..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BoundPythonLikeFunction.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public class BoundPythonLikeFunction implements PythonLikeFunction { - private final PythonLikeObject instance; - private final PythonLikeFunction function; - - public BoundPythonLikeFunction(PythonLikeObject instance, PythonLikeFunction function) { - this.instance = instance; - this.function = function; - } - - public static BoundPythonLikeFunction boundToTypeOfObject(PythonLikeObject instance, PythonLikeFunction function) { - if (instance instanceof PythonLikeType) { - return new BoundPythonLikeFunction(instance, function); - } else { - return new BoundPythonLikeFunction(instance.$getType(), function); - } - } - - public PythonLikeObject getInstance() { - return instance; - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - ArrayList actualPositionalArgs = new ArrayList<>(positionalArguments.size() + 1); - actualPositionalArgs.add(instance); - actualPositionalArgs.addAll(positionalArguments); - return function.$call(actualPositionalArgs, namedArguments, null); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BuiltinTypes.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BuiltinTypes.java deleted file mode 100644 index 191aae4c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BuiltinTypes.java +++ /dev/null @@ -1,155 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.builtins.FunctionBuiltinOperations; -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeFrozenSet; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.collections.view.DictItemView; -import ai.timefold.jpyinterpreter.types.collections.view.DictKeyView; -import ai.timefold.jpyinterpreter.types.collections.view.DictValueView; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonComplex; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.types.wrappers.JavaMethodReference; - -public class BuiltinTypes { - public static final PythonLikeType NULL_TYPE = - new PythonLikeType("NULL", PythonLikeObject.class, Collections.emptyList()); - public static final PythonLikeType BASE_TYPE = - new PythonLikeType("base-object", PythonLikeObject.class, Collections.emptyList()); - public static final PythonLikeType TYPE_TYPE = new PythonLikeType("type", PythonLikeType.class, List.of(BASE_TYPE)); - public static final PythonLikeType SUPER_TYPE = - new PythonLikeType("super", PythonSuperObject.class, List.of(BASE_TYPE)); - public static final PythonLikeType MODULE_TYPE = new PythonLikeType("module", PythonModule.class, List.of(BASE_TYPE)); - public static final PythonLikeType FUNCTION_TYPE = - new PythonLikeType("function", PythonLikeFunction.class, List.of(BASE_TYPE)); - public static final PythonLikeType CLASS_FUNCTION_TYPE = - new PythonLikeType("classmethod", PythonLikeFunction.class, List.of(BASE_TYPE)); - public static final PythonLikeType STATIC_FUNCTION_TYPE = - new PythonLikeType("staticmethod", PythonLikeFunction.class, List.of(BASE_TYPE)); - public static final PythonLikeType METHOD_TYPE = - new PythonLikeType("method", PythonLikeFunction.class, List.of(BASE_TYPE)); - public static final PythonLikeType GENERATOR_TYPE = - new PythonLikeType("generator", PythonGenerator.class, List.of(BASE_TYPE)); - public static final PythonLikeType CODE_TYPE = new PythonLikeType("code", PythonCode.class, List.of(BASE_TYPE)); - public static final PythonLikeType CELL_TYPE = new PythonLikeType("cell", PythonCell.class, List.of(BASE_TYPE)); - - public static final PythonLikeType NONE_TYPE = new PythonLikeType("NoneType", PythonNone.class, List.of(BASE_TYPE)); - public static final PythonLikeType NOT_IMPLEMENTED_TYPE = - new PythonLikeType("NotImplementedType", NotImplemented.class, List.of(BASE_TYPE)); - public static final PythonLikeType ELLIPSIS_TYPE = new PythonLikeType("EllipsisType", Ellipsis.class, List.of(BASE_TYPE)); - - public static final PythonLikeType NUMBER_TYPE = new PythonLikeType("number", PythonNumber.class, List.of(BASE_TYPE)); - public static final PythonLikeType INT_TYPE = new PythonLikeType("int", PythonInteger.class, List.of(NUMBER_TYPE)); - public static final PythonLikeType BOOLEAN_TYPE = new PythonLikeType("bool", PythonBoolean.class, List.of(INT_TYPE)); - public static final PythonLikeType FLOAT_TYPE = new PythonLikeType("float", PythonFloat.class, List.of(NUMBER_TYPE)); - public final static PythonLikeType COMPLEX_TYPE = new PythonLikeType("complex", PythonComplex.class, List.of(NUMBER_TYPE)); - public final static PythonLikeType DECIMAL_TYPE = new PythonLikeType("Decimal", PythonDecimal.class, List.of(NUMBER_TYPE)); - - public static final PythonLikeType STRING_TYPE = new PythonLikeType("str", PythonString.class, List.of(BASE_TYPE)); - public static final PythonLikeType BYTES_TYPE = new PythonLikeType("bytes", PythonBytes.class, List.of(BASE_TYPE)); - public static final PythonLikeType BYTE_ARRAY_TYPE = - new PythonLikeType("bytearray", PythonByteArray.class, List.of(BASE_TYPE)); - - public static final PythonLikeType ITERATOR_TYPE = new PythonLikeType("iterator", PythonIterator.class, List.of(BASE_TYPE)); - public static final PythonLikeType DICT_TYPE = new PythonLikeType("dict", PythonLikeDict.class, List.of(BASE_TYPE)); - public static final PythonLikeType DICT_ITEM_VIEW_TYPE = - new PythonLikeType("dict_items", DictItemView.class, List.of(BASE_TYPE)); - public static final PythonLikeType DICT_KEY_VIEW_TYPE = - new PythonLikeType("dict_keys", DictKeyView.class, List.of(BASE_TYPE)); - public static final PythonLikeType DICT_VALUE_VIEW_TYPE = - new PythonLikeType("dict_values", DictValueView.class, List.of(BASE_TYPE)); - public static final PythonLikeType SET_TYPE = new PythonLikeType("set", PythonLikeSet.class, List.of(BASE_TYPE)); - public static final PythonLikeType FROZEN_SET_TYPE = - new PythonLikeType("frozenset", PythonLikeFrozenSet.class, List.of(BASE_TYPE)); - public static final PythonLikeType TUPLE_TYPE = new PythonLikeType("tuple", PythonLikeTuple.class, List.of(BASE_TYPE)); - public static final PythonLikeType LIST_TYPE = new PythonLikeType("list", PythonLikeList.class, List.of(BASE_TYPE)); - public static final PythonLikeType RANGE_TYPE = new PythonLikeType("range", PythonRange.class, List.of(BASE_TYPE)); - public static final PythonLikeType SLICE_TYPE = new PythonLikeType("slice", PythonSlice.class, List.of(BASE_TYPE)); - /** - * The ASM generated bytecode. Used by - * asmClassLoader to create the Java versions of Python methods - */ - public static final Map classNameToBytecode = new HashMap<>(); - /** - * A custom classloader that looks for the class in - * classNameToBytecode - */ - public static ClassLoader asmClassLoader = new ClassLoader() { - // getName() is an abstract method in Java 11 but not in Java 8 - public String getName() { - return "JPyInterpreter Python Bytecode ClassLoader"; - } - - @Override - public Class findClass(String name) throws ClassNotFoundException { - if (classNameToBytecode.containsKey(name)) { - // Gizmo generated class - byte[] byteCode = classNameToBytecode.get(name); - return defineClass(name, byteCode, 0, byteCode.length); - } else { - // Not a Gizmo generated class; load from parent class loader - return PythonBytecodeToJavaBytecodeTranslator.class.getClassLoader().loadClass(name); - } - } - }; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeType::registerBaseType); - PythonOverloadImplementor.deferDispatchesFor(PythonLikeType::registerTypeType); - - try { - FUNCTION_TYPE.__dir__.put(PythonTernaryOperator.GET.dunderMethod, - new JavaMethodReference( - FunctionBuiltinOperations.class.getMethod("bindFunctionToInstance", PythonLikeFunction.class, - PythonLikeObject.class, PythonLikeType.class), - Map.of("self", 0, "obj", 1, "objtype", 2))); - CLASS_FUNCTION_TYPE.__dir__.put(PythonTernaryOperator.GET.dunderMethod, - new JavaMethodReference( - FunctionBuiltinOperations.class.getMethod("bindFunctionToType", PythonLikeFunction.class, - PythonLikeObject.class, PythonLikeType.class), - Map.of("self", 0, "obj", 1, "objtype", 2))); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - - for (Field field : BuiltinTypes.class.getFields()) { - try { - if (!field.getType().equals(PythonLikeType.class)) { - continue; - } - - PythonLikeType pythonLikeType = (PythonLikeType) field.get(null); - Class javaClass = pythonLikeType.getJavaClass(); - Class.forName(javaClass.getName(), true, javaClass.getClassLoader()); - } catch (IllegalAccessException | ClassNotFoundException e) { - throw new IllegalStateException(e); - } - } - GlobalBuiltins.loadBuiltinConstants(); - } - - public static void load() { - } - - // Class should not be initated; only holds static constants - private BuiltinTypes() { - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java deleted file mode 100644 index ff18a875..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java +++ /dev/null @@ -1,93 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; - -public class CPythonBackedPythonLikeObject extends AbstractPythonLikeObject implements PythonLikeFunction { - public static final PythonLikeType CPYTHON_BACKED_OBJECT_TYPE = - new PythonLikeType("object", CPythonBackedPythonLikeObject.class); - - private final PythonInterpreter interpreter; - - public OpaquePythonReference $cpythonReference; - - public PythonInteger $cpythonId; - - public Map $instanceMap; - - public CPythonBackedPythonLikeObject(PythonInterpreter interpreter, - PythonLikeType __type__) { - this(interpreter, __type__, (OpaquePythonReference) null); - } - - public CPythonBackedPythonLikeObject(PythonInterpreter interpreter, - PythonLikeType __type__, Map __dir__) { - this(interpreter, __type__, __dir__, null); - } - - public CPythonBackedPythonLikeObject(PythonInterpreter interpreter, - PythonLikeType __type__, - OpaquePythonReference reference) { - super(__type__); - this.interpreter = interpreter; - this.$cpythonReference = reference; - $instanceMap = new HashMap<>(); - } - - public CPythonBackedPythonLikeObject(PythonInterpreter interpreter, - PythonLikeType __type__, - Map __dir__, - OpaquePythonReference reference) { - super(__type__, __dir__); - this.interpreter = interpreter; - this.$cpythonReference = reference; - $instanceMap = new HashMap<>(); - } - - public OpaquePythonReference $getCPythonReference() { - return $cpythonReference; - } - - public void $setCPythonReference(OpaquePythonReference pythonReference) { - interpreter.setPythonReference(this, pythonReference); - } - - public PythonInteger $getCPythonId() { - return $cpythonId; - } - - public Map $getInstanceMap() { - return $instanceMap; - } - - public void $setInstanceMap(Map $instanceMap) { - this.$instanceMap = $instanceMap; - } - - public boolean $shouldCreateNewInstance() { - return !interpreter.hasValidPythonReference(this); - } - - public void $readFieldsFromCPythonReference() { - } - - public void $writeFieldsToCPythonReference(OpaquePythonReference cloneMap) { - for (var attributeEntry : getExtraAttributeMap().entrySet()) { - CPythonBackedPythonInterpreter.setAttributeOnPythonReference($cpythonReference, cloneMap, attributeEntry.getKey(), - attributeEntry.getValue()); - } - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - return PythonNone.INSTANCE; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Coercible.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Coercible.java deleted file mode 100644 index 197505a7..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Coercible.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -public interface Coercible { - T coerce(Class targetType); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Ellipsis.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Ellipsis.java deleted file mode 100644 index 148482d2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/Ellipsis.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class Ellipsis extends AbstractPythonLikeObject implements PlanningImmutable { - public static final Ellipsis INSTANCE; - public static final PythonLikeType ELLIPSIS_TYPE = new PythonLikeType("EllipsisType", Ellipsis.class); - public static final PythonLikeType $TYPE = ELLIPSIS_TYPE; - - static { - INSTANCE = new Ellipsis(); - - GlobalBuiltins.addBuiltinConstant("Ellipsis", INSTANCE); - } - - private Ellipsis() { - super(ELLIPSIS_TYPE); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return "NotImplemented"; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/GeneratedFunctionMethodReference.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/GeneratedFunctionMethodReference.java deleted file mode 100644 index ff7e66f0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/GeneratedFunctionMethodReference.java +++ /dev/null @@ -1,62 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public class GeneratedFunctionMethodReference implements PythonLikeFunction { - - private final Object instance; - - private final Method method; - private final Map parameterNameToIndexMap; - private final PythonLikeType type; - - public GeneratedFunctionMethodReference(Object instance, Method method, Map parameterNameToIndexMap, - PythonLikeType type) { - this.instance = instance; - this.method = method; - this.parameterNameToIndexMap = parameterNameToIndexMap; - this.type = type; - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - Object[] args = unwrapPrimitiveArguments(positionalArguments, namedArguments); - try { - return (PythonLikeObject) method.invoke(instance, args); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Method (" + method + ") is not accessible.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - private Object[] unwrapPrimitiveArguments(List positionalArguments, - Map namedArguments) { - namedArguments = (namedArguments != null) ? namedArguments : Map.of(); - Object[] out = new Object[method.getParameterCount()]; - - for (int i = 0; i < positionalArguments.size(); i++) { - PythonLikeObject argument = positionalArguments.get(i); - out[i] = argument; - } - - for (PythonString key : namedArguments.keySet()) { - int index = parameterNameToIndexMap.get(key.value); - PythonLikeObject argument = namedArguments.get(key); - out[index] = argument; - } - - return out; - } - - @Override - public PythonLikeType $getType() { - return type; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/NotImplemented.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/NotImplemented.java deleted file mode 100644 index 1b4ea643..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/NotImplemented.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; - -public class NotImplemented extends AbstractPythonLikeObject { - public static final NotImplemented INSTANCE; - public static final PythonLikeType NOT_IMPLEMENTED_TYPE = new PythonLikeType("NotImplementedType", NotImplemented.class); - public static final PythonLikeType $TYPE = NOT_IMPLEMENTED_TYPE; - - static { - INSTANCE = new NotImplemented(); - - GlobalBuiltins.addBuiltinConstant("NotImplemented", INSTANCE); - } - - private NotImplemented() { - super(NOT_IMPLEMENTED_TYPE); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return "NotImplemented"; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java deleted file mode 100644 index 2a8ba2f4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java +++ /dev/null @@ -1,1965 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.io.ByteArrayOutputStream; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.BitSet; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeDecodeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.ByteCharSequence; -import ai.timefold.jpyinterpreter.util.StringFormatter; - -public class PythonByteArray extends AbstractPythonLikeObject implements PythonBytesLikeObject { - private static final PythonByteArray ASCII_SPACE = new PythonByteArray(new byte[] { ' ' }); - private static final BitSet ASCII_WHITESPACE_BITSET = asBitSet(new PythonByteArray( - new byte[] { ' ', '\t', '\n', '\r', 0x0b, '\f' })); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonByteArray::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.BYTE_ARRAY_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.isEmpty()) { - return new PythonByteArray(); - } else if (positionalArguments.size() == 1) { - PythonLikeObject arg = positionalArguments.get(0); - if (arg instanceof PythonInteger) { - return new PythonByteArray(new byte[((PythonInteger) arg).value.intValueExact()]); - } else { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(arg); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] toWrite = new byte[1]; - while (iterator.hasNext()) { - PythonLikeObject item = iterator.nextPythonItem(); - if (!(item instanceof PythonInteger)) { - throw new ValueError("bytearray argument 1 must be an int or an iterable of int"); - } - toWrite[0] = ((PythonInteger) item).asByte(); - out.writeBytes(toWrite); - } - return new PythonByteArray(out.toByteArray()); - } - } else { - throw new ValueError("bytearray takes 0 or 1 arguments, not " + positionalArguments.size()); - } - })); - - // Unary - BuiltinTypes.BYTE_ARRAY_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - PythonByteArray.class.getMethod("repr")); - BuiltinTypes.BYTE_ARRAY_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, PythonByteArray.class.getMethod("asString")); - BuiltinTypes.BYTE_ARRAY_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - PythonByteArray.class.getMethod("getIterator")); - BuiltinTypes.BYTE_ARRAY_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonByteArray.class.getMethod("getLength")); - - // Binary - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonByteArray.class.getMethod("getCharAt", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonByteArray.class.getMethod("getSubsequence", PythonSlice.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ITEM, - PythonByteArray.class.getMethod("deleteIndex", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ITEM, - PythonByteArray.class.getMethod("deleteSlice", PythonSlice.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonByteArray.class.getMethod("containsSubsequence", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonByteArray.class.getMethod("concat", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_ADD, - PythonByteArray.class.getMethod("inplaceAdd", PythonLikeObject.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonByteArray.class.getMethod("repeat", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_MULTIPLY, - PythonByteArray.class.getMethod("inplaceRepeat", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonByteArray.class.getMethod("interpolate", PythonLikeObject.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonByteArray.class.getMethod("interpolate", PythonLikeTuple.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonByteArray.class.getMethod("interpolate", PythonLikeDict.class)); - - // Ternary - BuiltinTypes.BYTE_ARRAY_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ITEM, - PythonByteArray.class.getMethod("setByte", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ITEM, - PythonByteArray.class.getMethod("setSlice", PythonSlice.class, PythonLikeObject.class)); - - // Other - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("append", PythonByteArray.class.getMethod("appendByte", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("clear", PythonByteArray.class.getMethod("clear")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("copy", PythonByteArray.class.getMethod("copy")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("extend", PythonByteArray.class.getMethod("extend", PythonLikeObject.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("insert", - PythonByteArray.class.getMethod("insert", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("pop", PythonByteArray.class.getMethod("pop")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("pop", PythonByteArray.class.getMethod("pop", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("remove", PythonByteArray.class.getMethod("remove", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("reverse", PythonByteArray.class.getMethod("reverse")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("capitalize", PythonByteArray.class.getMethod("capitalize")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("center", PythonByteArray.class.getMethod("center", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("center", - PythonByteArray.class.getMethod("center", PythonInteger.class, PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", PythonByteArray.class.getMethod("count", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", - PythonByteArray.class.getMethod("count", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", - PythonByteArray.class.getMethod("count", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", PythonByteArray.class.getMethod("count", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", - PythonByteArray.class.getMethod("count", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("count", - PythonByteArray.class.getMethod("count", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - - // TODO: decode - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", PythonByteArray.class.getMethod("endsWith", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", PythonByteArray.class.getMethod("endsWith", PythonLikeTuple.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", - PythonByteArray.class.getMethod("endsWith", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", - PythonByteArray.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", - PythonByteArray.class.getMethod("endsWith", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("endswith", - PythonByteArray.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("expandtabs", PythonByteArray.class.getMethod("expandTabs")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("expandtabs", - PythonByteArray.class.getMethod("expandTabs", PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", PythonByteArray.class.getMethod("find", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", PythonByteArray.class.getMethod("find", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", - PythonByteArray.class.getMethod("find", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", - PythonByteArray.class.getMethod("find", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", - PythonByteArray.class.getMethod("find", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("find", - PythonByteArray.class.getMethod("find", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", PythonByteArray.class.getMethod("index", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", PythonByteArray.class.getMethod("index", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", - PythonByteArray.class.getMethod("index", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", - PythonByteArray.class.getMethod("index", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", - PythonByteArray.class.getMethod("index", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("index", - PythonByteArray.class.getMethod("index", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isalnum", PythonByteArray.class.getMethod("isAlphaNumeric")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isalpha", PythonByteArray.class.getMethod("isAlpha")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isascii", PythonByteArray.class.getMethod("isAscii")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isdigit", PythonByteArray.class.getMethod("isDigit")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("islower", PythonByteArray.class.getMethod("isLower")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isspace", PythonByteArray.class.getMethod("isSpace")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("istitle", PythonByteArray.class.getMethod("isTitle")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("isupper", PythonByteArray.class.getMethod("isUpper")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("join", PythonByteArray.class.getMethod("join", PythonLikeObject.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("ljust", PythonByteArray.class.getMethod("leftJustify", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("ljust", - PythonByteArray.class.getMethod("leftJustify", PythonInteger.class, PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("lower", PythonByteArray.class.getMethod("lower")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("lstrip", PythonByteArray.class.getMethod("leftStrip")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("lstrip", PythonByteArray.class.getMethod("leftStrip", PythonNone.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("lstrip", PythonByteArray.class.getMethod("leftStrip", PythonByteArray.class)); - - // TODO: maketrans - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("partition", - PythonByteArray.class.getMethod("partition", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("removeprefix", - PythonByteArray.class.getMethod("removePrefix", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("removesuffix", - PythonByteArray.class.getMethod("removeSuffix", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("replace", - PythonByteArray.class.getMethod("replace", PythonByteArray.class, PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("replace", - PythonByteArray.class.getMethod("replace", PythonByteArray.class, PythonByteArray.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", PythonByteArray.class.getMethod("rightFind", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", PythonByteArray.class.getMethod("rightFind", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", - PythonByteArray.class.getMethod("rightFind", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", - PythonByteArray.class.getMethod("rightFind", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", - PythonByteArray.class.getMethod("rightFind", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rfind", - PythonByteArray.class.getMethod("rightFind", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", PythonByteArray.class.getMethod("rightIndex", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", PythonByteArray.class.getMethod("rightIndex", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", - PythonByteArray.class.getMethod("rightIndex", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", - PythonByteArray.class.getMethod("rightIndex", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", - PythonByteArray.class.getMethod("rightIndex", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rindex", - PythonByteArray.class.getMethod("rightIndex", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rjust", PythonByteArray.class.getMethod("rightJustify", PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rjust", - PythonByteArray.class.getMethod("rightJustify", PythonInteger.class, PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rpartition", - PythonByteArray.class.getMethod("rightPartition", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rsplit", PythonByteArray.class.getMethod("rightSplit")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rsplit", PythonByteArray.class.getMethod("rightSplit", PythonNone.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rsplit", PythonByteArray.class.getMethod("rightSplit", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rsplit", - PythonByteArray.class.getMethod("rightSplit", PythonNone.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rsplit", - PythonByteArray.class.getMethod("rightSplit", PythonByteArray.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rstrip", PythonByteArray.class.getMethod("rightStrip")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rstrip", PythonByteArray.class.getMethod("rightStrip", PythonNone.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("rstrip", PythonByteArray.class.getMethod("rightStrip", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("split", PythonByteArray.class.getMethod("split")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("split", PythonByteArray.class.getMethod("split", PythonNone.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("split", PythonByteArray.class.getMethod("split", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("split", - PythonByteArray.class.getMethod("split", PythonNone.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("split", - PythonByteArray.class.getMethod("split", PythonByteArray.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("splitlines", PythonByteArray.class.getMethod("splitLines")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("splitlines", - PythonByteArray.class.getMethod("splitLines", PythonBoolean.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonByteArray.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonLikeTuple.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonByteArray.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonByteArray.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("startswith", - PythonByteArray.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("strip", PythonByteArray.class.getMethod("strip")); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("strip", PythonByteArray.class.getMethod("strip", PythonNone.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("strip", PythonByteArray.class.getMethod("strip", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("swapcase", PythonByteArray.class.getMethod("swapCase")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("title", PythonByteArray.class.getMethod("title")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("translate", PythonByteArray.class.getMethod("translate", PythonBytes.class)); - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("translate", - PythonByteArray.class.getMethod("translate", PythonByteArray.class)); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("upper", PythonByteArray.class.getMethod("upper")); - - BuiltinTypes.BYTE_ARRAY_TYPE.addMethod("zfill", PythonByteArray.class.getMethod("zfill", PythonInteger.class)); - - return BuiltinTypes.BYTE_ARRAY_TYPE; - } - - public ByteBuffer valueBuffer; - - public PythonByteArray() { - super(BuiltinTypes.BYTE_ARRAY_TYPE); - this.valueBuffer = ByteBuffer.allocate(4096); - valueBuffer.limit(0); - } - - public PythonByteArray(byte[] data) { - super(BuiltinTypes.BYTE_ARRAY_TYPE); - this.valueBuffer = ByteBuffer.allocate(data.length); - valueBuffer.put(data); - valueBuffer.position(0); - } - - public PythonByteArray(ByteBuffer valueBuffer) { - super(BuiltinTypes.BYTE_ARRAY_TYPE); - this.valueBuffer = valueBuffer; - } - - @Override - public ByteBuffer asByteBuffer() { - return valueBuffer.duplicate().asReadOnlyBuffer(); - } - - public final ByteCharSequence asCharSequence() { - return new ByteCharSequence(valueBuffer.array(), 0, valueBuffer.limit()); - } - - public final PythonString asAsciiString() { - return PythonString.valueOf(asCharSequence().toString()); - } - - public static PythonByteArray fromIntTuple(PythonLikeTuple tuple) { - byte[] out = new byte[tuple.size()]; - IntStream.range(0, tuple.size()).forEach(index -> out[index] = ((PythonInteger) tuple.get(index)).asByte()); - return new PythonByteArray(out); - } - - public final PythonLikeTuple asIntTuple() { - return IntStream.range(0, valueBuffer.limit()) - .mapToObj(index -> PythonBytes.BYTE_TO_INT[Byte.toUnsignedInt(valueBuffer.get(index))]) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - } - - private static BitSet asBitSet(PythonByteArray bytesInBitSet) { - BitSet out = new BitSet(); - for (int i = 0; i < bytesInBitSet.valueBuffer.limit(); i++) { - out.set(bytesInBitSet.valueBuffer.get(i) & 0xFF); - } - return out; - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(valueBuffer.limit()); - } - - public PythonInteger getCharAt(PythonInteger position) { - int index = PythonSlice.asIntIndexForLength(position, valueBuffer.limit()); - - if (index >= valueBuffer.limit()) { - throw new IndexError("position " + position + " larger than bytes length " + valueBuffer.limit()); - } else if (index < 0) { - throw new IndexError("position " + position + " is less than 0"); - } - - return PythonBytes.BYTE_TO_INT[Byte.toUnsignedInt(valueBuffer.get(index))]; - } - - public PythonByteArray getSubsequence(PythonSlice slice) { - int length = valueBuffer.limit(); - int start = slice.getStartIndex(length); - int stop = slice.getStopIndex(length); - int step = slice.getStrideLength(); - - if (step == 1) { - if (stop <= start) { - return new PythonByteArray(new byte[] {}); - } else { - return new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), start, stop)); - } - } else { - byte[] out = new byte[slice.getSliceSize(length)]; - slice.iterate(length, (index, iteration) -> { - out[iteration] = valueBuffer.get(index); - }); - return new PythonByteArray(out); - } - } - - public PythonBoolean containsSubsequence(PythonByteArray subsequence) { - if (subsequence.valueBuffer.limit() == 0) { - return PythonBoolean.TRUE; - } - - if (subsequence.valueBuffer.limit() > valueBuffer.limit()) { - return PythonBoolean.FALSE; - } - - for (int i = 0; i <= valueBuffer.limit() - subsequence.valueBuffer.limit(); i++) { - if (Arrays.equals(valueBuffer.array(), i, i + subsequence.valueBuffer.limit(), - subsequence.valueBuffer.array(), 0, subsequence.valueBuffer.limit())) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonByteArray concat(PythonByteArray other) { - if (valueBuffer.limit() == 0) { - return new PythonByteArray(other.valueBuffer.duplicate()); - } else if (other.valueBuffer.limit() == 0) { - return new PythonByteArray(valueBuffer.duplicate()); - } else { - byte[] out = new byte[valueBuffer.limit() + other.valueBuffer.limit()]; - System.arraycopy(valueBuffer.array(), 0, out, 0, valueBuffer.limit()); - System.arraycopy(other.valueBuffer.array(), 0, out, valueBuffer.limit(), other.valueBuffer.limit()); - return new PythonByteArray(out); - } - } - - public PythonByteArray repeat(PythonInteger times) { - int timesAsInt = times.value.intValueExact(); - - if (timesAsInt <= 0) { - return new PythonByteArray(new byte[] {}); - } - - byte[] out = new byte[valueBuffer.limit() * timesAsInt]; - for (int i = 0; i < timesAsInt; i++) { - System.arraycopy(valueBuffer.array(), 0, out, i * valueBuffer.limit(), valueBuffer.limit()); - } - return new PythonByteArray(out); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator<>(IntStream.range(0, valueBuffer.limit()) - .mapToObj(index -> PythonBytes.BYTE_TO_INT[Byte.toUnsignedInt(valueBuffer.get(index))]) - .iterator()); - } - - public PythonInteger countByte(byte query, int start, int end) { - int count = 0; - for (int i = start; i < end; i++) { - if (valueBuffer.get(i) == query) { - count++; - } - } - return PythonInteger.valueOf(count); - } - - public PythonInteger count(PythonInteger byteAsInt) { - byte query = byteAsInt.asByte(); - return countByte(query, 0, valueBuffer.limit()); - } - - public PythonInteger count(PythonInteger byteAsInt, PythonInteger start) { - byte query = byteAsInt.asByte(); - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - - return countByte(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger count(PythonInteger byteAsInt, PythonInteger start, PythonInteger end) { - byte query = byteAsInt.asByte(); - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return countByte(query, startAsInt, endAsInt); - } - - private PythonInteger countSubsequence(byte[] query, int from, int to) { - int count = 0; - - if ((to - from) == 0 || query.length > (to - from)) { - return PythonInteger.ZERO; - } - - if (query.length == 0) { - return PythonInteger.valueOf((to - from) + 1); - } - - for (int i = from; i <= to - query.length; i++) { - if (Arrays.equals(valueBuffer.array(), i, i + query.length, - query, 0, query.length)) { - count++; - i += query.length - 1; - } - } - return PythonInteger.valueOf(count); - } - - public PythonInteger count(PythonByteArray bytes) { - return countSubsequence(bytes.valueBuffer.array(), 0, valueBuffer.limit()); - } - - public PythonInteger count(PythonByteArray bytes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - - return countSubsequence(bytes.valueBuffer.array(), startAsInt, valueBuffer.limit()); - } - - public PythonInteger count(PythonByteArray bytes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return countSubsequence(bytes.valueBuffer.array(), startAsInt, endAsInt); - } - - private static byte[] getBytes(PythonLikeObject iterable) { - if (iterable instanceof PythonBytes) { - return ((PythonBytes) iterable).value; - } else if (iterable instanceof PythonByteArray) { - return ((PythonByteArray) iterable).asByteArray(); - } else { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - while (iterator.hasNext()) { - PythonLikeObject next = iterator.nextPythonItem(); - byte[] byteWrapper = new byte[1]; - if (!(next instanceof PythonInteger)) { - throw new TypeError("'" + next.$getType().getTypeName() + "' object cannot be interpreted as an integer"); - } - byteWrapper[0] = ((PythonInteger) next).asByte(); - out.writeBytes(byteWrapper); - } - return out.toByteArray(); - } - } - - private void ensureCapacity(int minimumCapacity) { - int oldCapacity = valueBuffer.capacity(); - if (oldCapacity >= minimumCapacity) { - return; - } - - int newCapacity = Math.max(oldCapacity + (oldCapacity >> 1), minimumCapacity); - - ByteBuffer newValueBuffer = ByteBuffer.allocate(newCapacity); - System.arraycopy(valueBuffer.array(), 0, newValueBuffer.array(), 0, valueBuffer.limit()); - newValueBuffer.limit(valueBuffer.limit()); - this.valueBuffer = newValueBuffer; - } - - private void insertExtraBytesAt(int position, int extraBytes) { - System.arraycopy(valueBuffer.array(), position, - valueBuffer.array(), position + extraBytes, - valueBuffer.limit() - position); - valueBuffer.limit(valueBuffer.limit() + extraBytes); - } - - private void removeBytesStartingAt(int position, int removedBytes) { - System.arraycopy(valueBuffer.array(), position + removedBytes, - valueBuffer.array(), position, - valueBuffer.limit() - position - removedBytes); - valueBuffer.limit(valueBuffer.limit() - removedBytes); - } - - public PythonNone setByte(PythonInteger index, PythonInteger item) { - int indexAsInt = PythonSlice.asIntIndexForLength(index, valueBuffer.limit()); - if (indexAsInt < 0 || indexAsInt >= valueBuffer.limit()) { - throw new IndexError("bytearray index out of range"); - } - valueBuffer.put(indexAsInt, item.asByte()); - return PythonNone.INSTANCE; - } - - public PythonNone setSlice(PythonSlice slice, PythonLikeObject iterable) { - byte[] iterableBytes = getBytes(iterable); - if (slice.getStrideLength() == 1) { - int sizeDifference = iterableBytes.length - slice.getSliceSize(valueBuffer.limit()); - if (sizeDifference == 0) { - valueBuffer.position(slice.getStartIndex(valueBuffer.limit())); - valueBuffer.put(iterableBytes); - } else if (sizeDifference > 0) { - // inserted extra bytes - int oldLimit = valueBuffer.limit(); - ensureCapacity(valueBuffer.capacity() + sizeDifference); - insertExtraBytesAt(slice.getStopIndex(oldLimit), sizeDifference); - System.arraycopy(iterableBytes, 0, valueBuffer.array(), slice.getStartIndex(oldLimit), - iterableBytes.length); - } else { - // removed some bytes - int oldLimit = valueBuffer.limit(); - removeBytesStartingAt(slice.getStopIndex(oldLimit) + sizeDifference, -sizeDifference); - System.arraycopy(iterableBytes, 0, valueBuffer.array(), slice.getStartIndex(oldLimit), - iterableBytes.length); - } - } else { - if (iterableBytes.length == 0) { - deleteSlice(slice); - } else { - if (iterableBytes.length != slice.getSliceSize(valueBuffer.limit())) { - throw new ValueError( - "attempt to assign bytes of size " + iterableBytes.length + " to extended slice of size " + - slice.getSliceSize(valueBuffer.limit())); - } - - slice.iterate(valueBuffer.limit(), (index, step) -> { - valueBuffer.put(index, iterableBytes[step]); - }); - } - } - return PythonNone.INSTANCE; - } - - public PythonNone deleteIndex(PythonInteger index) { - int indexAsInt = PythonSlice.asIntIndexForLength(index, valueBuffer.limit()); - removeBytesStartingAt(indexAsInt, 1); - return PythonNone.INSTANCE; - } - - public PythonNone deleteSlice(PythonSlice deletedSlice) { - if (deletedSlice.getStrideLength() == 1) { - removeBytesStartingAt(deletedSlice.getStartIndex(valueBuffer.limit()), - deletedSlice.getSliceSize(valueBuffer.limit())); - } else { - if (deletedSlice.getStrideLength() > 0) { - deletedSlice.iterate(valueBuffer.limit(), (index, step) -> { - removeBytesStartingAt(index - step, 1); - }); - } else { - deletedSlice.iterate(valueBuffer.limit(), (index, step) -> { - removeBytesStartingAt(index, 1); - }); - } - } - return PythonNone.INSTANCE; - } - - public PythonNone appendByte(PythonInteger addedByte) { - byte toAdd = addedByte.asByte(); - ensureCapacity(valueBuffer.limit() + 1); - valueBuffer.limit(valueBuffer.limit() + 1); - valueBuffer.position(valueBuffer.limit() - 1); - valueBuffer.put(toAdd); - return PythonNone.INSTANCE; - } - - public PythonNone clear() { - valueBuffer.limit(0); - return PythonNone.INSTANCE; - } - - public PythonByteArray copy() { - byte[] copiedData = asByteArray(); - return new PythonByteArray(copiedData); - } - - public PythonNone extend(PythonLikeObject iterable) { - byte[] data = getBytes(iterable); - ensureCapacity(valueBuffer.limit() + data.length); - int oldLimit = valueBuffer.limit(); - valueBuffer.limit(valueBuffer.limit() + data.length); - valueBuffer.position(oldLimit); - valueBuffer.put(data); - return PythonNone.INSTANCE; - } - - public PythonByteArray inplaceAdd(PythonLikeObject iterable) { - extend(iterable); - return this; - } - - public PythonByteArray inplaceRepeat(PythonLikeObject indexable) { - return inplaceRepeat((PythonInteger) UnaryDunderBuiltin.INDEX.invoke(indexable)); - } - - public PythonByteArray inplaceRepeat(PythonInteger index) { - int indexAsInt = index.value.intValueExact(); - if (indexAsInt <= 0) { - clear(); - return this; - } else if (indexAsInt == 1) { - return this; - } else { - ensureCapacity(valueBuffer.limit() * indexAsInt); - int oldLimit = valueBuffer.limit(); - valueBuffer.limit(oldLimit * indexAsInt); - for (int i = 1; i < indexAsInt; i++) { - System.arraycopy(valueBuffer.array(), 0, valueBuffer.array(), i * oldLimit, oldLimit); - } - return this; - } - } - - public PythonNone insert(PythonInteger index, PythonInteger value) { - byte toInsert = value.asByte(); - ensureCapacity(valueBuffer.limit() + 1); - int indexAsInt = PythonSlice.asIntIndexForLength(index, valueBuffer.limit()); - - if (indexAsInt < 0) { - indexAsInt = 0; - } - - if (indexAsInt > valueBuffer.limit()) { - indexAsInt = valueBuffer.limit(); - } - - insertExtraBytesAt(indexAsInt, 1); - valueBuffer.position(indexAsInt); - valueBuffer.put(toInsert); - return PythonNone.INSTANCE; - } - - public PythonInteger pop() { - if (valueBuffer.limit() == 0) { - throw new IndexError("pop from empty bytearray"); - } - PythonInteger out = PythonBytes.BYTE_TO_INT[Byte.toUnsignedInt(valueBuffer.get(valueBuffer.limit() - 1))]; - valueBuffer.limit(valueBuffer.limit() - 1); - return out; - } - - public PythonInteger pop(PythonInteger index) { - int indexAsInt = PythonSlice.asIntIndexForLength(index, valueBuffer.limit()); - if (valueBuffer.limit() == 0) { - throw new IndexError("pop from empty bytearray"); - } - - if (indexAsInt < 0 || indexAsInt > valueBuffer.limit()) { - throw new IndexError("index out of range for bytearray"); - } - PythonInteger out = PythonBytes.BYTE_TO_INT[Byte.toUnsignedInt(valueBuffer.get(indexAsInt))]; - removeBytesStartingAt(indexAsInt, 1); - return out; - } - - public PythonNone remove(PythonInteger item) { - byte queryByte = item.asByte(); - - for (int i = 0; i < valueBuffer.limit(); i++) { - if (valueBuffer.get(i) == queryByte) { - removeBytesStartingAt(i, 1); - return PythonNone.INSTANCE; - } - } - throw new ValueError("Subsequence not found"); - } - - public PythonNone reverse() { - int limit = valueBuffer.limit(); - byte[] data = valueBuffer.array(); - for (int i = 0; i < limit >> 1; i++) { - byte temp = data[i]; - data[i] = data[data.length - i - 1]; - data[data.length - i - 1] = temp; - } - - return PythonNone.INSTANCE; - } - - public boolean hasPrefix(ByteBuffer prefixBytes, int start, int end) { - if (prefixBytes.limit() > end - start) { - return false; - } - - return Arrays.equals(valueBuffer.array(), start, start + prefixBytes.limit(), - prefixBytes.array(), 0, prefixBytes.limit()); - } - - public boolean hasSuffix(ByteBuffer suffixBytes, int start, int end) { - if (suffixBytes.limit() > end - start) { - return false; - } - - return Arrays.equals(valueBuffer.array(), end - suffixBytes.limit(), end, - suffixBytes.array(), 0, suffixBytes.limit()); - } - - public PythonByteArray removePrefix(PythonByteArray prefix) { - return hasPrefix(prefix.valueBuffer, 0, valueBuffer.limit()) - ? new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), prefix.valueBuffer.limit(), valueBuffer.limit())) - : this; - } - - public PythonByteArray removeSuffix(PythonByteArray suffix) { - return hasSuffix(suffix.valueBuffer, 0, valueBuffer.limit()) - ? new PythonByteArray( - Arrays.copyOfRange(valueBuffer.array(), 0, valueBuffer.limit() - suffix.valueBuffer.limit())) - : this; - } - - public PythonString decode() { - try { - return PythonString.valueOf(StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .decode(valueBuffer).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - - } - - public PythonString decode(PythonString charset) { - try { - return PythonString.valueOf(Charset.forName(charset.value).newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .decode(valueBuffer).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public PythonString decode(PythonString charset, PythonString errorActionString) { - CodingErrorAction errorAction; - switch (errorActionString.value) { - case "strict": - errorAction = CodingErrorAction.REPORT; - break; - case "ignore": - errorAction = CodingErrorAction.IGNORE; - break; - case "replace": - errorAction = CodingErrorAction.REPLACE; - break; - default: - throw new ValueError(errorActionString.repr() + " is not a valid value for errors. Possible values are: " + - "\"strict\", \"ignore\", \"replace\"."); - } - try { - return PythonString.valueOf(Charset.forName(charset.value).newDecoder() - .onMalformedInput(errorAction) - .decode(valueBuffer).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public PythonBoolean endsWith(PythonByteArray suffix) { - return PythonBoolean.valueOf(hasSuffix(suffix.valueBuffer, 0, valueBuffer.limit())); - } - - public PythonBoolean endsWith(PythonByteArray suffix, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return PythonBoolean.valueOf(hasSuffix(suffix.valueBuffer, startAsInt, valueBuffer.limit())); - } - - public PythonBoolean endsWith(PythonByteArray suffix, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - return PythonBoolean.valueOf(hasSuffix(suffix.valueBuffer, startAsInt, endAsInt)); - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes) { - for (PythonByteArray suffix : suffixes) { - if (hasSuffix(suffix.valueBuffer, 0, valueBuffer.limit())) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - - for (PythonByteArray suffix : suffixes) { - if (hasSuffix(suffix.valueBuffer, startAsInt, valueBuffer.limit())) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - for (PythonByteArray suffix : suffixes) { - if (hasSuffix(suffix.valueBuffer, startAsInt, endAsInt)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - private PythonInteger find(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = start; i < end; i++) { - if (valueBuffer.get(i) == queryByte) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger index(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = start; i < end; i++) { - if (valueBuffer.get(i) == queryByte) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger find(PythonInteger query) { - return find(query, 0, valueBuffer.limit()); - } - - public PythonInteger find(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return find(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger find(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return find(query, startAsInt, endAsInt); - } - - public PythonInteger index(PythonInteger query) { - return index(query, 0, valueBuffer.limit()); - } - - public PythonInteger index(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return index(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger index(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return index(query, startAsInt, endAsInt); - } - - private PythonInteger find(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - if (queryBytes.length == 0) { - return (valueBuffer.limit() > 0) ? PythonInteger.ZERO : PythonInteger.valueOf(-1); - } - - for (int i = start; i <= end - queryBytes.length; i++) { - if (Arrays.equals(valueBuffer.array(), i, i + queryBytes.length, queryBytes, 0, queryBytes.length)) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger index(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - if (queryBytes.length == 0) { - if (valueBuffer.limit() > 0) { - return PythonInteger.ZERO; - } else { - throw new ValueError("Subsequence not found"); - } - } - - for (int i = start; i <= end - queryBytes.length; i++) { - if (Arrays.equals(valueBuffer.array(), i, i + queryBytes.length, queryBytes, 0, queryBytes.length)) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger find(PythonByteArray query) { - return find(query, 0, valueBuffer.limit()); - } - - public PythonInteger find(PythonByteArray query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return find(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger find(PythonByteArray query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return find(query, startAsInt, endAsInt); - } - - public PythonInteger index(PythonByteArray query) { - return index(query, 0, valueBuffer.limit()); - } - - public PythonInteger index(PythonByteArray query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return index(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger index(PythonByteArray query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return index(query, startAsInt, endAsInt); - } - - public PythonByteArray interpolate(PythonLikeObject object) { - if (object instanceof PythonLikeTuple) { - return interpolate((PythonLikeTuple) object); - } else if (object instanceof PythonLikeDict) { - return interpolate((PythonLikeDict) object); - } else { - return interpolate(PythonLikeTuple.fromItems(object)); - } - } - - public PythonByteArray interpolate(PythonLikeTuple tuple) { - return PythonString.valueOf(StringFormatter.printfInterpolate(asCharSequence(), tuple, - StringFormatter.PrintfStringType.BYTES)).asAsciiByteArray(); - } - - public PythonByteArray interpolate(PythonLikeDict dict) { - return PythonString.valueOf(StringFormatter.printfInterpolate(asCharSequence(), dict, - StringFormatter.PrintfStringType.BYTES)).asAsciiByteArray(); - } - - public PythonByteArray join(PythonLikeObject iterable) { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - while (iterator.hasNext()) { - PythonLikeObject item = iterator.nextPythonItem(); - - if (!(item instanceof PythonBytesLikeObject)) { - throw new TypeError("type " + item.$getType() + " is not a bytes-like type"); - } - - outputStream.writeBytes(((PythonBytesLikeObject) item).asByteArray()); - if (iterator.hasNext()) { - outputStream.write(valueBuffer.array(), 0, valueBuffer.limit()); - } - } - return new PythonByteArray(outputStream.toByteArray()); - } - - private PythonLikeTuple partition(PythonBytesLikeObject sep, int start, int end) { - byte[] sepBytes = sep.asByteArray(); - - for (int i = start; i < end - sepBytes.length; i++) { - int j = 0; - for (; j < sepBytes.length; j++) { - if (valueBuffer.get(i + j) != sepBytes[j]) { - break; - } - } - - if (j == sepBytes.length) { - return PythonLikeTuple.fromItems( - new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), 0, i)), - sep, - new PythonByteArray( - Arrays.copyOfRange(valueBuffer.array(), i + sepBytes.length, valueBuffer.limit()))); - } - } - - return PythonLikeTuple.fromItems( - this, - new PythonByteArray(new byte[] {}), - new PythonByteArray(new byte[] {})); - } - - public PythonLikeTuple partition(PythonByteArray sep) { - return partition(sep, 0, valueBuffer.limit()); - } - - public PythonLikeTuple partition(PythonByteArray sep, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - - return partition(sep, startAsInt, valueBuffer.limit()); - } - - public PythonLikeTuple partition(PythonByteArray sep, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidStartIntIndexForLength(end, valueBuffer.limit()); - - return partition(sep, startAsInt, endAsInt); - } - - public PythonByteArray replace(PythonBytesLikeObject old, PythonBytesLikeObject replacement) { - byte[] oldBytes = old.asByteArray(); - byte[] replacementBytes = replacement.asByteArray(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - int lastReplacementEnd = 0; - for (int i = 0; i < valueBuffer.limit() - oldBytes.length; i++) { - if (!Arrays.equals(valueBuffer.array(), i, i + oldBytes.length, - oldBytes, 0, oldBytes.length)) { - continue; - } - - outputStream.write(valueBuffer.array(), lastReplacementEnd, i - lastReplacementEnd); - outputStream.writeBytes(replacementBytes); - - i += oldBytes.length; - lastReplacementEnd = i; - } - - outputStream.write(valueBuffer.array(), lastReplacementEnd, valueBuffer.limit() - lastReplacementEnd); - return new PythonByteArray(outputStream.toByteArray()); - } - - public PythonByteArray replace(PythonBytesLikeObject old, PythonBytesLikeObject replacement, BigInteger count) { - byte[] oldBytes = old.asByteArray(); - byte[] replacementBytes = replacement.asByteArray(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - int lastReplacementEnd = 0; - for (int i = 0; i < valueBuffer.limit() - oldBytes.length; i++) { - if (count.compareTo(BigInteger.ZERO) == 0) { - break; - } - - if (!Arrays.equals(valueBuffer.array(), i, i + oldBytes.length, - oldBytes, 0, oldBytes.length)) { - continue; - } - - outputStream.write(valueBuffer.array(), lastReplacementEnd, i - lastReplacementEnd); - outputStream.writeBytes(replacementBytes); - - i += oldBytes.length; - lastReplacementEnd = i; - count = count.subtract(BigInteger.ONE); - } - - outputStream.write(valueBuffer.array(), lastReplacementEnd, valueBuffer.limit() - lastReplacementEnd); - return new PythonByteArray(outputStream.toByteArray()); - } - - public PythonByteArray replace(PythonByteArray old, PythonByteArray replacement) { - return replace((PythonBytesLikeObject) old, replacement); - } - - public PythonByteArray replace(PythonByteArray old, PythonByteArray replacement, PythonInteger count) { - return replace(old, replacement, count.value); - } - - private PythonInteger rightFind(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = end - 1; i >= start; i--) { - if (valueBuffer.get(i) == queryByte) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger rightIndex(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = end - 1; i >= start; i--) { - if (valueBuffer.get(i) == queryByte) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger rightFind(PythonInteger query) { - return rightFind(query, 0, valueBuffer.limit()); - } - - public PythonInteger rightFind(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return rightFind(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger rightFind(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return rightFind(query, startAsInt, endAsInt); - } - - public PythonInteger rightIndex(PythonInteger query) { - return rightIndex(query, 0, valueBuffer.limit()); - } - - public PythonInteger rightIndex(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return rightIndex(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger rightIndex(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return rightIndex(query, startAsInt, endAsInt); - } - - private PythonInteger rightFind(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - for (int i = end - queryBytes.length; i >= start; i--) { - int j = 0; - for (; j < queryBytes.length; j++) { - if (valueBuffer.get(i + j) != queryBytes[j]) { - break; - } - } - - if (j == queryBytes.length) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger rightIndex(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - for (int i = end - queryBytes.length; i >= start; i--) { - int j = 0; - for (; j < queryBytes.length; j++) { - if (valueBuffer.get(i + j) != queryBytes[j]) { - break; - } - } - - if (j == queryBytes.length) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger rightFind(PythonByteArray query) { - return rightFind(query, 0, valueBuffer.limit()); - } - - public PythonInteger rightFind(PythonByteArray query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return rightFind(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger rightFind(PythonByteArray query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return rightFind(query, startAsInt, endAsInt); - } - - public PythonInteger rightIndex(PythonByteArray query) { - return rightIndex(query, 0, valueBuffer.limit()); - } - - public PythonInteger rightIndex(PythonByteArray query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return rightIndex(query, startAsInt, valueBuffer.limit()); - } - - public PythonInteger rightIndex(PythonByteArray query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return rightIndex(query, startAsInt, endAsInt); - } - - private PythonLikeTuple rightPartition(PythonBytesLikeObject sep, int start, int end) { - byte[] sepBytes = sep.asByteArray(); - - for (int i = end - sepBytes.length; i >= start; i--) { - if (!Arrays.equals(valueBuffer.array(), i, i + sepBytes.length, - sepBytes, 0, sepBytes.length)) { - continue; - } - - return PythonLikeTuple.fromItems( - new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), 0, i)), - sep, - new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), i + sepBytes.length, valueBuffer.limit()))); - } - - return PythonLikeTuple.fromItems( - new PythonByteArray(new byte[] {}), - new PythonByteArray(new byte[] {}), - this); - } - - public PythonLikeTuple rightPartition(PythonByteArray sep) { - return rightPartition(sep, 0, valueBuffer.limit()); - } - - public PythonBoolean startsWith(PythonByteArray prefix) { - return PythonBoolean.valueOf(hasPrefix(prefix.valueBuffer, 0, valueBuffer.limit())); - } - - public PythonBoolean startsWith(PythonByteArray prefix, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - return PythonBoolean.valueOf(hasPrefix(prefix.valueBuffer, startAsInt, valueBuffer.limit())); - } - - public PythonBoolean startsWith(PythonByteArray prefix, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - return PythonBoolean.valueOf(hasPrefix(prefix.valueBuffer, startAsInt, endAsInt)); - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes) { - for (PythonByteArray prefix : prefixes) { - if (hasPrefix(prefix.valueBuffer, 0, valueBuffer.limit())) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - - for (PythonByteArray prefix : prefixes) { - if (hasPrefix(prefix.valueBuffer, startAsInt, valueBuffer.limit())) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, valueBuffer.limit()); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, valueBuffer.limit()); - - for (PythonByteArray prefix : prefixes) { - if (hasPrefix(prefix.valueBuffer, startAsInt, endAsInt)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonByteArray translate(PythonBytes table) { - byte[] tableBytes = table.value; - if (tableBytes.length != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - byte[] out = new byte[valueBuffer.limit()]; - - for (int i = 0; i < valueBuffer.limit(); i++) { - out[i] = tableBytes[valueBuffer.get(i) & 0xFF]; - } - - return new PythonByteArray(out); - } - - public PythonByteArray translate(PythonByteArray table) { - if (table.valueBuffer.limit() != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - byte[] out = new byte[valueBuffer.limit()]; - - for (int i = 0; i < valueBuffer.limit(); i++) { - out[i] = table.valueBuffer.get(valueBuffer.get(i) & 0xFF); - } - - return new PythonByteArray(out); - } - - public PythonByteArray translate(PythonNone table) { - return this; - } - - public PythonByteArray translate(PythonBytes table, PythonByteArray delete) { - byte[] tableBytes = table.value; - if (tableBytes.length != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(valueBuffer.limit()); - BitSet removedSet = asBitSet(delete); - - for (int i = 0; i < valueBuffer.limit(); i++) { - byte b = valueBuffer.get(i); - if (!removedSet.get(b & 0xFF)) { - out.write(tableBytes, b & 0xFF, 1); - } - } - - return new PythonByteArray(out.toByteArray()); - } - - public PythonByteArray translate(PythonNone table, PythonByteArray delete) { - ByteArrayOutputStream out = new ByteArrayOutputStream(valueBuffer.limit()); - BitSet removedSet = asBitSet(delete); - - for (int i = 0; i < valueBuffer.limit(); i++) { - if (!removedSet.get(valueBuffer.get(i) & 0xFF)) { - out.write(valueBuffer.array(), i, 1); - } - } - - return new PythonByteArray(out.toByteArray()); - } - - public PythonByteArray translate(PythonByteArray table, PythonByteArray delete) { - if (table.valueBuffer.limit() != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(valueBuffer.limit()); - BitSet removedSet = asBitSet(delete); - - for (int i = 0; i < valueBuffer.limit(); i++) { - byte b = valueBuffer.get(i); - if (!removedSet.get(b & 0xFF)) { - out.write(table.valueBuffer.array(), b & 0xFF, 1); - } - } - - return new PythonByteArray(out.toByteArray()); - } - - public PythonByteArray center(PythonInteger fillWidth) { - return center(fillWidth, ASCII_SPACE); - } - - public PythonByteArray center(PythonInteger fillWidth, PythonByteArray fillCharacter) { - if (fillCharacter.valueBuffer.limit() != 1) { - throw new TypeError("center() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= valueBuffer.limit()) { - return this; - } - int extraWidth = widthAsInt - valueBuffer.limit(); - int rightPadding = extraWidth / 2; - // left padding get extra character if extraWidth is odd - int leftPadding = rightPadding + (extraWidth & 1); // x & 1 == x % 2 - - byte[] out = new byte[widthAsInt]; - Arrays.fill(out, 0, leftPadding, fillCharacter.valueBuffer.get(0)); - System.arraycopy(valueBuffer.array(), 0, out, leftPadding, valueBuffer.limit()); - Arrays.fill(out, leftPadding + valueBuffer.limit(), widthAsInt, fillCharacter.valueBuffer.get(0)); - - return new PythonByteArray(out); - } - - public PythonByteArray leftJustify(PythonInteger fillWidth) { - return leftJustify(fillWidth, ASCII_SPACE); - } - - public PythonByteArray leftJustify(PythonInteger fillWidth, PythonByteArray fillCharacter) { - if (fillCharacter.valueBuffer.limit() != 1) { - throw new TypeError("ljust() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= valueBuffer.limit()) { - return this; - } - - byte[] out = new byte[widthAsInt]; - System.arraycopy(valueBuffer.array(), 0, out, 0, valueBuffer.limit()); - Arrays.fill(out, valueBuffer.limit(), widthAsInt, fillCharacter.valueBuffer.get(0)); - - return new PythonByteArray(out); - } - - public PythonByteArray rightJustify(PythonInteger fillWidth) { - return rightJustify(fillWidth, ASCII_SPACE); - } - - public PythonByteArray rightJustify(PythonInteger fillWidth, PythonByteArray fillCharacter) { - if (fillCharacter.valueBuffer.limit() != 1) { - throw new TypeError("rjust() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= valueBuffer.limit()) { - return this; - } - - int extraWidth = widthAsInt - valueBuffer.limit(); - - byte[] out = new byte[widthAsInt]; - Arrays.fill(out, 0, extraWidth, fillCharacter.valueBuffer.get(0)); - System.arraycopy(valueBuffer.array(), 0, out, extraWidth, valueBuffer.limit()); - - return new PythonByteArray(out); - } - - public PythonByteArray strip() { - return strip(ASCII_SPACE); - } - - public PythonByteArray strip(PythonNone ignored) { - return strip(); - } - - public PythonByteArray strip(PythonByteArray bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int start = 0; - int end = valueBuffer.limit() - 1; - - while (start < valueBuffer.limit() && toStrip.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - - while (end >= start && toStrip.get(valueBuffer.get(end) & 0xFF)) { - end--; - } - - if (end < start) { - return new PythonByteArray(new byte[] {}); - } - - return new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), start, end + 1)); - } - - public PythonByteArray leftStrip() { - return leftStrip(ASCII_SPACE); - } - - public PythonByteArray leftStrip(PythonNone ignored) { - return leftStrip(); - } - - public PythonByteArray leftStrip(PythonByteArray bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int start = 0; - - while (start < valueBuffer.limit() && toStrip.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - - if (start == valueBuffer.limit()) { - return new PythonByteArray(new byte[] {}); - } - - return new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), start, valueBuffer.limit())); - } - - public PythonByteArray rightStrip() { - return rightStrip(ASCII_SPACE); - } - - public PythonByteArray rightStrip(PythonNone ignored) { - return rightStrip(); - } - - public PythonByteArray rightStrip(PythonByteArray bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int end = valueBuffer.limit() - 1; - - while (end >= 0 && toStrip.get(valueBuffer.get(end) & 0xFF)) { - end--; - } - - if (end < 0) { - return new PythonByteArray(new byte[] {}); - } - - return new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), 0, end + 1)); - } - - public PythonLikeList split() { - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = valueBuffer.limit(); - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(end - 1) & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1) { - while (start < end - 1 && - !ASCII_WHITESPACE_BITSET.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + 1; - start = lastEnd; - } - } - - if (lastEnd != end) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, end))); - } - - return out; - } - - public PythonLikeList split(PythonNone ignored) { - return split(); - } - - public PythonLikeList split(PythonByteArray seperator) { - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = valueBuffer.limit(); - - int lastEnd = start; - while (start < end - seperator.valueBuffer.limit()) { - while (start < end - seperator.valueBuffer.limit() && - !Arrays.equals(valueBuffer.array(), start, start + seperator.valueBuffer.limit(), - seperator.valueBuffer.array(), 0, seperator.valueBuffer.limit())) { - start++; - } - if (start != end - seperator.valueBuffer.limit()) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + seperator.valueBuffer.limit(); - start = lastEnd; - } - } - - if (Arrays.equals(valueBuffer.array(), start, start + seperator.valueBuffer.limit(), - seperator.valueBuffer.array(), 0, seperator.valueBuffer.limit())) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + seperator.valueBuffer.limit(); - } - - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, end))); - return out; - } - - public PythonLikeList split(PythonByteArray seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = valueBuffer.limit(); - - int lastEnd = start; - while (start < end - seperator.valueBuffer.limit() && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - seperator.valueBuffer.limit() && - !Arrays.equals(valueBuffer.array(), start, start + seperator.valueBuffer.limit(), - seperator.valueBuffer.array(), 0, seperator.valueBuffer.limit())) { - start++; - } - if (start != end - seperator.valueBuffer.limit()) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + seperator.valueBuffer.limit(); - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (maxSplits.compareTo(PythonInteger.ONE) >= 0 && - Arrays.equals(valueBuffer.array(), start, start + seperator.valueBuffer.limit(), - seperator.valueBuffer.array(), 0, seperator.valueBuffer.limit())) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + seperator.valueBuffer.limit(); - } - - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, end))); - return out; - } - - public PythonLikeList split(PythonNone seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = valueBuffer.limit(); - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(end - 1) & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1 && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - 1 && !ASCII_WHITESPACE_BITSET.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, start))); - lastEnd = start + 1; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (lastEnd != end) { - out.add(new PythonByteArray(Arrays.copyOfRange(valueBuffer.array(), lastEnd, end))); - } - - return out; - } - - public PythonLikeList rightSplit() { - return split(); - } - - public PythonLikeList rightSplit(PythonNone ignored) { - return rightSplit(); - } - - public PythonLikeList rightSplit(PythonByteArray seperator) { - return split(seperator); - } - - private static byte[] reverseInplace(byte[] array) { - for (int i = 0; i < array.length >> 1; i++) { - byte temp = array[i]; - array[i] = array[array.length - i - 1]; - array[array.length - i - 1] = temp; - } - return array; - } - - public PythonLikeList rightSplit(PythonByteArray seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - - byte[] reversedValue = reverseInplace(valueBuffer.array().clone()); - byte[] reversedSep = reverseInplace(seperator.valueBuffer.array().clone()); - - int start = 0; - int end = reversedValue.length; - - int lastEnd = start; - while (start < end - reversedSep.length && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - reversedSep.length && - !Arrays.equals(reversedValue, start, start + reversedSep.length, - reversedSep, 0, reversedSep.length)) { - start++; - } - if (start != end - reversedSep.length) { - out.add(new PythonByteArray(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + reversedSep.length; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (maxSplits.compareTo(PythonInteger.ONE) >= 0 && - Arrays.equals(reversedValue, start, start + reversedSep.length, - reversedSep, 0, reversedSep.length)) { - out.add(new PythonByteArray(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + seperator.valueBuffer.limit(); - } - - out.add(new PythonByteArray(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, end)))); - out.reverse(); - return out; - } - - public PythonLikeList rightSplit(PythonNone seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - - byte[] reversedValue = reverseInplace(Arrays.copyOfRange(valueBuffer.array(), 0, valueBuffer.limit())); - - int start = 0; - int end = valueBuffer.limit(); - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(end - 1) & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(valueBuffer.get(start) & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1 && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - 1 && - !ASCII_WHITESPACE_BITSET.get(reversedValue[start] & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonByteArray(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + 1; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (lastEnd != end) { - out.add(new PythonByteArray(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, end)))); - } - - out.reverse(); - return out; - } - - public PythonByteArray capitalize() { - var asString = asAsciiString(); - if (asString.value.isEmpty()) { - return asString.asAsciiByteArray(); - } - var tail = PythonString.valueOf(asString.value.substring(1)) - .withModifiedCodepoints(cp -> cp < 128 ? Character.toLowerCase(cp) : cp).value; - var head = asString.value.charAt(0); - if (head < 128) { - head = Character.toTitleCase(head); - } - return (PythonString.valueOf(head + tail)).asAsciiByteArray(); - } - - public PythonByteArray expandTabs() { - return asAsciiString().expandTabs().asAsciiByteArray(); - } - - public PythonByteArray expandTabs(PythonInteger tabSize) { - return asAsciiString().expandTabs(tabSize).asAsciiByteArray(); - } - - public PythonBoolean isAlphaNumeric() { - return asAsciiString().isAlphaNumeric(); - } - - public PythonBoolean isAlpha() { - return asAsciiString().isAlpha(); - } - - public PythonBoolean isAscii() { - for (int i = 0; i < valueBuffer.limit(); i++) { - byte b = valueBuffer.get(i); - if ((b & 0xFF) > 0x7F) { - return PythonBoolean.FALSE; - } - } - return PythonBoolean.TRUE; - } - - public PythonBoolean isDigit() { - return asAsciiString().isDigit(); - } - - public PythonBoolean isLower() { - return asAsciiString().isLower(); - } - - public PythonBoolean isSpace() { - return asAsciiString().isSpace(); - } - - public PythonBoolean isTitle() { - return asAsciiString().isTitle(); - } - - public PythonBoolean isUpper() { - return asAsciiString().isUpper(); - } - - public PythonByteArray lower() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? Character.toLowerCase(cp) : cp).asAsciiByteArray(); - } - - public PythonLikeList splitLines() { - return asAsciiString().splitLines() - .stream() - .map(PythonString::asAsciiByteArray) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList splitLines(PythonBoolean keepEnds) { - return asAsciiString().splitLines(keepEnds) - .stream() - .map(PythonString::asAsciiByteArray) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonByteArray swapCase() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? PythonString.CharacterCase.swapCase(cp) : cp).asAsciiByteArray(); - } - - public PythonByteArray title() { - return asAsciiString().title(cp -> cp < 128).asAsciiByteArray(); - } - - public PythonByteArray upper() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? Character.toUpperCase(cp) : cp).asAsciiByteArray(); - } - - public PythonByteArray zfill(PythonInteger width) { - return asAsciiString().zfill(width).asAsciiByteArray(); - } - - public PythonString asString() { - return PythonString.valueOf(toString()); - } - - public PythonString repr() { - return asString(); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder(valueBuffer.limit()); - out.append("bytearray("); - - out.append(new PythonBytes(Arrays.copyOfRange(valueBuffer.array(), 0, valueBuffer.limit())).repr().value); - - out.append(")"); - - return out.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonByteArray that = (PythonByteArray) o; - return valueBuffer.equals(that.valueBuffer); - } - - @Override - public int hashCode() { - return valueBuffer.hashCode(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java deleted file mode 100644 index 3b328dd0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java +++ /dev/null @@ -1,1795 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.io.ByteArrayOutputStream; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.BitSet; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeDecodeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.ByteCharSequence; -import ai.timefold.jpyinterpreter.util.StringFormatter; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonBytes extends AbstractPythonLikeObject implements PythonBytesLikeObject, PlanningImmutable { - - public static final PythonBytes EMPTY = new PythonBytes(new byte[0]); - - private static final PythonBytes ASCII_SPACE = new PythonBytes(new byte[] { ' ' }); - private static final BitSet ASCII_WHITESPACE_BITSET = asBitSet(new PythonBytes( - new byte[] { ' ', '\t', '\n', '\r', 0x0b, '\f' })); - - public static final PythonInteger[] BYTE_TO_INT = new PythonInteger[] { - PythonInteger.valueOf(0x00), PythonInteger.valueOf(0x01), PythonInteger.valueOf(0x02), PythonInteger.valueOf(0x03), - PythonInteger.valueOf(0x04), PythonInteger.valueOf(0x05), PythonInteger.valueOf(0x06), PythonInteger.valueOf(0x07), - PythonInteger.valueOf(0x08), PythonInteger.valueOf(0x09), PythonInteger.valueOf(0x0a), PythonInteger.valueOf(0x0b), - PythonInteger.valueOf(0x0c), PythonInteger.valueOf(0x0d), PythonInteger.valueOf(0x0e), PythonInteger.valueOf(0x0f), - - PythonInteger.valueOf(0x10), PythonInteger.valueOf(0x11), PythonInteger.valueOf(0x12), PythonInteger.valueOf(0x13), - PythonInteger.valueOf(0x14), PythonInteger.valueOf(0x15), PythonInteger.valueOf(0x16), PythonInteger.valueOf(0x17), - PythonInteger.valueOf(0x18), PythonInteger.valueOf(0x19), PythonInteger.valueOf(0x1a), PythonInteger.valueOf(0x1b), - PythonInteger.valueOf(0x1c), PythonInteger.valueOf(0x1d), PythonInteger.valueOf(0x1e), PythonInteger.valueOf(0x1f), - - PythonInteger.valueOf(0x20), PythonInteger.valueOf(0x21), PythonInteger.valueOf(0x22), PythonInteger.valueOf(0x23), - PythonInteger.valueOf(0x24), PythonInteger.valueOf(0x25), PythonInteger.valueOf(0x26), PythonInteger.valueOf(0x27), - PythonInteger.valueOf(0x28), PythonInteger.valueOf(0x29), PythonInteger.valueOf(0x2a), PythonInteger.valueOf(0x2b), - PythonInteger.valueOf(0x2c), PythonInteger.valueOf(0x2d), PythonInteger.valueOf(0x2e), PythonInteger.valueOf(0x2f), - - PythonInteger.valueOf(0x30), PythonInteger.valueOf(0x31), PythonInteger.valueOf(0x32), PythonInteger.valueOf(0x33), - PythonInteger.valueOf(0x34), PythonInteger.valueOf(0x35), PythonInteger.valueOf(0x36), PythonInteger.valueOf(0x37), - PythonInteger.valueOf(0x38), PythonInteger.valueOf(0x39), PythonInteger.valueOf(0x3a), PythonInteger.valueOf(0x3b), - PythonInteger.valueOf(0x3c), PythonInteger.valueOf(0x3d), PythonInteger.valueOf(0x3e), PythonInteger.valueOf(0x3f), - - PythonInteger.valueOf(0x40), PythonInteger.valueOf(0x41), PythonInteger.valueOf(0x42), PythonInteger.valueOf(0x43), - PythonInteger.valueOf(0x44), PythonInteger.valueOf(0x45), PythonInteger.valueOf(0x46), PythonInteger.valueOf(0x47), - PythonInteger.valueOf(0x48), PythonInteger.valueOf(0x49), PythonInteger.valueOf(0x4a), PythonInteger.valueOf(0x4b), - PythonInteger.valueOf(0x4c), PythonInteger.valueOf(0x4d), PythonInteger.valueOf(0x4e), PythonInteger.valueOf(0x4f), - - PythonInteger.valueOf(0x50), PythonInteger.valueOf(0x51), PythonInteger.valueOf(0x52), PythonInteger.valueOf(0x53), - PythonInteger.valueOf(0x54), PythonInteger.valueOf(0x55), PythonInteger.valueOf(0x56), PythonInteger.valueOf(0x57), - PythonInteger.valueOf(0x58), PythonInteger.valueOf(0x59), PythonInteger.valueOf(0x5a), PythonInteger.valueOf(0x5b), - PythonInteger.valueOf(0x5c), PythonInteger.valueOf(0x5d), PythonInteger.valueOf(0x5e), PythonInteger.valueOf(0x5f), - - PythonInteger.valueOf(0x60), PythonInteger.valueOf(0x61), PythonInteger.valueOf(0x62), PythonInteger.valueOf(0x63), - PythonInteger.valueOf(0x64), PythonInteger.valueOf(0x65), PythonInteger.valueOf(0x66), PythonInteger.valueOf(0x67), - PythonInteger.valueOf(0x68), PythonInteger.valueOf(0x69), PythonInteger.valueOf(0x6a), PythonInteger.valueOf(0x6b), - PythonInteger.valueOf(0x6c), PythonInteger.valueOf(0x6d), PythonInteger.valueOf(0x6e), PythonInteger.valueOf(0x6f), - - PythonInteger.valueOf(0x70), PythonInteger.valueOf(0x71), PythonInteger.valueOf(0x72), PythonInteger.valueOf(0x73), - PythonInteger.valueOf(0x74), PythonInteger.valueOf(0x75), PythonInteger.valueOf(0x76), PythonInteger.valueOf(0x77), - PythonInteger.valueOf(0x78), PythonInteger.valueOf(0x79), PythonInteger.valueOf(0x7a), PythonInteger.valueOf(0x7b), - PythonInteger.valueOf(0x7c), PythonInteger.valueOf(0x7d), PythonInteger.valueOf(0x7e), PythonInteger.valueOf(0x7f), - - PythonInteger.valueOf(0x80), PythonInteger.valueOf(0x81), PythonInteger.valueOf(0x82), PythonInteger.valueOf(0x83), - PythonInteger.valueOf(0x84), PythonInteger.valueOf(0x85), PythonInteger.valueOf(0x86), PythonInteger.valueOf(0x87), - PythonInteger.valueOf(0x88), PythonInteger.valueOf(0x89), PythonInteger.valueOf(0x8a), PythonInteger.valueOf(0x8b), - PythonInteger.valueOf(0x8c), PythonInteger.valueOf(0x8d), PythonInteger.valueOf(0x8e), PythonInteger.valueOf(0x8f), - - PythonInteger.valueOf(0x90), PythonInteger.valueOf(0x91), PythonInteger.valueOf(0x92), PythonInteger.valueOf(0x93), - PythonInteger.valueOf(0x94), PythonInteger.valueOf(0x95), PythonInteger.valueOf(0x96), PythonInteger.valueOf(0x97), - PythonInteger.valueOf(0x98), PythonInteger.valueOf(0x99), PythonInteger.valueOf(0x9a), PythonInteger.valueOf(0x9b), - PythonInteger.valueOf(0x9c), PythonInteger.valueOf(0x9d), PythonInteger.valueOf(0x9e), PythonInteger.valueOf(0x9f), - - PythonInteger.valueOf(0xa0), PythonInteger.valueOf(0xa1), PythonInteger.valueOf(0xa2), PythonInteger.valueOf(0xa3), - PythonInteger.valueOf(0xa4), PythonInteger.valueOf(0xa5), PythonInteger.valueOf(0xa6), PythonInteger.valueOf(0xa7), - PythonInteger.valueOf(0xa8), PythonInteger.valueOf(0xa9), PythonInteger.valueOf(0xaa), PythonInteger.valueOf(0xab), - PythonInteger.valueOf(0xac), PythonInteger.valueOf(0xad), PythonInteger.valueOf(0xae), PythonInteger.valueOf(0xaf), - - PythonInteger.valueOf(0xb0), PythonInteger.valueOf(0xb1), PythonInteger.valueOf(0xb2), PythonInteger.valueOf(0xb3), - PythonInteger.valueOf(0xb4), PythonInteger.valueOf(0xb5), PythonInteger.valueOf(0xb6), PythonInteger.valueOf(0xb7), - PythonInteger.valueOf(0xb8), PythonInteger.valueOf(0xb9), PythonInteger.valueOf(0xba), PythonInteger.valueOf(0xbb), - PythonInteger.valueOf(0xbc), PythonInteger.valueOf(0xbd), PythonInteger.valueOf(0xbe), PythonInteger.valueOf(0xbf), - - PythonInteger.valueOf(0xc0), PythonInteger.valueOf(0xc1), PythonInteger.valueOf(0xc2), PythonInteger.valueOf(0xc3), - PythonInteger.valueOf(0xc4), PythonInteger.valueOf(0xc5), PythonInteger.valueOf(0xc6), PythonInteger.valueOf(0xc7), - PythonInteger.valueOf(0xc8), PythonInteger.valueOf(0xc9), PythonInteger.valueOf(0xca), PythonInteger.valueOf(0xcb), - PythonInteger.valueOf(0xcc), PythonInteger.valueOf(0xcd), PythonInteger.valueOf(0xce), PythonInteger.valueOf(0xcf), - - PythonInteger.valueOf(0xd0), PythonInteger.valueOf(0xd1), PythonInteger.valueOf(0xd2), PythonInteger.valueOf(0xd3), - PythonInteger.valueOf(0xd4), PythonInteger.valueOf(0xd5), PythonInteger.valueOf(0xd6), PythonInteger.valueOf(0xd7), - PythonInteger.valueOf(0xd8), PythonInteger.valueOf(0xd9), PythonInteger.valueOf(0xda), PythonInteger.valueOf(0xdb), - PythonInteger.valueOf(0xdc), PythonInteger.valueOf(0xdd), PythonInteger.valueOf(0xde), PythonInteger.valueOf(0xdf), - - PythonInteger.valueOf(0xe0), PythonInteger.valueOf(0xe1), PythonInteger.valueOf(0xe2), PythonInteger.valueOf(0xe3), - PythonInteger.valueOf(0xe4), PythonInteger.valueOf(0xe5), PythonInteger.valueOf(0xe6), PythonInteger.valueOf(0xe7), - PythonInteger.valueOf(0xe8), PythonInteger.valueOf(0xe9), PythonInteger.valueOf(0xea), PythonInteger.valueOf(0xeb), - PythonInteger.valueOf(0xec), PythonInteger.valueOf(0xed), PythonInteger.valueOf(0xee), PythonInteger.valueOf(0xef), - - PythonInteger.valueOf(0xf0), PythonInteger.valueOf(0xf1), PythonInteger.valueOf(0xf2), PythonInteger.valueOf(0xf3), - PythonInteger.valueOf(0xf4), PythonInteger.valueOf(0xf5), PythonInteger.valueOf(0xf6), PythonInteger.valueOf(0xf7), - PythonInteger.valueOf(0xf8), PythonInteger.valueOf(0xf9), PythonInteger.valueOf(0xfa), PythonInteger.valueOf(0xfb), - PythonInteger.valueOf(0xfc), PythonInteger.valueOf(0xfd), PythonInteger.valueOf(0xfe), PythonInteger.valueOf(0xff), - }; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonBytes::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.BYTES_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.isEmpty()) { - return new PythonBytes(new byte[] {}); - } else if (positionalArguments.size() == 1) { - PythonLikeObject arg = positionalArguments.get(0); - if (arg instanceof PythonInteger) { - return new PythonBytes(new byte[((PythonInteger) arg).value.intValueExact()]); - } else { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(arg); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] toWrite = new byte[1]; - while (iterator.hasNext()) { - PythonLikeObject item = iterator.nextPythonItem(); - if (!(item instanceof PythonInteger)) { - throw new ValueError("bytearray argument 1 must be an int or an iterable of int"); - } - toWrite[0] = ((PythonInteger) item).asByte(); - out.writeBytes(toWrite); - } - return new PythonBytes(out.toByteArray()); - } - } else { - throw new ValueError("bytearray takes 0 or 1 arguments, not " + positionalArguments.size()); - } - })); - - // Unary - BuiltinTypes.BYTES_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, PythonBytes.class.getMethod("repr")); - BuiltinTypes.BYTES_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, PythonBytes.class.getMethod("asString")); - BuiltinTypes.BYTES_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonBytes.class.getMethod("getIterator")); - BuiltinTypes.BYTES_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonBytes.class.getMethod("getLength")); - - // Binary - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonBytes.class.getMethod("getCharAt", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonBytes.class.getMethod("getSubsequence", PythonSlice.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonBytes.class.getMethod("containsSubsequence", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonBytes.class.getMethod("concat", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonBytes.class.getMethod("repeat", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonBytes.class.getMethod("interpolate", PythonLikeObject.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonBytes.class.getMethod("interpolate", PythonLikeTuple.class)); - BuiltinTypes.BYTES_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonBytes.class.getMethod("interpolate", PythonLikeDict.class)); - - // Other - BuiltinTypes.BYTES_TYPE.addMethod("capitalize", PythonBytes.class.getMethod("capitalize")); - - BuiltinTypes.BYTES_TYPE.addMethod("center", PythonBytes.class.getMethod("center", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("center", - PythonBytes.class.getMethod("center", PythonInteger.class, PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("count", PythonBytes.class.getMethod("count", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("count", - PythonBytes.class.getMethod("count", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("count", - PythonBytes.class.getMethod("count", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("count", PythonBytes.class.getMethod("count", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("count", - PythonBytes.class.getMethod("count", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("count", - PythonBytes.class.getMethod("count", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - - // TODO: decode - - BuiltinTypes.BYTES_TYPE.addMethod("endswith", PythonBytes.class.getMethod("endsWith", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("endswith", PythonBytes.class.getMethod("endsWith", PythonLikeTuple.class)); - BuiltinTypes.BYTES_TYPE.addMethod("endswith", - PythonBytes.class.getMethod("endsWith", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("endswith", - PythonBytes.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("endswith", - PythonBytes.class.getMethod("endsWith", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("endswith", - PythonBytes.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("expandtabs", PythonBytes.class.getMethod("expandTabs")); - BuiltinTypes.BYTES_TYPE.addMethod("expandtabs", PythonBytes.class.getMethod("expandTabs", PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("find", PythonBytes.class.getMethod("find", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("find", PythonBytes.class.getMethod("find", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("find", PythonBytes.class.getMethod("find", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("find", - PythonBytes.class.getMethod("find", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("find", - PythonBytes.class.getMethod("find", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("find", - PythonBytes.class.getMethod("find", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("index", PythonBytes.class.getMethod("index", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("index", PythonBytes.class.getMethod("index", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("index", - PythonBytes.class.getMethod("index", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("index", - PythonBytes.class.getMethod("index", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("index", - PythonBytes.class.getMethod("index", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("index", - PythonBytes.class.getMethod("index", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("isalnum", PythonBytes.class.getMethod("isAlphaNumeric")); - BuiltinTypes.BYTES_TYPE.addMethod("isalpha", PythonBytes.class.getMethod("isAlpha")); - BuiltinTypes.BYTES_TYPE.addMethod("isascii", PythonBytes.class.getMethod("isAscii")); - BuiltinTypes.BYTES_TYPE.addMethod("isdigit", PythonBytes.class.getMethod("isDigit")); - BuiltinTypes.BYTES_TYPE.addMethod("islower", PythonBytes.class.getMethod("isLower")); - BuiltinTypes.BYTES_TYPE.addMethod("isspace", PythonBytes.class.getMethod("isSpace")); - BuiltinTypes.BYTES_TYPE.addMethod("istitle", PythonBytes.class.getMethod("isTitle")); - BuiltinTypes.BYTES_TYPE.addMethod("isupper", PythonBytes.class.getMethod("isUpper")); - - BuiltinTypes.BYTES_TYPE.addMethod("join", PythonBytes.class.getMethod("join", PythonLikeObject.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("ljust", PythonBytes.class.getMethod("leftJustify", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("ljust", - PythonBytes.class.getMethod("leftJustify", PythonInteger.class, PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("lower", PythonBytes.class.getMethod("lower")); - - BuiltinTypes.BYTES_TYPE.addMethod("lstrip", PythonBytes.class.getMethod("leftStrip")); - BuiltinTypes.BYTES_TYPE.addMethod("lstrip", PythonBytes.class.getMethod("leftStrip", PythonNone.class)); - BuiltinTypes.BYTES_TYPE.addMethod("lstrip", PythonBytes.class.getMethod("leftStrip", PythonBytes.class)); - - // TODO: maketrans - - BuiltinTypes.BYTES_TYPE.addMethod("partition", PythonBytes.class.getMethod("partition", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("removeprefix", PythonBytes.class.getMethod("removePrefix", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("removesuffix", PythonBytes.class.getMethod("removeSuffix", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("replace", - PythonBytes.class.getMethod("replace", PythonBytes.class, PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("replace", - PythonBytes.class.getMethod("replace", PythonBytes.class, PythonBytes.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rfind", PythonBytes.class.getMethod("rightFind", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rfind", PythonBytes.class.getMethod("rightFind", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rfind", - PythonBytes.class.getMethod("rightFind", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rfind", - PythonBytes.class.getMethod("rightFind", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rfind", - PythonBytes.class.getMethod("rightFind", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rfind", - PythonBytes.class.getMethod("rightFind", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rindex", PythonBytes.class.getMethod("rightIndex", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rindex", PythonBytes.class.getMethod("rightIndex", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rindex", - PythonBytes.class.getMethod("rightIndex", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rindex", - PythonBytes.class.getMethod("rightIndex", PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rindex", - PythonBytes.class.getMethod("rightIndex", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rindex", - PythonBytes.class.getMethod("rightIndex", PythonInteger.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rjust", PythonBytes.class.getMethod("rightJustify", PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rjust", - PythonBytes.class.getMethod("rightJustify", PythonInteger.class, PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rpartition", PythonBytes.class.getMethod("rightPartition", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rsplit", PythonBytes.class.getMethod("rightSplit")); - BuiltinTypes.BYTES_TYPE.addMethod("rsplit", PythonBytes.class.getMethod("rightSplit", PythonNone.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rsplit", PythonBytes.class.getMethod("rightSplit", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rsplit", - PythonBytes.class.getMethod("rightSplit", PythonNone.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rsplit", - PythonBytes.class.getMethod("rightSplit", PythonBytes.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("rstrip", PythonBytes.class.getMethod("rightStrip")); - BuiltinTypes.BYTES_TYPE.addMethod("rstrip", PythonBytes.class.getMethod("rightStrip", PythonNone.class)); - BuiltinTypes.BYTES_TYPE.addMethod("rstrip", PythonBytes.class.getMethod("rightStrip", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("split", PythonBytes.class.getMethod("split")); - BuiltinTypes.BYTES_TYPE.addMethod("split", PythonBytes.class.getMethod("split", PythonNone.class)); - BuiltinTypes.BYTES_TYPE.addMethod("split", PythonBytes.class.getMethod("split", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("split", PythonBytes.class.getMethod("split", PythonNone.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("split", - PythonBytes.class.getMethod("split", PythonBytes.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("splitlines", PythonBytes.class.getMethod("splitLines")); - BuiltinTypes.BYTES_TYPE.addMethod("splitlines", PythonBytes.class.getMethod("splitLines", PythonBoolean.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("startswith", PythonBytes.class.getMethod("startsWith", PythonBytes.class)); - BuiltinTypes.BYTES_TYPE.addMethod("startswith", PythonBytes.class.getMethod("startsWith", PythonLikeTuple.class)); - BuiltinTypes.BYTES_TYPE.addMethod("startswith", - PythonBytes.class.getMethod("startsWith", PythonBytes.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("startswith", - PythonBytes.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("startswith", - PythonBytes.class.getMethod("startsWith", PythonBytes.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.BYTES_TYPE.addMethod("startswith", - PythonBytes.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("strip", PythonBytes.class.getMethod("strip")); - BuiltinTypes.BYTES_TYPE.addMethod("strip", PythonBytes.class.getMethod("strip", PythonNone.class)); - BuiltinTypes.BYTES_TYPE.addMethod("strip", PythonBytes.class.getMethod("strip", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("swapcase", PythonBytes.class.getMethod("swapCase")); - - BuiltinTypes.BYTES_TYPE.addMethod("title", PythonBytes.class.getMethod("title")); - - BuiltinTypes.BYTES_TYPE.addMethod("translate", PythonBytes.class.getMethod("translate", PythonBytes.class)); - - BuiltinTypes.BYTES_TYPE.addMethod("upper", PythonBytes.class.getMethod("upper")); - - BuiltinTypes.BYTES_TYPE.addMethod("zfill", PythonBytes.class.getMethod("zfill", PythonInteger.class)); - - return BuiltinTypes.BYTES_TYPE; - } - - public final byte[] value; - - public PythonBytes(byte[] value) { - super(BuiltinTypes.BYTES_TYPE); - this.value = value; - } - - @Override - public ByteBuffer asByteBuffer() { - return ByteBuffer.wrap(value).asReadOnlyBuffer(); - } - - public final ByteCharSequence asCharSequence() { - return new ByteCharSequence(value); - } - - public final PythonString asAsciiString() { - return PythonString.valueOf(asCharSequence().toString()); - } - - public static PythonBytes fromIntTuple(PythonLikeTuple tuple) { - byte[] out = new byte[tuple.size()]; - IntStream.range(0, tuple.size()).forEach(index -> out[index] = ((PythonInteger) tuple.get(index)).asByte()); - return new PythonBytes(out); - } - - public final PythonLikeTuple asIntTuple() { - return IntStream.range(0, value.length).mapToObj(index -> BYTE_TO_INT[Byte.toUnsignedInt(value[index])]) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - } - - private static BitSet asBitSet(PythonBytes bytesInBitSet) { - BitSet out = new BitSet(); - for (byte item : bytesInBitSet.value) { - out.set(item & 0xFF); - } - return out; - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(value.length); - } - - public PythonInteger getCharAt(PythonInteger position) { - int index = PythonSlice.asIntIndexForLength(position, value.length); - - if (index >= value.length) { - throw new IndexError("position " + position + " larger than bytes length " + value.length); - } else if (index < 0) { - throw new IndexError("position " + position + " is less than 0"); - } - - return BYTE_TO_INT[Byte.toUnsignedInt(value[index])]; - } - - public PythonBytes getSubsequence(PythonSlice slice) { - int length = value.length; - int start = slice.getStartIndex(length); - int stop = slice.getStopIndex(length); - int step = slice.getStrideLength(); - - if (step == 1) { - if (stop <= start) { - return EMPTY; - } else { - return new PythonBytes(Arrays.copyOfRange(value, start, stop)); - } - } else { - byte[] out = new byte[slice.getSliceSize(length)]; - slice.iterate(length, (index, iteration) -> { - out[iteration] = value[index]; - }); - return new PythonBytes(out); - } - } - - public PythonBoolean containsSubsequence(PythonBytes subsequence) { - if (subsequence.value.length == 0) { - return PythonBoolean.TRUE; - } - - if (subsequence.value.length > value.length) { - return PythonBoolean.FALSE; - } - - for (int i = 0; i <= value.length - subsequence.value.length; i++) { - if (Arrays.equals(value, i, i + subsequence.value.length, - subsequence.value, 0, subsequence.value.length)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBytes concat(PythonBytes other) { - if (value.length == 0) { - return other; - } else if (other.value.length == 0) { - return this; - } else { - byte[] out = new byte[value.length + other.value.length]; - System.arraycopy(value, 0, out, 0, value.length); - System.arraycopy(other.value, 0, out, value.length, other.value.length); - return new PythonBytes(out); - } - } - - public PythonBytes repeat(PythonInteger times) { - int timesAsInt = times.value.intValueExact(); - - if (timesAsInt <= 0) { - return EMPTY; - } - - if (timesAsInt == 1 || value.length == 0) { - return this; - } - - byte[] out = new byte[value.length * timesAsInt]; - for (int i = 0; i < timesAsInt; i++) { - System.arraycopy(value, 0, out, i * value.length, value.length); - } - return new PythonBytes(out); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator<>(IntStream.range(0, value.length) - .mapToObj(index -> BYTE_TO_INT[Byte.toUnsignedInt(value[index])]) - .iterator()); - } - - public PythonInteger countByte(byte query, int start, int end) { - int count = 0; - for (int i = start; i < end; i++) { - if (value[i] == query) { - count++; - } - } - return PythonInteger.valueOf(count); - } - - public PythonInteger count(PythonInteger byteAsInt) { - byte query = byteAsInt.asByte(); - return countByte(query, 0, value.length); - } - - public PythonInteger count(PythonInteger byteAsInt, PythonInteger start) { - byte query = byteAsInt.asByte(); - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - - return countByte(query, startAsInt, value.length); - } - - public PythonInteger count(PythonInteger byteAsInt, PythonInteger start, PythonInteger end) { - byte query = byteAsInt.asByte(); - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return countByte(query, startAsInt, endAsInt); - } - - private PythonInteger countSubsequence(byte[] query, int from, int to) { - int count = 0; - - if ((to - from) == 0 || query.length > (to - from)) { - return PythonInteger.ZERO; - } - - if (query.length == 0) { - return PythonInteger.valueOf((to - from) + 1); - } - - for (int i = from; i <= to - query.length; i++) { - if (Arrays.equals(value, i, i + query.length, - query, 0, query.length)) { - count++; - i += query.length - 1; - } - } - return PythonInteger.valueOf(count); - } - - public PythonInteger count(PythonBytes bytes) { - return countSubsequence(bytes.value, 0, value.length); - } - - public PythonInteger count(PythonBytes bytes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - - return countSubsequence(bytes.value, startAsInt, value.length); - } - - public PythonInteger count(PythonBytes bytes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return countSubsequence(bytes.value, startAsInt, endAsInt); - } - - public boolean hasPrefix(byte[] prefixBytes, int start, int end) { - if (prefixBytes.length > end - start) { - return false; - } - - for (int i = 0; i < prefixBytes.length; i++) { - if (prefixBytes[i] != value[i + start]) { - return false; - } - } - - return true; - } - - public boolean hasSuffix(byte[] suffixBytes, int start, int end) { - if (suffixBytes.length > end - start) { - return false; - } - - for (int i = 1; i <= suffixBytes.length; i++) { - if (suffixBytes[suffixBytes.length - i] != value[end - i]) { - return false; - } - } - - return true; - } - - public PythonBytes removePrefix(PythonBytes prefix) { - byte[] prefixBytes = prefix.value; - - return hasPrefix(prefixBytes, 0, value.length) - ? new PythonBytes(Arrays.copyOfRange(value, prefixBytes.length, value.length)) - : this; - } - - public PythonBytes removeSuffix(PythonBytes suffix) { - byte[] suffixBytes = suffix.value; - - return hasSuffix(suffixBytes, 0, value.length) - ? new PythonBytes(Arrays.copyOfRange(value, 0, value.length - suffixBytes.length)) - : this; - } - - public PythonString decode() { - try { - return PythonString.valueOf(StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(value)).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - - } - - public PythonString decode(PythonString charset) { - try { - return PythonString.valueOf(Charset.forName(charset.value).newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(value)).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public PythonString decode(PythonString charset, PythonString errorActionString) { - CodingErrorAction errorAction; - switch (errorActionString.value) { - case "strict": - errorAction = CodingErrorAction.REPORT; - break; - case "ignore": - errorAction = CodingErrorAction.IGNORE; - break; - case "replace": - errorAction = CodingErrorAction.REPLACE; - break; - default: - throw new ValueError(errorActionString.repr() + " is not a valid value for errors. Possible values are: " + - "\"strict\", \"ignore\", \"replace\"."); - } - try { - return PythonString.valueOf(Charset.forName(charset.value).newDecoder() - .onMalformedInput(errorAction) - .decode(ByteBuffer.wrap(value)).toString()); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public PythonBoolean endsWith(PythonBytes suffix) { - return PythonBoolean.valueOf(hasSuffix(suffix.value, 0, value.length)); - } - - public PythonBoolean endsWith(PythonBytes suffix, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return PythonBoolean.valueOf(hasSuffix(suffix.value, startAsInt, value.length)); - } - - public PythonBoolean endsWith(PythonBytes suffix, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - return PythonBoolean.valueOf(hasSuffix(suffix.value, startAsInt, endAsInt)); - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes) { - for (PythonBytes suffix : suffixes) { - if (hasSuffix(suffix.value, 0, value.length)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - - for (PythonBytes suffix : suffixes) { - if (hasSuffix(suffix.value, startAsInt, value.length)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonLikeTuple suffixes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - for (PythonBytes suffix : suffixes) { - if (hasSuffix(suffix.value, startAsInt, endAsInt)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - private PythonInteger find(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = start; i < end; i++) { - if (value[i] == queryByte) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger index(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = start; i < end; i++) { - if (value[i] == queryByte) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger find(PythonInteger query) { - return find(query, 0, value.length); - } - - public PythonInteger find(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return find(query, startAsInt, value.length); - } - - public PythonInteger find(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return find(query, startAsInt, endAsInt); - } - - public PythonInteger index(PythonInteger query) { - return index(query, 0, value.length); - } - - public PythonInteger index(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return index(query, startAsInt, value.length); - } - - public PythonInteger index(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return index(query, startAsInt, endAsInt); - } - - private PythonInteger find(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - if (queryBytes.length == 0) { - return (value.length > 0) ? PythonInteger.ZERO : PythonInteger.valueOf(-1); - } - - for (int i = start; i <= end - queryBytes.length; i++) { - if (Arrays.equals(value, i, i + queryBytes.length, queryBytes, 0, queryBytes.length)) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger index(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - if (queryBytes.length == 0) { - if (value.length > 0) { - return PythonInteger.ZERO; - } else { - throw new ValueError("Subsequence not found"); - } - } - - for (int i = start; i <= end - queryBytes.length; i++) { - if (Arrays.equals(value, i, i + queryBytes.length, queryBytes, 0, queryBytes.length)) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger find(PythonBytes query) { - return find(query, 0, value.length); - } - - public PythonInteger find(PythonBytes query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return find(query, startAsInt, value.length); - } - - public PythonInteger find(PythonBytes query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return find(query, startAsInt, endAsInt); - } - - public PythonInteger index(PythonBytes query) { - return index(query, 0, value.length); - } - - public PythonInteger index(PythonBytes query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return index(query, startAsInt, value.length); - } - - public PythonInteger index(PythonBytes query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return index(query, startAsInt, endAsInt); - } - - public PythonBytes interpolate(PythonLikeObject object) { - if (object instanceof PythonLikeTuple) { - return interpolate((PythonLikeTuple) object); - } else if (object instanceof PythonLikeDict) { - return interpolate((PythonLikeDict) object); - } else { - return interpolate(PythonLikeTuple.fromItems(object)); - } - } - - public PythonBytes interpolate(PythonLikeTuple tuple) { - return PythonString.valueOf(StringFormatter.printfInterpolate(asCharSequence(), tuple, - StringFormatter.PrintfStringType.BYTES)).asAsciiBytes(); - } - - public PythonBytes interpolate(PythonLikeDict dict) { - return PythonString.valueOf(StringFormatter.printfInterpolate(asCharSequence(), dict, - StringFormatter.PrintfStringType.BYTES)).asAsciiBytes(); - } - - public PythonBytes join(PythonLikeObject iterable) { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - while (iterator.hasNext()) { - PythonLikeObject item = iterator.nextPythonItem(); - - if (!(item instanceof PythonBytesLikeObject)) { - throw new TypeError("type " + item.$getType() + " is not a bytes-like type"); - } - - outputStream.writeBytes(((PythonBytesLikeObject) item).asByteArray()); - if (iterator.hasNext()) { - outputStream.writeBytes(value); - } - } - return new PythonBytes(outputStream.toByteArray()); - } - - private PythonLikeTuple partition(PythonBytesLikeObject sep, int start, int end) { - byte[] sepBytes = sep.asByteArray(); - - for (int i = start; i < end - sepBytes.length; i++) { - int j = 0; - for (; j < sepBytes.length; j++) { - if (value[i + j] != sepBytes[j]) { - break; - } - } - - if (j == sepBytes.length) { - return PythonLikeTuple.fromItems( - new PythonBytes(Arrays.copyOfRange(value, 0, i)), - sep, - new PythonBytes(Arrays.copyOfRange(value, i + sepBytes.length, value.length))); - } - } - - return PythonLikeTuple.fromItems( - this, - EMPTY, - EMPTY); - } - - public PythonLikeTuple partition(PythonBytes sep) { - return partition(sep, 0, value.length); - } - - public PythonLikeTuple partition(PythonBytes sep, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - - return partition(sep, startAsInt, value.length); - } - - public PythonLikeTuple partition(PythonBytes sep, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidStartIntIndexForLength(end, value.length); - - return partition(sep, startAsInt, endAsInt); - } - - public PythonBytes replace(PythonBytesLikeObject old, PythonBytesLikeObject replacement) { - byte[] oldBytes = old.asByteArray(); - byte[] replacementBytes = replacement.asByteArray(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - int lastReplacementEnd = 0; - for (int i = 0; i < value.length - oldBytes.length; i++) { - if (!Arrays.equals(value, i, i + oldBytes.length, - oldBytes, 0, oldBytes.length)) { - continue; - } - - outputStream.write(value, lastReplacementEnd, i - lastReplacementEnd); - outputStream.writeBytes(replacementBytes); - - i += oldBytes.length; - lastReplacementEnd = i; - } - - outputStream.write(value, lastReplacementEnd, value.length - lastReplacementEnd); - return new PythonBytes(outputStream.toByteArray()); - } - - public PythonBytes replace(PythonBytesLikeObject old, PythonBytesLikeObject replacement, BigInteger count) { - byte[] oldBytes = old.asByteArray(); - byte[] replacementBytes = replacement.asByteArray(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - int lastReplacementEnd = 0; - for (int i = 0; i < value.length - oldBytes.length; i++) { - if (count.compareTo(BigInteger.ZERO) == 0) { - break; - } - - if (!Arrays.equals(value, i, i + oldBytes.length, - oldBytes, 0, oldBytes.length)) { - continue; - } - - outputStream.write(value, lastReplacementEnd, i - lastReplacementEnd); - outputStream.writeBytes(replacementBytes); - - i += oldBytes.length; - lastReplacementEnd = i; - count = count.subtract(BigInteger.ONE); - } - - outputStream.write(value, lastReplacementEnd, value.length - lastReplacementEnd); - return new PythonBytes(outputStream.toByteArray()); - } - - public PythonBytes replace(PythonBytes old, PythonBytes replacement) { - return replace((PythonBytesLikeObject) old, replacement); - } - - public PythonBytes replace(PythonBytes old, PythonBytes replacement, PythonInteger count) { - return replace(old, replacement, count.value); - } - - private PythonInteger rightFind(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = end - 1; i >= start; i--) { - if (value[i] == queryByte) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger rightIndex(PythonInteger query, int start, int end) { - byte queryByte = query.asByte(); - - for (int i = end - 1; i >= start; i--) { - if (value[i] == queryByte) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger rightFind(PythonInteger query) { - return rightFind(query, 0, value.length); - } - - public PythonInteger rightFind(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return rightFind(query, startAsInt, value.length); - } - - public PythonInteger rightFind(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return rightFind(query, startAsInt, endAsInt); - } - - public PythonInteger rightIndex(PythonInteger query) { - return rightIndex(query, 0, value.length); - } - - public PythonInteger rightIndex(PythonInteger query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return rightIndex(query, startAsInt, value.length); - } - - public PythonInteger rightIndex(PythonInteger query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return rightIndex(query, startAsInt, endAsInt); - } - - private PythonInteger rightFind(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - for (int i = end - queryBytes.length; i >= start; i--) { - int j = 0; - for (; j < queryBytes.length; j++) { - if (value[i + j] != queryBytes[j]) { - break; - } - } - - if (j == queryBytes.length) { - return PythonInteger.valueOf(i); - } - } - - return PythonInteger.valueOf(-1); - } - - private PythonInteger rightIndex(PythonBytesLikeObject query, int start, int end) { - byte[] queryBytes = query.asByteArray(); - - for (int i = end - queryBytes.length; i >= start; i--) { - int j = 0; - for (; j < queryBytes.length; j++) { - if (value[i + j] != queryBytes[j]) { - break; - } - } - - if (j == queryBytes.length) { - return PythonInteger.valueOf(i); - } - } - - throw new ValueError("Subsequence not found"); - } - - public PythonInteger rightFind(PythonBytes query) { - return rightFind(query, 0, value.length); - } - - public PythonInteger rightFind(PythonBytes query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return rightFind(query, startAsInt, value.length); - } - - public PythonInteger rightFind(PythonBytes query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return rightFind(query, startAsInt, endAsInt); - } - - public PythonInteger rightIndex(PythonBytes query) { - return rightIndex(query, 0, value.length); - } - - public PythonInteger rightIndex(PythonBytes query, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return rightIndex(query, startAsInt, value.length); - } - - public PythonInteger rightIndex(PythonBytes query, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return rightIndex(query, startAsInt, endAsInt); - } - - private PythonLikeTuple rightPartition(PythonBytesLikeObject sep, int start, int end) { - byte[] sepBytes = sep.asByteArray(); - - for (int i = end - sepBytes.length; i >= start; i--) { - if (!Arrays.equals(value, i, i + sepBytes.length, - sepBytes, 0, sepBytes.length)) { - continue; - } - - return PythonLikeTuple.fromItems( - new PythonBytes(Arrays.copyOfRange(value, 0, i)), - sep, - new PythonBytes(Arrays.copyOfRange(value, i + sepBytes.length, value.length))); - } - - return PythonLikeTuple.fromItems( - EMPTY, - EMPTY, - this); - } - - public PythonLikeTuple rightPartition(PythonBytes sep) { - return rightPartition(sep, 0, value.length); - } - - public PythonBoolean startsWith(PythonBytes prefix) { - return PythonBoolean.valueOf(hasPrefix(prefix.value, 0, value.length)); - } - - public PythonBoolean startsWith(PythonBytes prefix, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - return PythonBoolean.valueOf(hasPrefix(prefix.value, startAsInt, value.length)); - } - - public PythonBoolean startsWith(PythonBytes prefix, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - return PythonBoolean.valueOf(hasPrefix(prefix.value, startAsInt, endAsInt)); - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes) { - for (PythonBytes prefix : prefixes) { - if (hasPrefix(prefix.value, 0, value.length)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes, PythonInteger start) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - - for (PythonBytes prefix : prefixes) { - if (hasPrefix(prefix.value, startAsInt, value.length)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonLikeTuple prefixes, PythonInteger start, PythonInteger end) { - int startAsInt = PythonSlice.asValidStartIntIndexForLength(start, value.length); - int endAsInt = PythonSlice.asValidEndIntIndexForLength(end, value.length); - - for (PythonBytes prefix : prefixes) { - if (hasPrefix(prefix.value, startAsInt, endAsInt)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBytes translate(PythonBytes table) { - byte[] tableBytes = table.value; - if (tableBytes.length != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - byte[] out = new byte[value.length]; - - for (int i = 0; i < value.length; i++) { - out[i] = tableBytes[value[i] & 0xFF]; - } - - return new PythonBytes(out); - } - - public PythonBytes translate(PythonNone table) { - return this; - } - - public PythonBytes translate(PythonBytes table, PythonBytes delete) { - byte[] tableBytes = table.value; - if (tableBytes.length != 256) { - throw new ValueError("translate table must be a bytes object of length 256"); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(value.length); - BitSet removedSet = asBitSet(delete); - - for (byte b : value) { - if (!removedSet.get(b & 0xFF)) { - out.write(tableBytes, b & 0xFF, 1); - } - } - - return new PythonBytes(out.toByteArray()); - } - - public PythonBytes translate(PythonNone table, PythonBytes delete) { - ByteArrayOutputStream out = new ByteArrayOutputStream(value.length); - BitSet removedSet = asBitSet(delete); - - for (int i = 0; i < value.length; i++) { - if (!removedSet.get(value[i] & 0xFF)) { - out.write(value, i, 1); - } - } - - return new PythonBytes(out.toByteArray()); - } - - public PythonBytes center(PythonInteger fillWidth) { - return center(fillWidth, ASCII_SPACE); - } - - public PythonBytes center(PythonInteger fillWidth, PythonBytes fillCharacter) { - if (fillCharacter.value.length != 1) { - throw new TypeError("center() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= value.length) { - return this; - } - int extraWidth = widthAsInt - value.length; - int rightPadding = extraWidth / 2; - // left padding get extra character if extraWidth is odd - int leftPadding = rightPadding + (extraWidth & 1); // x & 1 == x % 2 - - byte[] out = new byte[widthAsInt]; - Arrays.fill(out, 0, leftPadding, fillCharacter.value[0]); - System.arraycopy(value, 0, out, leftPadding, value.length); - Arrays.fill(out, leftPadding + value.length, widthAsInt, fillCharacter.value[0]); - - return new PythonBytes(out); - } - - public PythonBytes leftJustify(PythonInteger fillWidth) { - return leftJustify(fillWidth, ASCII_SPACE); - } - - public PythonBytes leftJustify(PythonInteger fillWidth, PythonBytes fillCharacter) { - if (fillCharacter.value.length != 1) { - throw new TypeError("ljust() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= value.length) { - return this; - } - - byte[] out = new byte[widthAsInt]; - System.arraycopy(value, 0, out, 0, value.length); - Arrays.fill(out, value.length, widthAsInt, fillCharacter.value[0]); - - return new PythonBytes(out); - } - - public PythonBytes rightJustify(PythonInteger fillWidth) { - return rightJustify(fillWidth, ASCII_SPACE); - } - - public PythonBytes rightJustify(PythonInteger fillWidth, PythonBytes fillCharacter) { - if (fillCharacter.value.length != 1) { - throw new TypeError("rjust() argument 2 must be a byte string of length 1"); - } - - int widthAsInt = fillWidth.value.intValueExact(); - if (widthAsInt <= value.length) { - return this; - } - - int extraWidth = widthAsInt - value.length; - - byte[] out = new byte[widthAsInt]; - Arrays.fill(out, 0, extraWidth, fillCharacter.value[0]); - System.arraycopy(value, 0, out, extraWidth, value.length); - - return new PythonBytes(out); - } - - public PythonBytes strip() { - return strip(ASCII_SPACE); - } - - public PythonBytes strip(PythonNone ignored) { - return strip(); - } - - public PythonBytes strip(PythonBytes bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int start = 0; - int end = value.length - 1; - - while (start < value.length && toStrip.get(value[start] & 0xFF)) { - start++; - } - - while (end >= start && toStrip.get(value[end] & 0xFF)) { - end--; - } - - if (end < start) { - return new PythonBytes(new byte[] {}); - } - - return new PythonBytes(Arrays.copyOfRange(value, start, end + 1)); - } - - public PythonBytes leftStrip() { - return leftStrip(ASCII_SPACE); - } - - public PythonBytes leftStrip(PythonNone ignored) { - return leftStrip(); - } - - public PythonBytes leftStrip(PythonBytes bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int start = 0; - - while (start < value.length && toStrip.get(value[start] & 0xFF)) { - start++; - } - - if (start == value.length) { - return new PythonBytes(new byte[] {}); - } - - return new PythonBytes(Arrays.copyOfRange(value, start, value.length)); - } - - public PythonBytes rightStrip() { - return rightStrip(ASCII_SPACE); - } - - public PythonBytes rightStrip(PythonNone ignored) { - return rightStrip(); - } - - public PythonBytes rightStrip(PythonBytes bytesToStrip) { - BitSet toStrip = asBitSet(bytesToStrip); - - int end = value.length - 1; - - while (end >= 0 && toStrip.get(value[end] & 0xFF)) { - end--; - } - - if (end < 0) { - return new PythonBytes(new byte[] {}); - } - - return new PythonBytes(Arrays.copyOfRange(value, 0, end + 1)); - } - - public PythonLikeList split() { - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = value.length; - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(value[end - 1] & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(value[start] & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1) { - while (start < end - 1 && - !ASCII_WHITESPACE_BITSET.get(value[start] & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + 1; - start = lastEnd; - } - } - - if (lastEnd != end) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, end))); - } - - return out; - } - - public PythonLikeList split(PythonNone ignored) { - return split(); - } - - public PythonLikeList split(PythonBytes seperator) { - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = value.length; - - int lastEnd = start; - while (start < end - seperator.value.length) { - while (start < end - seperator.value.length && - !Arrays.equals(value, start, start + seperator.value.length, - seperator.value, 0, seperator.value.length)) { - start++; - } - if (start != end - seperator.value.length) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + seperator.value.length; - start = lastEnd; - } - } - - if (Arrays.equals(value, start, start + seperator.value.length, - seperator.value, 0, seperator.value.length)) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + seperator.value.length; - } - - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, end))); - return out; - } - - public PythonLikeList split(PythonBytes seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = value.length; - - int lastEnd = start; - while (start < end - seperator.value.length && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - seperator.value.length && - !Arrays.equals(value, start, start + seperator.value.length, - seperator.value, 0, seperator.value.length)) { - start++; - } - if (start != end - seperator.value.length) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + seperator.value.length; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (maxSplits.compareTo(PythonInteger.ONE) >= 0 && - Arrays.equals(value, start, start + seperator.value.length, - seperator.value, 0, seperator.value.length)) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + seperator.value.length; - } - - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, end))); - return out; - } - - public PythonLikeList split(PythonNone seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - int start = 0; - int end = value.length; - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(value[end - 1] & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(value[start] & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1 && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - 1 && !ASCII_WHITESPACE_BITSET.get(value[start] & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, start))); - lastEnd = start + 1; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (lastEnd != end) { - out.add(new PythonBytes(Arrays.copyOfRange(value, lastEnd, end))); - } - - return out; - } - - public PythonLikeList rightSplit() { - return split(); - } - - public PythonLikeList rightSplit(PythonNone ignored) { - return rightSplit(); - } - - public PythonLikeList rightSplit(PythonBytes seperator) { - return split(seperator); - } - - private static byte[] reverseInplace(byte[] array) { - for (int i = 0; i < array.length >> 1; i++) { - byte temp = array[i]; - array[i] = array[array.length - i - 1]; - array[array.length - i - 1] = temp; - } - return array; - } - - public PythonLikeList rightSplit(PythonBytes seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - - byte[] reversedValue = reverseInplace(value.clone()); - byte[] reversedSep = reverseInplace(seperator.value.clone()); - - int start = 0; - int end = reversedValue.length; - - int lastEnd = start; - while (start < end - reversedSep.length && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - reversedSep.length && - !Arrays.equals(reversedValue, start, start + reversedSep.length, - reversedSep, 0, reversedSep.length)) { - start++; - } - if (start != end - reversedSep.length) { - out.add(new PythonBytes(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + reversedSep.length; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (maxSplits.compareTo(PythonInteger.ONE) >= 0 && - Arrays.equals(reversedValue, start, start + reversedSep.length, - reversedSep, 0, reversedSep.length)) { - out.add(new PythonBytes(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + seperator.value.length; - } - - out.add(new PythonBytes(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, end)))); - out.reverse(); - return out; - } - - public PythonLikeList rightSplit(PythonNone seperator, PythonInteger maxSplits) { - if (maxSplits.equals(new PythonInteger(-1))) { - return split(seperator); - } - - PythonLikeList out = new PythonLikeList<>(); - - byte[] reversedValue = reverseInplace(value.clone()); - - int start = 0; - int end = value.length; - - while (end > 0 && ASCII_WHITESPACE_BITSET.get(value[end - 1] & 0xFF)) { - end--; - } - - while (start < end && ASCII_WHITESPACE_BITSET.get(value[start] & 0xFF)) { - start++; - } - - if (start == end) { - return out; - } - - int lastEnd = start; - while (start < end - 1 && maxSplits.compareTo(PythonInteger.ONE) >= 0) { - while (start < end - 1 && - !ASCII_WHITESPACE_BITSET.get(reversedValue[start] & 0xFF)) { - start++; - } - if (start != end - 1) { - out.add(new PythonBytes(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, start)))); - lastEnd = start + 1; - start = lastEnd; - maxSplits = maxSplits.subtract(PythonInteger.ONE); - } - } - - if (lastEnd != end) { - out.add(new PythonBytes(reverseInplace(Arrays.copyOfRange(reversedValue, lastEnd, end)))); - } - - out.reverse(); - return out; - } - - public PythonBytes capitalize() { - var asString = asAsciiString(); - if (asString.value.isEmpty()) { - return this; - } - var tail = PythonString.valueOf(asString.value.substring(1)) - .withModifiedCodepoints(cp -> cp < 128 ? Character.toLowerCase(cp) : cp).value; - var head = asString.value.charAt(0); - if (head < 128) { - head = Character.toTitleCase(head); - } - return (PythonString.valueOf(head + tail)).asAsciiBytes(); - } - - public PythonBytes expandTabs() { - return asAsciiString().expandTabs().asAsciiBytes(); - } - - public PythonBytes expandTabs(PythonInteger tabSize) { - return asAsciiString().expandTabs(tabSize).asAsciiBytes(); - } - - public PythonBoolean isAlphaNumeric() { - return asAsciiString().isAlphaNumeric(); - } - - public PythonBoolean isAlpha() { - return asAsciiString().isAlpha(); - } - - public PythonBoolean isAscii() { - for (byte b : value) { - if ((b & 0xFF) > 0x7F) { - return PythonBoolean.FALSE; - } - } - return PythonBoolean.TRUE; - } - - public PythonBoolean isDigit() { - return asAsciiString().isDigit(); - } - - public PythonBoolean isLower() { - return asAsciiString().isLower(); - } - - public PythonBoolean isSpace() { - return asAsciiString().isSpace(); - } - - public PythonBoolean isTitle() { - return asAsciiString().isTitle(); - } - - public PythonBoolean isUpper() { - return asAsciiString().isUpper(); - } - - public PythonBytes lower() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? Character.toLowerCase(cp) : cp).asAsciiBytes(); - } - - public PythonLikeList splitLines() { - return asAsciiString().splitLines() - .stream() - .map(PythonString::asAsciiBytes) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList splitLines(PythonBoolean keepEnds) { - return asAsciiString().splitLines(keepEnds) - .stream() - .map(PythonString::asAsciiBytes) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonBytes swapCase() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? PythonString.CharacterCase.swapCase(cp) : cp).asAsciiBytes(); - } - - public PythonBytes title() { - return asAsciiString().title(cp -> cp < 128).asAsciiBytes(); - } - - public PythonBytes upper() { - return asAsciiString().withModifiedCodepoints( - cp -> cp < 128 ? Character.toUpperCase(cp) : cp).asAsciiBytes(); - } - - public PythonBytes zfill(PythonInteger width) { - return asAsciiString().zfill(width).asAsciiBytes(); - } - - public PythonString asString() { - return PythonString.valueOf(toString()); - } - - public PythonString repr() { - return asString(); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - boolean hasSingleQuotes = false; - boolean hasDoubleQuotes = false; - - for (byte b : value) { - switch (b) { - case '\'': - hasSingleQuotes = true; - break; - - case '\"': - hasDoubleQuotes = true; - break; - - // Default: do nothing - } - } - - StringBuilder out = new StringBuilder(value.length); - out.append("b"); - - if (!hasSingleQuotes || hasDoubleQuotes) { - out.append('\''); - } else { - out.append('\"'); - } - - boolean escapeSingleQuotes = hasSingleQuotes && hasDoubleQuotes; - for (byte b : value) { - switch (b) { - case '\'': - if (escapeSingleQuotes) { - out.append("\\'"); - } else { - out.append('\''); - } - break; - - case '\\': - out.append("\\\\"); - break; - case '\t': - out.append("\\t"); - break; - case '\n': - out.append("\\n"); - break; - case '\r': - out.append("\\r"); - break; - default: - out.append((char) (b & 0xFF)); - break; - } - } - - if (!hasSingleQuotes || hasDoubleQuotes) { - out.append('\''); - } else { - out.append('\"'); - } - - return out.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonBytes that = (PythonBytes) o; - return Arrays.equals(value, that.value); - } - - @Override - public int hashCode() { - return Arrays.hashCode(value); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytesLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytesLikeObject.java deleted file mode 100644 index 40517e7a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytesLikeObject.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.nio.ByteBuffer; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public interface PythonBytesLikeObject extends PythonLikeObject { - ByteBuffer asByteBuffer(); - - default byte[] asByteArray() { - ByteBuffer byteBuffer = asByteBuffer(); - byte[] out = new byte[byteBuffer.limit()]; - byteBuffer.get(out); - return out; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCell.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCell.java deleted file mode 100644 index 9aca492b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCell.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.concurrent.atomic.AtomicReference; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -/** - * Holds a reference to a PythonLikeObject. Cannot use {@link AtomicReference}, - * since cells are stored in a tuple via BUILD_TUPLE and thus need to be a {@link PythonLikeObject}. - */ -public class PythonCell extends AbstractPythonLikeObject { - public static final PythonLikeType CELL_TYPE = new PythonLikeType("cell", PythonCell.class), - $TYPE = CELL_TYPE; - - /** - * The value the cell stores. - */ - public PythonLikeObject cellValue; - - public PythonCell() { - super(CELL_TYPE); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCode.java deleted file mode 100644 index 6a32f677..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonCode.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -/** - * Holds a reference to a PythonLikeFunction's Class. Cannot use {@link Class}, - * since code can be accessed like a {@code PythonLikeObject} - */ -public class PythonCode extends AbstractPythonLikeObject { - public static final PythonLikeType CODE_TYPE = new PythonLikeType("code", PythonCode.class), - $TYPE = CODE_TYPE; - - /** - * The class of the function that implement the code - */ - public final Class functionClass; - - public PythonCode(final Class functionClass) { - super(CODE_TYPE); - this.functionClass = functionClass; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonGenerator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonGenerator.java deleted file mode 100644 index 7b69cad8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonGenerator.java +++ /dev/null @@ -1,72 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.Iterator; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.errors.GeneratorExit; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -public abstract class PythonGenerator extends AbstractPythonLikeObject implements Iterator { - public static PythonLikeType $TYPE = BuiltinTypes.GENERATOR_TYPE; - static { - PythonOverloadImplementor.deferDispatchesFor(PythonGenerator::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.GENERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - new PythonFunctionSignature( - new MethodDescriptor(PythonGenerator.class.getMethod("asPythonIterator")), - BuiltinTypes.GENERATOR_TYPE)); - BuiltinTypes.GENERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.NEXT, - new PythonFunctionSignature( - new MethodDescriptor(PythonGenerator.class.getMethod("next")), - BuiltinTypes.BASE_TYPE)); - BuiltinTypes.GENERATOR_TYPE.addMethod("send", - new PythonFunctionSignature( - new MethodDescriptor(PythonGenerator.class.getMethod("send", PythonLikeObject.class)), - BuiltinTypes.BASE_TYPE, BuiltinTypes.BASE_TYPE)); - BuiltinTypes.GENERATOR_TYPE.addMethod("throw", - new PythonFunctionSignature( - new MethodDescriptor(PythonGenerator.class.getMethod("throwValue", Throwable.class)), - BuiltinTypes.BASE_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE)); - BuiltinTypes.GENERATOR_TYPE.addMethod("close", - new PythonFunctionSignature( - new MethodDescriptor(PythonGenerator.class.getMethod("close")), - BuiltinTypes.BASE_TYPE)); - - return BuiltinTypes.GENERATOR_TYPE; - } - - public PythonLikeObject sentValue; - public Throwable thrownValue; - - public PythonGenerator() { - super(BuiltinTypes.GENERATOR_TYPE); - sentValue = PythonNone.INSTANCE; - thrownValue = null; - } - - public PythonNone close() { - this.thrownValue = new GeneratorExit(); - next(); - return PythonNone.INSTANCE; - } - - public PythonLikeObject send(PythonLikeObject sentValue) { - this.sentValue = sentValue; - return next(); - } - - public PythonLikeObject throwValue(Throwable thrownValue) { - this.thrownValue = thrownValue; - return next(); - } - - public PythonGenerator asPythonIterator() { - return this; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonJavaTypeMapping.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonJavaTypeMapping.java deleted file mode 100644 index 39ff0ad7..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonJavaTypeMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -public interface PythonJavaTypeMapping { - PythonLikeType getPythonType(); - - Class getJavaType(); - - PythonType_ toPythonObject(JavaType_ javaObject); - - JavaType_ toJavaObject(PythonType_ pythonObject); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonKnownFunctionType.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonKnownFunctionType.java deleted file mode 100644 index 0c53a2c3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonKnownFunctionType.java +++ /dev/null @@ -1,81 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; - -public class PythonKnownFunctionType extends PythonLikeType { - final List overloadFunctionSignatureList; - - public PythonKnownFunctionType(String methodName, List overloadFunctionSignatureList) { - super("function-" + methodName, PythonKnownFunctionType.class, List.of(BuiltinTypes.FUNCTION_TYPE)); - this.overloadFunctionSignatureList = overloadFunctionSignatureList; - } - - public List getOverloadFunctionSignatureList() { - return overloadFunctionSignatureList; - } - - public boolean isStaticMethod() { - return overloadFunctionSignatureList.get(0).getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.STATIC; - } - - public boolean isClassMethod() { - return overloadFunctionSignatureList.get(0).getMethodDescriptor().getMethodType() == MethodDescriptor.MethodType.CLASS; - } - - @Override - public PythonLikeType $getType() { - return BuiltinTypes.BASE_TYPE; - } - - @Override - public PythonLikeType $getGenericType() { - return BuiltinTypes.BASE_TYPE; - } - - public Optional getDefaultFunctionSignature() { - return overloadFunctionSignatureList.stream().findAny(); - } - - public Optional getFunctionForParameters(PythonLikeType... parameters) { - List matchingOverloads = overloadFunctionSignatureList.stream() - .filter(signature -> signature.matchesParameters(parameters)) - .collect(Collectors.toList()); - - if (matchingOverloads.isEmpty()) { - return Optional.empty(); - } - - PythonFunctionSignature best = matchingOverloads.get(0); - for (PythonFunctionSignature signature : matchingOverloads) { - if (signature.moreSpecificThan(best)) { - best = signature; - } - } - return Optional.of(best); - } - - public Optional getFunctionForParameters(int positionalItemCount, - List keywordNames, - List callStackTypeList) { - List matchingOverloads = overloadFunctionSignatureList.stream() - .filter(signature -> signature.matchesParameters(positionalItemCount, keywordNames, callStackTypeList)) - .collect(Collectors.toList()); - - if (matchingOverloads.isEmpty()) { - return Optional.empty(); - } - - PythonFunctionSignature best = matchingOverloads.get(0); - for (PythonFunctionSignature signature : matchingOverloads) { - if (signature.moreSpecificThan(best)) { - best = signature; - } - } - return Optional.of(best); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeComparable.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeComparable.java deleted file mode 100644 index 8dc287ed..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeComparable.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; - -public interface PythonLikeComparable extends Comparable { - static void setup(PythonLikeType type) { - try { - type.addBinaryMethod(PythonBinaryOperator.LESS_THAN, new PythonFunctionSignature( - new MethodDescriptor(PythonLikeComparable.class.getMethod("lessThan", Object.class)), - BuiltinTypes.BOOLEAN_TYPE, type)); - type.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, new PythonFunctionSignature( - new MethodDescriptor(PythonLikeComparable.class.getMethod("greaterThan", Object.class)), - BuiltinTypes.BOOLEAN_TYPE, type)); - type.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, new PythonFunctionSignature( - new MethodDescriptor(PythonLikeComparable.class.getMethod("lessThanOrEqual", Object.class)), - BuiltinTypes.BOOLEAN_TYPE, type)); - type.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, new PythonFunctionSignature( - new MethodDescriptor(PythonLikeComparable.class.getMethod("greaterThanOrEqual", Object.class)), - BuiltinTypes.BOOLEAN_TYPE, type)); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - } - - default PythonBoolean lessThan(T other) { - return PythonBoolean.valueOf(compareTo(other) < 0); - } - - default PythonBoolean greaterThan(T other) { - return PythonBoolean.valueOf(compareTo(other) > 0); - } - - default PythonBoolean lessThanOrEqual(T other) { - return PythonBoolean.valueOf(compareTo(other) <= 0); - } - - default PythonBoolean greaterThanOrEqual(T other) { - return PythonBoolean.valueOf(compareTo(other) >= 0); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeFunction.java deleted file mode 100644 index 9e23ba1f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeFunction.java +++ /dev/null @@ -1,51 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public interface PythonLikeFunction extends PythonLikeObject { - static PythonLikeType getStaticFunctionType() { - return BuiltinTypes.STATIC_FUNCTION_TYPE; - } - - static PythonLikeType getFunctionType() { - return BuiltinTypes.FUNCTION_TYPE; - } - - static PythonLikeType getClassFunctionType() { - return BuiltinTypes.CLASS_FUNCTION_TYPE; - } - - /** - * Calls the function with positional arguments and named arguments. - * - * @param positionalArguments Positional arguments - * @param namedArguments Named arguments - * @param callerInstance The first argument passed to the caller, if any; null otherwise (used for super) - * @return The function result - */ - PythonLikeObject $call(List positionalArguments, Map namedArguments, - PythonLikeObject callerInstance); - - @Override - default PythonLikeObject $getAttributeOrNull(String attributeName) { - return null; - } - - @Override - default void $setAttribute(String attributeName, PythonLikeObject value) { - throw new UnsupportedOperationException(); - } - - @Override - default void $deleteAttribute(String attributeName) { - throw new UnsupportedOperationException(); - } - - @Override - default PythonLikeType $getType() { - return BuiltinTypes.FUNCTION_TYPE; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeGenericType.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeGenericType.java deleted file mode 100644 index 89880f3b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeGenericType.java +++ /dev/null @@ -1,71 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.Optional; - -import ai.timefold.jpyinterpreter.FieldDescriptor; -import ai.timefold.jpyinterpreter.PythonClassTranslator; - -public class PythonLikeGenericType extends PythonLikeType { - final PythonLikeType origin; - - public PythonLikeGenericType(PythonLikeType origin) { - super(BuiltinTypes.TYPE_TYPE.getTypeName(), PythonLikeType.class); - this.origin = origin; - } - - public PythonLikeType getOrigin() { - return origin; - } - - @Override - public Optional getInstanceFieldDescriptor(String fieldName) { - Optional maybeMethodType = getMethodType(fieldName); - if (maybeMethodType.isEmpty()) { - return BuiltinTypes.TYPE_TYPE.getInstanceFieldDescriptor(fieldName); - } else { - PythonKnownFunctionType knownFunctionType = maybeMethodType.get(); - FieldDescriptor out = new FieldDescriptor(fieldName, - PythonClassTranslator.getJavaMethodName(fieldName), - origin.getJavaTypeInternalName(), - knownFunctionType.getJavaTypeDescriptor(), - knownFunctionType, false, false); - return Optional.of(out); - } - } - - @Override - public Optional getMethodType(String methodName) { - Optional originKnownFunctionType = origin.getMethodType(methodName); - if (originKnownFunctionType.isEmpty()) { - return originKnownFunctionType; - } - - PythonKnownFunctionType knownFunctionType = originKnownFunctionType.get(); - if (knownFunctionType.isStaticMethod() || knownFunctionType.isClassMethod()) { - return originKnownFunctionType; - } else { - return Optional.empty(); - } - } - - @Override - public Optional getMethodKind(String methodName) { - Optional originMethodKind = origin.getMethodKind(methodName); - if (originMethodKind.isEmpty()) { - return originMethodKind; - } - - switch (originMethodKind.get()) { - case STATIC_METHOD: - case CLASS_METHOD: - return originMethodKind; - default: - return Optional.empty(); - } - } - - @Override - public String toString() { - return ""; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java deleted file mode 100644 index 5e13521a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java +++ /dev/null @@ -1,627 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import ai.timefold.jpyinterpreter.FieldDescriptor; -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.TernaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.wrappers.JavaObjectWrapper; - -import org.objectweb.asm.Type; - -public class PythonLikeType implements PythonLikeObject, - PythonLikeFunction { - public final Map __dir__; - - private final String TYPE_NAME; - - private final String JAVA_TYPE_INTERNAL_NAME; - private final List PARENT_TYPES; - public final List MRO; - private Class javaObjectWrapperType = null; - - private final Map functionNameToKnownFunctionType; - private Optional constructorKnownFunctionType; - - private final Map instanceFieldToFieldDescriptorMap; - - private PythonLikeFunction constructor; - - public PythonLikeType(String typeName, Class javaClass) { - this(typeName, javaClass, List.of(BuiltinTypes.BASE_TYPE)); - } - - public PythonLikeType(String typeName, Class javaClass, - Class javaObjectWrapperType) { - this(typeName, javaClass, List.of(BuiltinTypes.BASE_TYPE)); - this.javaObjectWrapperType = javaObjectWrapperType; - } - - public PythonLikeType(String typeName, Class javaClass, List parents) { - TYPE_NAME = typeName; - JAVA_TYPE_INTERNAL_NAME = Type.getInternalName(javaClass); - PARENT_TYPES = parents; - constructor = (positional, keywords, callerInstance) -> { - throw new UnsupportedOperationException("Cannot create instance of type (" + TYPE_NAME + ")."); - }; - __dir__ = new HashMap<>(); - functionNameToKnownFunctionType = new HashMap<>(); - constructorKnownFunctionType = Optional.empty(); - instanceFieldToFieldDescriptorMap = new HashMap<>(); - MRO = determineMRO(); - } - - public PythonLikeType(String typeName, String javaTypeInternalName, List parents) { - TYPE_NAME = typeName; - JAVA_TYPE_INTERNAL_NAME = javaTypeInternalName; - PARENT_TYPES = parents; - constructor = (positional, keywords, callerInstance) -> { - throw new UnsupportedOperationException("Cannot create instance of type (" + TYPE_NAME + ")."); - }; - __dir__ = new HashMap<>(); - functionNameToKnownFunctionType = new HashMap<>(); - constructorKnownFunctionType = Optional.empty(); - instanceFieldToFieldDescriptorMap = new HashMap<>(); - MRO = determineMRO(); - } - - public PythonLikeType(String typeName, Class javaClass, Consumer initializer) { - this(typeName, javaClass, List.of(BuiltinTypes.BASE_TYPE)); - initializer.accept(this); - } - - private PythonLikeType(String typeName, String internalName) { - TYPE_NAME = typeName; - JAVA_TYPE_INTERNAL_NAME = internalName; - PARENT_TYPES = new ArrayList<>(); - constructor = (positional, keywords, callerInstance) -> { - throw new UnsupportedOperationException("Cannot create instance of type (" + TYPE_NAME + ")."); - }; - __dir__ = new HashMap<>(); - functionNameToKnownFunctionType = new HashMap<>(); - constructorKnownFunctionType = Optional.empty(); - instanceFieldToFieldDescriptorMap = new HashMap<>(); - MRO = new ArrayList<>(); - } - - public static PythonLikeType getTypeForNewClass(String typeName, String internalName) { - var out = new PythonLikeType(typeName, internalName); - out.__dir__.put("__class__", out); - return out; - } - - public void initializeNewType(List superClassTypes) { - PARENT_TYPES.addAll(superClassTypes); - MRO.addAll(determineMRO()); - } - - private List determineMRO() { - List out = new ArrayList<>(); - out.add(this); - out.addAll(mergeMRO()); - return out; - } - - private List mergeMRO() { - List out = new ArrayList<>(); - List> parentMROLists = new ArrayList<>(); - for (PythonLikeType parent : PARENT_TYPES) { - parentMROLists.add(new ArrayList<>(parent.MRO)); - } - parentMROLists.add(new ArrayList<>(PARENT_TYPES)); // to preserve local precedent order, add list of parents last - - while (!parentMROLists.stream().allMatch(List::isEmpty)) { - boolean candidateFound = false; - for (List parentMRO : parentMROLists) { - if (!parentMRO.isEmpty()) { - PythonLikeType candidate = parentMRO.get(0); - if (parentMROLists.stream().allMatch(mro -> mro.indexOf(candidate) < 1)) { - out.add(candidate); - parentMROLists.forEach(mro -> { - if (!mro.isEmpty() && mro.get(0) == candidate) { - mro.remove(0); - } - }); - candidateFound = true; - break; - } - } - } - if (!candidateFound) { - throw new TypeError("Cannot calculate MRO; Cycle found"); - } - } - return out; - } - - public boolean isInstance(PythonLikeObject object) { - PythonLikeType objectType = object.$getType(); - return objectType.isSubclassOf(this); - } - - public static PythonLikeType registerBaseType() { - try { - BuiltinTypes.BASE_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ATTRIBUTE, - PythonLikeObject.class.getMethod("$method$__getattribute__", PythonString.class)); - BuiltinTypes.BASE_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ATTRIBUTE, - PythonLikeObject.class.getMethod("$method$__setattr__", PythonString.class, PythonLikeObject.class)); - BuiltinTypes.BASE_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ATTRIBUTE, - PythonLikeObject.class.getMethod("$method$__delattr__", PythonString.class)); - BuiltinTypes.BASE_TYPE.addBinaryMethod(PythonBinaryOperator.EQUAL, - PythonLikeObject.class.getMethod("$method$__eq__", PythonLikeObject.class)); - BuiltinTypes.BASE_TYPE.addBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonLikeObject.class.getMethod("$method$__ne__", PythonLikeObject.class)); - BuiltinTypes.BASE_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - PythonLikeObject.class.getMethod("$method$__str__")); - BuiltinTypes.BASE_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - PythonLikeObject.class.getMethod("$method$__repr__")); - BuiltinTypes.BASE_TYPE.addUnaryMethod(PythonUnaryOperator.HASH, - PythonLikeObject.class.getMethod("$method$__hash__")); - BuiltinTypes.BASE_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonLikeObject.class.getMethod("$method$__format__", PythonLikeObject.class)); - BuiltinTypes.BASE_TYPE - .setConstructor((vargs, kwargs, callerInstance) -> new AbstractPythonLikeObject(BuiltinTypes.BASE_TYPE) { - }); - - PythonOverloadImplementor.createDispatchesFor(BuiltinTypes.BASE_TYPE); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - return BuiltinTypes.BASE_TYPE; - } - - public Class getJavaObjectWrapperType() { - if (javaObjectWrapperType == null) { - throw new IllegalStateException("Can only call this method on %s types." - .formatted(JavaObjectWrapper.class.getSimpleName())); - } - return javaObjectWrapperType; - } - - public static PythonLikeType registerTypeType() { - BuiltinTypes.TYPE_TYPE.setConstructor((positional, keywords, callerInstance) -> { - if (positional.size() == 1) { - return positional.get(0).$getType(); - } else if (positional.size() == 3) { - var name = (PythonString) positional.get(0); - var baseClasses = (PythonLikeTuple) positional.get(1); - var dict = (PythonLikeDict) positional.get(2); - - PythonLikeType out; - if (baseClasses.isEmpty()) { - out = new PythonLikeType(name.value, PythonLikeObject.class); - } else { - out = new PythonLikeType(name.value, PythonLikeObject.class, (List) baseClasses); - } - - for (Map.Entry entry : dict.entrySet()) { - PythonString attributeName = (PythonString) entry.getKey(); - - out.$setAttribute(attributeName.value, entry.getValue()); - } - - return out; - } else { - throw new ValueError("type takes 1 or 3 positional arguments, got " + positional.size()); - } - }); - - return BuiltinTypes.TYPE_TYPE; - } - - @Override - public PythonLikeObject $method$__getattribute__(PythonString pythonName) { - String name = pythonName.value; - PythonLikeObject typeResult = this.$getAttributeOrNull(name); - if (typeResult != null) { - PythonLikeObject maybeDescriptor = typeResult.$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - if (maybeDescriptor == null) { - maybeDescriptor = typeResult.$getType().$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - } - - if (maybeDescriptor != null) { - if (!(maybeDescriptor instanceof PythonLikeFunction)) { - throw new UnsupportedOperationException("'" + maybeDescriptor.$getType() + "' is not callable"); - } - return TernaryDunderBuiltin.GET_DESCRIPTOR.invoke(typeResult, PythonNone.INSTANCE, this); - } - return typeResult; - } - - throw new AttributeError("object '" + this + "' does not have attribute '" + name + "'"); - } - - public void addMethod(String methodName, Method method) { - addMethod(methodName, PythonFunctionSignature.forMethod(method)); - } - - public void addUnaryMethod(PythonUnaryOperator operator, Method method) { - addMethod(operator.getDunderMethod(), PythonFunctionSignature.forMethod(method)); - } - - public void addBinaryMethod(PythonBinaryOperator operator, Method method) { - addMethod(operator.getDunderMethod(), PythonFunctionSignature.forMethod(method)); - if (operator.hasRightDunderMethod() && !operator.isComparisonMethod()) { - addMethod(operator.getRightDunderMethod(), PythonFunctionSignature.forMethod(method)); - } - } - - public void addLeftBinaryMethod(PythonBinaryOperator operator, Method method) { - addMethod(operator.getDunderMethod(), PythonFunctionSignature.forMethod(method)); - } - - public void addRightBinaryMethod(PythonBinaryOperator operator, Method method) { - addMethod(operator.getRightDunderMethod(), PythonFunctionSignature.forMethod(method)); - } - - public void addTernaryMethod(PythonTernaryOperator operator, Method method) { - addMethod(operator.getDunderMethod(), PythonFunctionSignature.forMethod(method)); - } - - public void addUnaryMethod(PythonUnaryOperator operator, PythonFunctionSignature method) { - addMethod(operator.getDunderMethod(), method); - } - - public void addBinaryMethod(PythonBinaryOperator operator, PythonFunctionSignature method) { - addMethod(operator.getDunderMethod(), method); - if (operator.hasRightDunderMethod() && !operator.isComparisonMethod()) { - addMethod(operator.getRightDunderMethod(), method); - } - } - - public void addLeftBinaryMethod(PythonBinaryOperator operator, PythonFunctionSignature method) { - addMethod(operator.getDunderMethod(), method); - } - - public void addRightBinaryMethod(PythonBinaryOperator operator, PythonFunctionSignature method) { - addMethod(operator.getRightDunderMethod(), method); - } - - public void addTernaryMethod(PythonTernaryOperator operator, PythonFunctionSignature method) { - addMethod(operator.getDunderMethod(), method); - } - - public void clearMethod(String methodName) { - PythonKnownFunctionType knownFunctionType = functionNameToKnownFunctionType.computeIfAbsent(methodName, - key -> new PythonKnownFunctionType(methodName, new ArrayList<>())); - knownFunctionType.getOverloadFunctionSignatureList().clear(); - } - - public void addMethod(String methodName, PythonFunctionSignature method) { - PythonKnownFunctionType knownFunctionType = functionNameToKnownFunctionType.computeIfAbsent(methodName, - key -> new PythonKnownFunctionType(methodName, new ArrayList<>())); - knownFunctionType.getOverloadFunctionSignatureList().add(method); - } - - public Set getKnownMethodsDefinedByClass() { - return functionNameToKnownFunctionType.keySet(); - } - - public Set getKnownMethods() { - Set out = new HashSet<>(); - getAssignableTypesStream().forEach(type -> out.addAll(type.getKnownMethodsDefinedByClass())); - return out; - } - - public void setConstructor(PythonLikeFunction constructor) { - this.constructor = constructor; - } - - public void addConstructor(PythonFunctionSignature constructor) { - if (constructorKnownFunctionType.isEmpty()) { - constructorKnownFunctionType = Optional.of(new PythonKnownFunctionType("", new ArrayList<>())); - } - constructorKnownFunctionType.get().getOverloadFunctionSignatureList().add(constructor); - } - - public Optional getMethodType(String methodName) { - PythonKnownFunctionType out = new PythonKnownFunctionType(methodName, new ArrayList<>()); - getAssignableTypesStream().forEach(type -> { - PythonKnownFunctionType knownFunctionType = type.functionNameToKnownFunctionType.get(methodName); - if (knownFunctionType != null) { - out.getOverloadFunctionSignatureList().addAll(knownFunctionType.getOverloadFunctionSignatureList()); - } - }); - - if (out.getOverloadFunctionSignatureList().isEmpty()) { - return Optional.empty(); - } - - return Optional.of(out); - } - - public Optional getMethodKind(String methodName) { - PythonLikeObject maybeMethod = this.$getAttributeOrNull(methodName); - if (maybeMethod != null) { - PythonLikeType methodKind = maybeMethod.$getType(); - if (methodKind == BuiltinTypes.FUNCTION_TYPE) { - return Optional.of(PythonClassTranslator.PythonMethodKind.VIRTUAL_METHOD); - } - if (methodKind == BuiltinTypes.STATIC_FUNCTION_TYPE) { - return Optional.of(PythonClassTranslator.PythonMethodKind.STATIC_METHOD); - } - if (methodKind == BuiltinTypes.CLASS_FUNCTION_TYPE) { - return Optional.of(PythonClassTranslator.PythonMethodKind.CLASS_METHOD); - } - return Optional.empty(); - } - - Optional maybeKnownFunctionType = getMethodType(methodName); - if (maybeKnownFunctionType.isPresent()) { - PythonKnownFunctionType knownFunctionType = maybeKnownFunctionType.get(); - switch (knownFunctionType.getOverloadFunctionSignatureList().get(0).getMethodDescriptor().getMethodType()) { - case VIRTUAL: - case INTERFACE: - case CONSTRUCTOR: - case STATIC_AS_VIRTUAL: - return Optional.of(PythonClassTranslator.PythonMethodKind.VIRTUAL_METHOD); - case STATIC: - return Optional.of(PythonClassTranslator.PythonMethodKind.STATIC_METHOD); - case CLASS: - return Optional.of(PythonClassTranslator.PythonMethodKind.CLASS_METHOD); - default: - throw new IllegalStateException("Unhandled case " + knownFunctionType.getOverloadFunctionSignatureList() - .get(0).getMethodDescriptor().getMethodType()); - } - } - - return Optional.empty(); - } - - public Optional getConstructorType() { - return constructorKnownFunctionType; - } - - public Optional getInstanceFieldDescriptor(String fieldName) { - return getAssignableTypesStream().map(PythonLikeType::getInstanceFieldToFieldDescriptorMap) - .filter(map -> map.containsKey(fieldName)) - .map(map -> map.get(fieldName)) - .findAny(); - } - - public void addInstanceField(FieldDescriptor fieldDescriptor) { - Optional maybeExistingField = getInstanceFieldDescriptor(fieldDescriptor.pythonFieldName()); - if (maybeExistingField.isPresent()) { - PythonLikeType existingFieldType = maybeExistingField.get().fieldPythonLikeType(); - if (!fieldDescriptor.fieldPythonLikeType().isSubclassOf(existingFieldType)) { - throw new IllegalStateException("Field (" + fieldDescriptor.pythonFieldName() + ") already exist with type (" - + - existingFieldType + ") which is not assignable from (" + fieldDescriptor.fieldPythonLikeType() - + ")."); - } - } else { - instanceFieldToFieldDescriptorMap.put(fieldDescriptor.pythonFieldName(), fieldDescriptor); - } - } - - private Map getInstanceFieldToFieldDescriptorMap() { - return instanceFieldToFieldDescriptorMap; - } - - public PythonLikeType unifyWith(PythonLikeType other) { - Optional maybeCommonType = other.getAssignableTypesStream().filter(otherType -> { - if (otherType.isSubclassOf(this)) { - return true; - } - return this.isSubclassOf(otherType); - }).findAny(); - - if (maybeCommonType.isPresent() && maybeCommonType.get() != BuiltinTypes.BASE_TYPE) { - PythonLikeType commonType = maybeCommonType.get(); - if (commonType.isSubclassOf(this)) { - return this; - } else { - return commonType; - } - } - - for (PythonLikeType parent : getParentList()) { - PythonLikeType parentUnification = parent.unifyWith(other); - if (parentUnification != BuiltinTypes.BASE_TYPE) { - return parentUnification; - } - } - return BuiltinTypes.BASE_TYPE; - } - - public boolean isSubclassOf(PythonLikeType type) { - return isSubclassOf(type, new HashSet<>()); - } - - private Stream getAssignableTypesStream() { - return Stream.concat( - Stream.of(this), - getParentList().stream() - .flatMap(PythonLikeType::getAssignableTypesStream)) - .distinct(); - } - - private boolean isSubclassOf(PythonLikeType type, Set visited) { - if (visited.contains(this)) { - return false; - } - - if (this == type) { - return true; - } - - visited.add(this); - for (PythonLikeType parent : PARENT_TYPES) { - if (parent.isSubclassOf(type, visited)) { - return true; - } - } - return false; - } - - public int getDepth() { - if (PARENT_TYPES.size() == 0) { - return 0; - } else { - return 1 + PARENT_TYPES.stream().map(PythonLikeType::getDepth).max(Comparator.naturalOrder()).get(); - } - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - return constructor.$call(positionalArguments, namedArguments, null); - } - - public PythonLikeObject loadMethod(String methodName) { - PythonLikeObject out = this.$getAttributeOrNull(methodName); - if (out == null) { - return null; - } - - if (out.$getType() == BuiltinTypes.FUNCTION_TYPE) { - return out; - } - - return null; - //if (out.$getType() == PythonLikeFunction.getClassFunctionType()) { - // return FunctionBuiltinOperations.bindFunctionToType((PythonLikeFunction) out, null, this); - //} else { - // return null; - //} - } - - public PythonLikeType getDefiningTypeOrNull(String attributeName) { - if (__dir__.containsKey(attributeName) && - (this == BuiltinTypes.BASE_TYPE - || (__dir__.get(attributeName).getClass() != BuiltinTypes.BASE_TYPE.__dir__.get(attributeName) - .getClass()))) { - return this; - } - - for (PythonLikeType parent : PARENT_TYPES) { - PythonLikeType out = parent.getDefiningTypeOrNull(attributeName); - if (out != null) { - return out; - } - } - return null; - } - - public PythonLikeObject $getAttributeOrNull(String attributeName) { - PythonLikeObject out = __dir__.get(attributeName); - if (out == null) { - for (PythonLikeType type : PARENT_TYPES) { - out = type.$getAttributeOrNull(attributeName); - if (out != null) { - return out; - } - } - return null; - } else { - return out; - } - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - __dir__.put(attributeName, value); - } - - @Override - public void $deleteAttribute(String attributeName) { - // TODO: Descriptors: https://docs.python.org/3/howto/descriptor.html - __dir__.remove(attributeName); - } - - @Override - public PythonLikeType $getType() { - return BuiltinTypes.TYPE_TYPE; - } - - @Override - public PythonLikeType $getGenericType() { - return new PythonLikeGenericType(this); - } - - public String getTypeName() { - return TYPE_NAME; - } - - public String getJavaTypeInternalName() { - return JAVA_TYPE_INTERNAL_NAME; - } - - public String getJavaTypeDescriptor() { - return "L" + JAVA_TYPE_INTERNAL_NAME + ";"; - } - - /** - * Return the Java class corresponding to this type, if it exists. Throws {@link ClassNotFoundException} otherwise. - */ - public Class getJavaClass() throws ClassNotFoundException { - return Class.forName(JAVA_TYPE_INTERNAL_NAME.replace('/', '.'), true, - BuiltinTypes.asmClassLoader); - } - - /** - * Return the Java class corresponding to this type, if it exists. Returns {@code defaultValue} otherwise. - * - * @param defaultValue the value to return - */ - public Class getJavaClassOrDefault(Class defaultValue) { - try { - return getJavaClass(); - } catch (ClassNotFoundException e) { - return defaultValue; - } - } - - public List getParentList() { - return PARENT_TYPES; - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return ""; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !PythonLikeType.class.isAssignableFrom(o.getClass())) { - return false; - } - PythonLikeType that = (PythonLikeType) o; - return JAVA_TYPE_INTERNAL_NAME.equals(that.JAVA_TYPE_INTERNAL_NAME); - } - - @Override - public int hashCode() { - return Objects.hash(JAVA_TYPE_INTERNAL_NAME); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonModule.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonModule.java deleted file mode 100644 index 55fdd8ff..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonModule.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.Map; - -import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference; - -public class PythonModule extends AbstractPythonLikeObject { - public static PythonLikeType MODULE_TYPE = new PythonLikeType("module", PythonModule.class); - public static PythonLikeType $TYPE = MODULE_TYPE; - - private OpaquePythonReference pythonReference; - private Map referenceMap; - - public PythonModule(Map referenceMap) { - super(MODULE_TYPE); - this.referenceMap = referenceMap; - } - - public void addItem(String itemName, PythonLikeObject itemValue) { - $setAttribute(itemName, itemValue); - } - - public OpaquePythonReference getPythonReference() { - return pythonReference; - } - - public void setPythonReference(OpaquePythonReference pythonReference) { - this.pythonReference = pythonReference; - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - PythonLikeObject result = super.$getAttributeOrNull(attributeName); - if (result == null) { - PythonLikeObject actual = CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference(pythonReference, - attributeName, referenceMap); - $setAttribute(attributeName, actual); - return actual; - } - return result; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonNone.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonNone.java deleted file mode 100644 index 48c7103a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonNone.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonNone extends AbstractPythonLikeObject implements PlanningImmutable { - public static final PythonNone INSTANCE; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonNone::registerMethods); - INSTANCE = new PythonNone(); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.NONE_TYPE.addUnaryMethod(PythonUnaryOperator.AS_BOOLEAN, PythonNone.class.getMethod("asBool")); - BuiltinTypes.NONE_TYPE.addBinaryMethod(PythonBinaryOperator.EQUAL, - PythonNone.class.getMethod("equalsObject", PythonLikeObject.class)); - BuiltinTypes.NONE_TYPE.addBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonNone.class.getMethod("notEqualsObject", PythonLikeObject.class)); - GlobalBuiltins.addBuiltinConstant("None", INSTANCE); - return BuiltinTypes.NONE_TYPE; - } - - private PythonNone() { - super(BuiltinTypes.NONE_TYPE); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return "None"; - } - - public PythonBoolean asBool() { - return PythonBoolean.FALSE; - } - - public PythonBoolean equalsObject(PythonLikeObject other) { - return PythonBoolean.valueOf(this == other); - } - - public PythonBoolean notEqualsObject(PythonLikeObject other) { - return PythonBoolean.valueOf(this != other); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java deleted file mode 100644 index a8beaa37..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java +++ /dev/null @@ -1,390 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.lang.reflect.Array; -import java.math.BigInteger; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonRange extends AbstractPythonLikeObject implements List { - public static PythonLikeType $TYPE = BuiltinTypes.RANGE_TYPE; - - public final PythonInteger start; - public final PythonInteger stop; - public final PythonInteger step; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonRange::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.RANGE_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - PythonLikeObject start; - PythonLikeObject stop; - PythonLikeObject step; - - namedArguments = (namedArguments != null) ? namedArguments : Map.of(); - - if (positionalArguments.size() == 3) { - start = positionalArguments.get(0); - stop = positionalArguments.get(1); - step = positionalArguments.get(2); - } else if (positionalArguments.size() == 2) { - start = positionalArguments.get(0); - stop = positionalArguments.get(1); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonInteger.valueOf(1)); - } else if (positionalArguments.size() == 1 && namedArguments.containsKey(PythonString.valueOf("stop"))) { - start = positionalArguments.get(0); - stop = namedArguments.get(PythonString.valueOf("stop")); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonInteger.valueOf(1)); - } else if (positionalArguments.size() == 1) { - stop = positionalArguments.get(0); - start = PythonInteger.valueOf(0); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonInteger.valueOf(1)); - } else if (positionalArguments.isEmpty()) { - start = namedArguments.getOrDefault(PythonString.valueOf("start"), PythonInteger.valueOf(0)); - stop = namedArguments.get(PythonString.valueOf("stop")); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonInteger.valueOf(1)); - } else { - throw new ValueError("range expects 1 to 3 arguments, got " + positionalArguments.size()); - } - - PythonInteger intStart, intStop, intStep; - - if (start instanceof PythonInteger) { - intStart = (PythonInteger) start; - } else { - intStart = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(start); - } - - if (stop instanceof PythonInteger) { - intStop = (PythonInteger) stop; - } else { - intStop = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(stop); - } - - if (step instanceof PythonInteger) { - intStep = (PythonInteger) step; - } else { - intStep = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(step); - } - - return new PythonRange(intStart, intStop, intStep); - })); - - // Unary methods - BuiltinTypes.RANGE_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonRange.class.getMethod("getLength")); - BuiltinTypes.RANGE_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonRange.class.getMethod("getPythonIterator")); - - // Binary methods - BuiltinTypes.RANGE_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonRange.class.getMethod("getItem", PythonInteger.class)); - BuiltinTypes.RANGE_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonRange.class.getMethod("isObjectInRange", PythonLikeObject.class)); - - return BuiltinTypes.RANGE_TYPE; - } - - public PythonRange(PythonInteger start, PythonInteger stop, PythonInteger step) { - super(BuiltinTypes.RANGE_TYPE); - this.start = start; - this.stop = stop; - this.step = step; - - $setAttribute("start", start); - $setAttribute("stop", stop); - $setAttribute("step", step); - } - - @Override - public int size() { - // Need to use ceil division - BigInteger[] divideAndRemainder = stop.value.subtract(start.value).divideAndRemainder(step.value); - if (divideAndRemainder[1].equals(BigInteger.ZERO)) { - return divideAndRemainder[0].intValueExact(); - } else { - return divideAndRemainder[0].intValueExact() + 1; - } - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(size()); - } - - @Override - public boolean isEmpty() { - return size() == 0; - } - - @Override - public boolean contains(Object o) { - if (!(o instanceof PythonInteger)) { - return false; - } - PythonInteger query = (PythonInteger) o; - - if (step.value.compareTo(BigInteger.ZERO) < 0) { - if (query.value.compareTo(stop.value) < 0) { - return false; - } - } else { - if (query.value.compareTo(stop.value) > 0) { - return false; - } - } - - BigInteger relativeToStart = query.value.subtract(start.value); - BigInteger[] divisionAndRemainder = relativeToStart.divideAndRemainder(step.value); - - if (!divisionAndRemainder[1].equals(BigInteger.ZERO)) { - return false; // cannot be represented as start + step * i - } - return divisionAndRemainder[0].compareTo(BigInteger.ZERO) >= 0; // return true iff i is not negative - } - - public PythonBoolean isObjectInRange(PythonLikeObject query) { - return PythonBoolean.valueOf(contains(query)); - } - - @Override - public Iterator iterator() { - return new RangeIterator(start, stop, step, start, 0); - } - - public DelegatePythonIterator getPythonIterator() { - return new DelegatePythonIterator(iterator()); - } - - @Override - public Object[] toArray() { - PythonInteger[] out = new PythonInteger[size()]; - - for (int i = 0; i < out.length; i++) { - out[i] = get(i); - } - - return out; - } - - @Override - public T[] toArray(T[] ts) { - T[] out = ts; - if (ts.length < size()) { - out = (T[]) Array.newInstance(ts.getClass().getComponentType(), size()); - } - - for (int i = 0; i < out.length; i++) { - out[i] = (T) get(i); - } - - if (out.length > size()) { - out[size()] = null; - } - - return out; - } - - @Override - public boolean containsAll(Collection collection) { - for (Object o : collection) { - if (!contains(o)) { - return false; - } - } - return true; - } - - @Override - public PythonInteger get(int i) { - if (i < 0) { - throw new IndexOutOfBoundsException(); - } - - PythonInteger out = start.add(step.multiply(PythonInteger.valueOf(i))); - if (!contains(out)) { - throw new IndexOutOfBoundsException(); - } - return out; - } - - public PythonInteger getItem(PythonInteger index) { - if (index.value.compareTo(BigInteger.ZERO) < 0) { - throw new IndexOutOfBoundsException(); - } - PythonInteger out = start.add(step.multiply(index)); - - if (!contains(out)) { - throw new IndexOutOfBoundsException(); - } - - return out; - } - - @Override - public int indexOf(Object o) { - if (!contains(o)) { - return -1; - } - PythonInteger query = (PythonInteger) o; - BigInteger relativeToStart = query.value.subtract(start.value); - return relativeToStart.divide(step.value).intValueExact(); - } - - @Override - public int lastIndexOf(Object o) { - return indexOf(o); - } - - @Override - public ListIterator listIterator() { - return new RangeIterator(start, stop, step, start, 0); - } - - @Override - public ListIterator listIterator(int i) { - return new RangeIterator(get(i), stop, step, get(i), i); - } - - @Override - public List subList(int startIndexInclusive, int endIndexExclusive) { - return new PythonRange(get(startIndexInclusive), get(endIndexExclusive), step); - } - - @Override - public boolean addAll(Collection collection) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public boolean addAll(int i, Collection collection) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public boolean removeAll(Collection collection) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public boolean retainAll(Collection collection) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public PythonInteger set(int i, PythonInteger pythonInteger) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public void add(int i, PythonInteger pythonInteger) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public PythonInteger remove(int i) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public boolean add(PythonInteger pythonInteger) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - public static class RangeIterator implements ListIterator { - final PythonInteger startValue; - final PythonInteger stopValue; - final PythonInteger step; - final int startOffset; - - PythonInteger currentValue; - - public RangeIterator(PythonInteger startValue, PythonInteger stopValue, PythonInteger step, PythonInteger currentValue, - int startOffset) { - this.startValue = startValue; - this.stopValue = stopValue; - this.step = step; - this.currentValue = currentValue; - this.startOffset = startOffset; - } - - @Override - public boolean hasNext() { - if (step.value.compareTo(BigInteger.ZERO) < 0) { - return currentValue.compareTo(stopValue) > 0; - } else { - return currentValue.compareTo(stopValue) < 0; - } - } - - @Override - public PythonInteger next() { - PythonInteger out = currentValue; - currentValue = currentValue.add(step); - return out; - } - - @Override - public boolean hasPrevious() { - if (step.value.compareTo(BigInteger.ZERO) < 0) { - return currentValue.compareTo(startValue) < 0; - } else { - return currentValue.compareTo(startValue) > 0; - } - } - - @Override - public PythonInteger previous() { - PythonInteger out = currentValue; - currentValue = currentValue.subtract(step); - return out; - } - - @Override - public int nextIndex() { - return currentValue.value.divide(step.value).intValueExact() + startOffset + 1; - } - - @Override - public int previousIndex() { - return currentValue.value.divide(step.value).intValueExact() + startOffset - 1; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public void set(PythonInteger pythonInteger) { - throw new UnsupportedOperationException("Cannot modify range"); - } - - @Override - public void add(PythonInteger pythonInteger) { - throw new UnsupportedOperationException("Cannot modify range"); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java deleted file mode 100644 index 23d2e8e6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java +++ /dev/null @@ -1,297 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.Map; -import java.util.Objects; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonSlice extends AbstractPythonLikeObject { - public static PythonLikeType SLICE_TYPE = new PythonLikeType("slice", PythonSlice.class); - public static PythonLikeType $TYPE = SLICE_TYPE; - - public final PythonLikeObject start; - public final PythonLikeObject stop; - public final PythonLikeObject step; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonSlice::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - SLICE_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - PythonLikeObject start; - PythonLikeObject stop; - PythonLikeObject step; - - namedArguments = (namedArguments != null) ? namedArguments : Map.of(); - - if (positionalArguments.size() == 3) { - start = positionalArguments.get(0); - stop = positionalArguments.get(1); - step = positionalArguments.get(2); - } else if (positionalArguments.size() == 2) { - start = positionalArguments.get(0); - stop = positionalArguments.get(1); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonNone.INSTANCE); - } else if (positionalArguments.size() == 1 && namedArguments.containsKey(PythonString.valueOf("stop"))) { - start = positionalArguments.get(0); - stop = namedArguments.getOrDefault(PythonString.valueOf("stop"), PythonNone.INSTANCE); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonNone.INSTANCE); - } else if (positionalArguments.size() == 1) { - stop = positionalArguments.get(0); - start = PythonInteger.valueOf(0); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonNone.INSTANCE); - } else if (positionalArguments.isEmpty()) { - start = namedArguments.getOrDefault(PythonString.valueOf("start"), PythonInteger.valueOf(0)); - stop = namedArguments.getOrDefault(PythonString.valueOf("stop"), PythonNone.INSTANCE); - step = namedArguments.getOrDefault(PythonString.valueOf("step"), PythonNone.INSTANCE); - } else { - throw new ValueError("slice expects 1 to 3 arguments, got " + positionalArguments.size()); - } - - return new PythonSlice(start, stop, step); - })); - - // Unary - SLICE_TYPE.addUnaryMethod(PythonUnaryOperator.HASH, PythonSlice.class.getMethod("pythonHash")); - - // Binary - SLICE_TYPE.addBinaryMethod(PythonBinaryOperator.EQUAL, PythonSlice.class.getMethod("pythonEquals", PythonSlice.class)); - - // Other methods - SLICE_TYPE.addMethod("indices", PythonSlice.class.getMethod("indices", PythonInteger.class)); - - return SLICE_TYPE; - } - - public PythonSlice(PythonLikeObject start, PythonLikeObject stop, PythonLikeObject step) { - super(SLICE_TYPE); - this.start = start; - this.stop = stop; - this.step = step; - - $setAttribute("start", (start != null) ? start : PythonNone.INSTANCE); - $setAttribute("stop", (stop != null) ? stop : PythonNone.INSTANCE); - $setAttribute("step", (step != null) ? step : PythonNone.INSTANCE); - } - - /** - * Convert index into a index for a sequence of length {@code length}. May be outside the range - * [0, length - 1]. Use for indexing into a sequence. - * - * @param index The given index - * @param length The length - * @return index, if index in [0, length -1]; length - index, if index < 0. - */ - public static int asIntIndexForLength(PythonInteger index, int length) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - return length + indexAsInt; - } else { - return indexAsInt; - } - } - - /** - * Convert index into a VALID start index for a sequence of length {@code length}. bounding it to the - * range [0, length - 1]. Use for sequence operations that need to search an portion of a sequence. - * - * @param index The given index - * @param length The length - * @return index, if index in [0, length -1]; length - index, if index < 0 and -index <= length; - * otherwise 0 (if the index represent a position before 0) or length - 1 (if the index represent a - * position after the sequence). - */ - public static int asValidStartIntIndexForLength(PythonInteger index, int length) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - return Math.max(0, Math.min(length - 1, length + indexAsInt)); - } else { - return Math.max(0, Math.min(length - 1, indexAsInt)); - } - } - - /** - * Convert index into a VALID end index for a sequence of length {@code length}. bounding it to the - * range [0, length]. Use for sequence operations that need to search an portion of a sequence. - * - * @param index The given index - * @param length The length - * @return index, if index in [0, length]; length - index, if index < 0 and -index <= length + 1; - * otherwise 0 (if the index represent a position before 0) or length (if the index represent a - * position after the sequence). - */ - public static int asValidEndIntIndexForLength(PythonInteger index, int length) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - return Math.max(0, Math.min(length, length + indexAsInt)); - } else { - return Math.max(0, Math.min(length, indexAsInt)); - } - } - - private record SliceIndices(int start, int stop, int strideLength) { - } - - private SliceIndices getSliceIndices(int length) { - return new SliceIndices(getStartIndex(length), getStopIndex(length), getStrideLength()); - } - - private SliceIndices getSliceIndices(PythonInteger length) { - return getSliceIndices(length.getValue().intValue()); - } - - public PythonLikeTuple indices(PythonInteger sequenceLength) { - var sliceIndices = getSliceIndices(sequenceLength); - - return PythonLikeTuple.fromItems(PythonInteger.valueOf(sliceIndices.start), - PythonInteger.valueOf(sliceIndices.start), - PythonInteger.valueOf(sliceIndices.strideLength)); - } - - public int getStartIndex(int length) { - int startIndex; - boolean isReversed = getStrideLength() < 0; - - 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; - } - - return startIndex; - } - - public int getStopIndex(int length) { - int stopIndex; - boolean isReversed = getStrideLength() < 0; - - 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; - } - - return stopIndex; - } - - public int getStrideLength() { - PythonInteger strideLength; - - if (step instanceof PythonInteger) { - strideLength = (PythonInteger) step; - } else if (step != null && step != PythonNone.INSTANCE) { - strideLength = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(step); - } else { - strideLength = PythonInteger.ONE; - } - - int out = strideLength.value.intValueExact(); - - if (out == 0) { - throw new ValueError("stride length cannot be zero"); - } - - return out; - } - - public void iterate(int length, SliceConsumer consumer) { - var sliceIndices = getSliceIndices(length); - - int step = 0; - if (sliceIndices.strideLength < 0) { - for (int i = sliceIndices.start; i > sliceIndices.stop; i += sliceIndices.strideLength) { - consumer.accept(i, step); - step++; - } - } else { - for (int i = sliceIndices.start; i < sliceIndices.stop; i += sliceIndices.strideLength) { - consumer.accept(i, step); - step++; - } - } - } - - private boolean isReversed() { - return getStrideLength() < 0; - } - - public int getSliceSize(int length) { - var sliceIndices = getSliceIndices(length); - int span = sliceIndices.stop - sliceIndices.start; - int strideLength = sliceIndices.strideLength; - - // ceil division - return span / strideLength + (span % strideLength == 0 ? 0 : 1); - } - - public PythonBoolean pythonEquals(PythonSlice other) { - return PythonBoolean.valueOf(this.equals(other)); - } - - public PythonInteger pythonHash() { - return PythonInteger.valueOf(hashCode()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonSlice that = (PythonSlice) o; - return Objects.equals(start, that.start) && Objects.equals(stop, that.stop) && Objects.equals(step, that.step); - } - - @Override - public int hashCode() { - return Objects.hash(start, stop, step); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } - - public interface SliceConsumer { - void accept(int index, int step); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java deleted file mode 100644 index eecf634f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java +++ /dev/null @@ -1,1525 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.IntPredicate; -import java.util.function.IntUnaryOperator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.BinaryDunderBuiltin; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.errors.lookup.LookupError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeDecodeError; -import ai.timefold.jpyinterpreter.types.errors.unicode.UnicodeEncodeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.DefaultFormatSpec; -import ai.timefold.jpyinterpreter.util.StringFormatter; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonString extends AbstractPythonLikeObject implements PythonLikeComparable, PlanningImmutable { - public final String value; - - public final static PythonString EMPTY = new PythonString(""); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonString::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - PythonLikeComparable.setup(BuiltinTypes.STRING_TYPE); - BuiltinTypes.STRING_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.size() == 1) { // TODO: Named arguments - return UnaryDunderBuiltin.STR.invoke(positionalArguments.get(0)); - } else if (positionalArguments.size() == 2) { - if (!(positionalArguments.get(0) instanceof PythonBytes) || - !(positionalArguments.get(1) instanceof PythonString)) { - throw new TypeError(); // TODO: Better error message - } - return ((PythonBytes) positionalArguments.get(0)).decode((PythonString) positionalArguments.get(1)); - } else if (positionalArguments.size() == 3) { - if (!(positionalArguments.get(0) instanceof PythonBytes) || - !(positionalArguments.get(1) instanceof PythonString) || - !(positionalArguments.get(2) instanceof PythonString)) { - throw new TypeError(); // TODO: Better error message - } - return ((PythonBytes) positionalArguments.get(0)).decode((PythonString) positionalArguments.get(1), - (PythonString) positionalArguments.get(2)); - } else { - throw new ValueError("str expects 1 or 3 arguments, got " + positionalArguments.size()); - } - }); - - // Unary - BuiltinTypes.STRING_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, PythonString.class.getMethod("repr")); - BuiltinTypes.STRING_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, PythonString.class.getMethod("asString")); - BuiltinTypes.STRING_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonString.class.getMethod("getIterator")); - BuiltinTypes.STRING_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonString.class.getMethod("getLength")); - - // Binary - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonString.class.getMethod("getCharAt", PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonString.class.getMethod("getSubstring", PythonSlice.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonString.class.getMethod("containsSubstring", PythonString.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonString.class.getMethod("concat", PythonString.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonString.class.getMethod("repeat", PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonString.class.getMethod("interpolate", PythonLikeObject.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonString.class.getMethod("interpolate", PythonLikeTuple.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonString.class.getMethod("interpolate", PythonLikeDict.class)); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, PythonString.class.getMethod("formatSelf")); - BuiltinTypes.STRING_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonString.class.getMethod("formatSelf", PythonString.class)); - - // Other - BuiltinTypes.STRING_TYPE.addMethod("capitalize", PythonString.class.getMethod("capitalize")); - BuiltinTypes.STRING_TYPE.addMethod("casefold", PythonString.class.getMethod("casefold")); - - BuiltinTypes.STRING_TYPE.addMethod("center", PythonString.class.getMethod("center", PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("center", - PythonString.class.getMethod("center", PythonInteger.class, PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("count", PythonString.class.getMethod("count", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("count", - PythonString.class.getMethod("count", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("count", - PythonString.class.getMethod("count", PythonString.class, PythonInteger.class, PythonInteger.class)); - - // TODO: encode - - BuiltinTypes.STRING_TYPE.addMethod("endswith", PythonString.class.getMethod("endsWith", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("endswith", PythonString.class.getMethod("endsWith", PythonLikeTuple.class)); - BuiltinTypes.STRING_TYPE.addMethod("endswith", - PythonString.class.getMethod("endsWith", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("endswith", - PythonString.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("endswith", - PythonString.class.getMethod("endsWith", PythonString.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("endswith", - PythonString.class.getMethod("endsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("expandtabs", PythonString.class.getMethod("expandTabs")); - BuiltinTypes.STRING_TYPE.addMethod("expandtabs", PythonString.class.getMethod("expandTabs", PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("find", PythonString.class.getMethod("findSubstringIndex", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("find", - PythonString.class.getMethod("findSubstringIndex", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("find", PythonString.class.getMethod("findSubstringIndex", PythonString.class, - PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("format", ArgumentSpec.forFunctionReturning("format", PythonString.class.getName()) - .addExtraPositionalVarArgument("vargs") - .addExtraKeywordVarArgument("kwargs") - .asPythonFunctionSignature(PythonString.class.getMethod("format", List.class, Map.class))); - - BuiltinTypes.STRING_TYPE.addMethod("format_map", PythonString.class.getMethod("formatMap", PythonLikeDict.class)); - - BuiltinTypes.STRING_TYPE.addMethod("index", - PythonString.class.getMethod("findSubstringIndexOrError", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("index", - PythonString.class.getMethod("findSubstringIndexOrError", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("index", - PythonString.class.getMethod("findSubstringIndexOrError", PythonString.class, - PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("isalnum", PythonString.class.getMethod("isAlphaNumeric")); - BuiltinTypes.STRING_TYPE.addMethod("isalpha", PythonString.class.getMethod("isAlpha")); - BuiltinTypes.STRING_TYPE.addMethod("isascii", PythonString.class.getMethod("isAscii")); - BuiltinTypes.STRING_TYPE.addMethod("isdecimal", PythonString.class.getMethod("isDecimal")); - BuiltinTypes.STRING_TYPE.addMethod("isdigit", PythonString.class.getMethod("isDigit")); - BuiltinTypes.STRING_TYPE.addMethod("isidentifier", PythonString.class.getMethod("isIdentifier")); - BuiltinTypes.STRING_TYPE.addMethod("islower", PythonString.class.getMethod("isLower")); - BuiltinTypes.STRING_TYPE.addMethod("isnumeric", PythonString.class.getMethod("isNumeric")); - BuiltinTypes.STRING_TYPE.addMethod("isprintable", PythonString.class.getMethod("isPrintable")); - BuiltinTypes.STRING_TYPE.addMethod("isspace", PythonString.class.getMethod("isSpace")); - BuiltinTypes.STRING_TYPE.addMethod("istitle", PythonString.class.getMethod("isTitle")); - BuiltinTypes.STRING_TYPE.addMethod("isupper", PythonString.class.getMethod("isUpper")); - - BuiltinTypes.STRING_TYPE.addMethod("join", PythonString.class.getMethod("join", PythonLikeObject.class)); - - BuiltinTypes.STRING_TYPE.addMethod("ljust", PythonString.class.getMethod("leftJustify", PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("ljust", - PythonString.class.getMethod("leftJustify", PythonInteger.class, PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("lower", PythonString.class.getMethod("lower")); - - BuiltinTypes.STRING_TYPE.addMethod("lstrip", PythonString.class.getMethod("leftStrip")); - BuiltinTypes.STRING_TYPE.addMethod("lstrip", PythonString.class.getMethod("leftStrip", PythonNone.class)); - BuiltinTypes.STRING_TYPE.addMethod("lstrip", PythonString.class.getMethod("leftStrip", PythonString.class)); - - // TODO: maketrans - - BuiltinTypes.STRING_TYPE.addMethod("partition", PythonString.class.getMethod("partition", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("removeprefix", PythonString.class.getMethod("removePrefix", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("removesuffix", PythonString.class.getMethod("removeSuffix", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("replace", - PythonString.class.getMethod("replaceAll", PythonString.class, PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("replace", - PythonString.class.getMethod("replaceUpToCount", PythonString.class, PythonString.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rfind", - PythonString.class.getMethod("rightFindSubstringIndex", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("rfind", - PythonString.class.getMethod("rightFindSubstringIndex", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("rfind", PythonString.class.getMethod("rightFindSubstringIndex", PythonString.class, - PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rindex", - PythonString.class.getMethod("rightFindSubstringIndexOrError", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("rindex", - PythonString.class.getMethod("rightFindSubstringIndexOrError", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("rindex", - PythonString.class.getMethod("rightFindSubstringIndexOrError", PythonString.class, - PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rjust", PythonString.class.getMethod("rightJustify", PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("rjust", - PythonString.class.getMethod("rightJustify", PythonInteger.class, PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rpartition", PythonString.class.getMethod("rightPartition", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rsplit", PythonString.class.getMethod("rightSplit")); - BuiltinTypes.STRING_TYPE.addMethod("rsplit", PythonString.class.getMethod("rightSplit", PythonNone.class)); - BuiltinTypes.STRING_TYPE.addMethod("rsplit", PythonString.class.getMethod("rightSplit", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("rsplit", - PythonString.class.getMethod("rightSplit", PythonNone.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("rsplit", - PythonString.class.getMethod("rightSplit", PythonString.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("rstrip", PythonString.class.getMethod("rightStrip")); - BuiltinTypes.STRING_TYPE.addMethod("rstrip", PythonString.class.getMethod("rightStrip", PythonNone.class)); - BuiltinTypes.STRING_TYPE.addMethod("rstrip", PythonString.class.getMethod("rightStrip", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("split", PythonString.class.getMethod("split")); - BuiltinTypes.STRING_TYPE.addMethod("split", PythonString.class.getMethod("split", PythonNone.class)); - BuiltinTypes.STRING_TYPE.addMethod("split", PythonString.class.getMethod("split", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("split", - PythonString.class.getMethod("split", PythonNone.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("split", - PythonString.class.getMethod("split", PythonString.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("splitlines", PythonString.class.getMethod("splitLines")); - BuiltinTypes.STRING_TYPE.addMethod("splitlines", PythonString.class.getMethod("splitLines", PythonBoolean.class)); - - BuiltinTypes.STRING_TYPE.addMethod("startswith", PythonString.class.getMethod("startsWith", PythonString.class)); - BuiltinTypes.STRING_TYPE.addMethod("startswith", PythonString.class.getMethod("startsWith", PythonLikeTuple.class)); - BuiltinTypes.STRING_TYPE.addMethod("startswith", - PythonString.class.getMethod("startsWith", PythonString.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("startswith", - PythonString.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("startswith", - PythonString.class.getMethod("startsWith", PythonString.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.STRING_TYPE.addMethod("startswith", - PythonString.class.getMethod("startsWith", PythonLikeTuple.class, PythonInteger.class, PythonInteger.class)); - - BuiltinTypes.STRING_TYPE.addMethod("strip", PythonString.class.getMethod("strip")); - BuiltinTypes.STRING_TYPE.addMethod("strip", PythonString.class.getMethod("strip", PythonNone.class)); - BuiltinTypes.STRING_TYPE.addMethod("strip", PythonString.class.getMethod("strip", PythonString.class)); - - BuiltinTypes.STRING_TYPE.addMethod("swapcase", PythonString.class.getMethod("swapCase")); - - BuiltinTypes.STRING_TYPE.addMethod("title", PythonString.class.getMethod("title")); - - BuiltinTypes.STRING_TYPE.addMethod("translate", PythonString.class.getMethod("translate", PythonLikeObject.class)); - - BuiltinTypes.STRING_TYPE.addMethod("upper", PythonString.class.getMethod("upper")); - - BuiltinTypes.STRING_TYPE.addMethod("zfill", PythonString.class.getMethod("zfill", PythonInteger.class)); - - return BuiltinTypes.STRING_TYPE; - } - - public PythonString(String value) { - super(BuiltinTypes.STRING_TYPE); - this.value = value; - } - - public static PythonString valueOf(String value) { - return new PythonString(value); - } - - public String getValue() { - return value; - } - - public final PythonBytes asAsciiBytes() { - char[] charData = value.toCharArray(); - int length = 0; - for (char charDatum : charData) { - if (charDatum < 0xFF) { - length++; - } else { - length += 2; - } - } - byte[] out = new byte[length]; - - int outIndex = 0; - for (char charDatum : charData) { - if (charDatum < 0xFF) { - out[outIndex] = (byte) charDatum; - outIndex++; - } else { - out[outIndex] = (byte) ((charDatum & 0xFF00) >> 8); - outIndex++; - out[outIndex] = (byte) (charDatum & 0x00FF); - outIndex++; - } - } - return new PythonBytes(out); - } - - public final PythonByteArray asAsciiByteArray() { - return new PythonByteArray(asAsciiBytes().value); - } - - public PythonBytes encode() { - try { - ByteBuffer byteBuffer = StandardCharsets.UTF_8.newEncoder() - .onMalformedInput(CodingErrorAction.REPORT) - .encode(CharBuffer.wrap(value)); - byte[] out = new byte[byteBuffer.limit()]; - byteBuffer.get(out); - return new PythonBytes(out); - } catch (CharacterCodingException e) { - throw new UnicodeEncodeError(e.getMessage()); - } - - } - - public PythonBytes encode(PythonString charset) { - try { - ByteBuffer byteBuffer = Charset.forName(charset.value).newEncoder() - .onMalformedInput(CodingErrorAction.REPORT) - .encode(CharBuffer.wrap(value)); - byte[] out = new byte[byteBuffer.limit()]; - byteBuffer.get(out); - return new PythonBytes(out); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public PythonBytes encode(PythonString charset, PythonString errorActionString) { - CodingErrorAction errorAction; - switch (errorActionString.value) { - case "strict": - errorAction = CodingErrorAction.REPORT; - break; - case "ignore": - errorAction = CodingErrorAction.IGNORE; - break; - case "replace": - errorAction = CodingErrorAction.REPLACE; - break; - default: - throw new ValueError(errorActionString.repr() + " is not a valid value for errors. Possible values are: " + - "\"strict\", \"ignore\", \"replace\"."); - } - try { - ByteBuffer byteBuffer = Charset.forName(charset.value).newEncoder() - .onMalformedInput(errorAction) - .encode(CharBuffer.wrap(value)); - byte[] out = new byte[byteBuffer.limit()]; - byteBuffer.get(out); - return new PythonBytes(out); - } catch (CharacterCodingException e) { - throw new UnicodeDecodeError(e.getMessage()); - } - } - - public int length() { - return value.length(); - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(value.length()); - } - - public PythonString getCharAt(PythonInteger position) { - int index = PythonSlice.asIntIndexForLength(position, value.length()); - - if (index >= value.length()) { - throw new IndexError("position " + position + " larger than string length " + value.length()); - } else if (index < 0) { - throw new IndexError("position " + position + " is less than 0"); - } - - return new PythonString(Character.toString(value.charAt(index))); - } - - public PythonString getSubstring(PythonSlice slice) { - int length = value.length(); - int start = slice.getStartIndex(length); - int stop = slice.getStopIndex(length); - int step = slice.getStrideLength(); - - if (step == 1) { - if (stop <= start) { - return PythonString.valueOf(""); - } else { - return PythonString.valueOf(value.substring(start, stop)); - } - } else { - StringBuilder out = new StringBuilder(); - if (step > 0) { - for (int i = start; i < stop; i += step) { - out.append(value.charAt(i)); - } - } else { - for (int i = start; i > stop; i += step) { - out.append(value.charAt(i)); - } - } - return PythonString.valueOf(out.toString()); - } - } - - public PythonBoolean containsSubstring(PythonString substring) { - return PythonBoolean.valueOf(value.contains(substring.value)); - } - - public PythonString concat(PythonString other) { - if (value.isEmpty()) { - return other; - } else if (other.value.isEmpty()) { - return this; - } else { - return PythonString.valueOf(value + other.value); - } - } - - public PythonString repeat(PythonInteger times) { - int timesAsInt = times.value.intValueExact(); - - if (timesAsInt <= 0) { - return EMPTY; - } - - if (timesAsInt == 1) { - return this; - } - - return PythonString.valueOf(value.repeat(timesAsInt)); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator(value.chars().mapToObj(charVal -> new PythonString(Character.toString(charVal))) - .iterator()); - } - - public PythonString capitalize() { - if (value.isEmpty()) { - return this; - } - return PythonString.valueOf(Character.toTitleCase(value.charAt(0)) + value.substring(1).toLowerCase()); - } - - public PythonString title() { - return title(ignored -> true); - } - - public PythonString title(IntPredicate predicate) { - if (value.isEmpty()) { - return this; - } - - int length = value.length(); - boolean previousIsWordBoundary = true; - - StringBuilder out = new StringBuilder(length); - for (int i = 0; i < length; i++) { - char character = value.charAt(i); - - if (predicate.test(character)) { - if (previousIsWordBoundary) { - out.append(Character.toTitleCase(character)); - } else { - out.append(Character.toLowerCase(character)); - } - } else { - out.append(character); - } - - previousIsWordBoundary = !Character.isAlphabetic(character); - } - - return PythonString.valueOf(out.toString()); - } - - public PythonString casefold() { - // This will work for the majority of cases, but fail for some cases - return PythonString.valueOf(value.toUpperCase().toLowerCase()); - } - - public PythonString swapCase() { - return withModifiedCodepoints(CharacterCase::swapCase); - } - - public PythonString lower() { - return PythonString.valueOf(value.toLowerCase()); - } - - public PythonString upper() { - return PythonString.valueOf(value.toUpperCase()); - } - - public PythonString withModifiedCodepoints(IntUnaryOperator modifier) { - return PythonString.valueOf(value.codePoints() - .map(modifier) - .collect(StringBuilder::new, - StringBuilder::appendCodePoint, StringBuilder::append) - .toString()); - } - - public PythonString center(PythonInteger width) { - return center(width, PythonString.valueOf(" ")); - } - - public PythonString center(PythonInteger width, PythonString fillChar) { - int widthAsInt = width.value.intValueExact(); - if (widthAsInt <= value.length()) { - return this; - } - int extraWidth = widthAsInt - value.length(); - int rightPadding = extraWidth / 2; - // left padding get extra character if extraWidth is odd - int leftPadding = rightPadding + (extraWidth & 1); // x & 1 == x % 2 - - if (fillChar.value.length() != 1) { - throw new TypeError("The fill character must be exactly one character long"); - } - - String fillCharAsString = fillChar.value; - - return PythonString.valueOf(fillCharAsString.repeat(leftPadding) + - value + - fillCharAsString.repeat(rightPadding)); - } - - public PythonString rightJustify(PythonInteger width) { - return rightJustify(width, PythonString.valueOf(" ")); - } - - public PythonString rightJustify(PythonInteger width, PythonString fillChar) { - int widthAsInt = width.value.intValueExact(); - if (widthAsInt <= value.length()) { - return this; - } - int leftPadding = widthAsInt - value.length(); - - if (fillChar.value.length() != 1) { - throw new TypeError("The fill character must be exactly one character long"); - } - - return PythonString.valueOf(fillChar.value.repeat(leftPadding) + value); - } - - public PythonString leftJustify(PythonInteger width) { - return leftJustify(width, PythonString.valueOf(" ")); - } - - public PythonString leftJustify(PythonInteger width, PythonString fillChar) { - int widthAsInt = width.value.intValueExact(); - if (widthAsInt <= value.length()) { - return this; - } - int rightPadding = widthAsInt - value.length(); - - if (fillChar.value.length() != 1) { - throw new TypeError("The fill character must be exactly one character long"); - } - - return PythonString.valueOf(value + fillChar.value.repeat(rightPadding)); - } - - public PythonInteger count(PythonString sub) { - Matcher matcher = Pattern.compile(Pattern.quote(sub.value)).matcher(value); - return PythonInteger.valueOf(matcher.results().count()); - } - - public PythonInteger count(PythonString sub, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - - Matcher matcher = Pattern.compile(Pattern.quote(sub.value)).matcher(value.substring(startIndex)); - return PythonInteger.valueOf(matcher.results().count()); - } - - public PythonInteger count(PythonString sub, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - Matcher matcher = Pattern.compile(Pattern.quote(sub.value)).matcher(value.substring(startIndex, endIndex)); - return PythonInteger.valueOf(matcher.results().count()); - } - - // TODO: encode https://docs.python.org/3/library/stdtypes.html#str.encode - - public PythonBoolean startsWith(PythonString prefix) { - return PythonBoolean.valueOf(value.startsWith(prefix.value)); - } - - public PythonBoolean startsWith(PythonLikeTuple prefixTuple) { - for (PythonString prefix : prefixTuple) { - if (value.startsWith(prefix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonString prefix, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - return PythonBoolean.valueOf(value.substring(startIndex).startsWith(prefix.value)); - } - - public PythonBoolean startsWith(PythonLikeTuple prefixTuple, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - String toCheck = value.substring(startIndex); - for (PythonString prefix : prefixTuple) { - if (toCheck.startsWith(prefix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean startsWith(PythonString prefix, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - return PythonBoolean.valueOf(value.substring(startIndex, endIndex).startsWith(prefix.value)); - } - - public PythonBoolean startsWith(PythonLikeTuple prefixTuple, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - String toCheck = value.substring(startIndex, endIndex); - for (PythonString prefix : prefixTuple) { - if (toCheck.startsWith(prefix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonString suffix) { - return PythonBoolean.valueOf(value.endsWith(suffix.value)); - } - - public PythonBoolean endsWith(PythonLikeTuple suffixTuple) { - for (PythonString suffix : suffixTuple) { - if (value.endsWith(suffix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonString suffix, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - return PythonBoolean.valueOf(value.substring(startIndex).endsWith(suffix.value)); - } - - public PythonBoolean endsWith(PythonLikeTuple suffixTuple, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - String toCheck = value.substring(startIndex); - for (PythonString suffix : suffixTuple) { - if (toCheck.endsWith(suffix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonBoolean endsWith(PythonString suffix, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - return PythonBoolean.valueOf(value.substring(startIndex, endIndex).endsWith(suffix.value)); - } - - public PythonBoolean endsWith(PythonLikeTuple suffixTuple, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - String toCheck = value.substring(startIndex, endIndex); - for (PythonString suffix : suffixTuple) { - if (toCheck.endsWith(suffix.value)) { - return PythonBoolean.TRUE; - } - } - return PythonBoolean.FALSE; - } - - public PythonString expandTabs() { - return expandTabs(PythonInteger.valueOf(8)); - } - - public PythonString expandTabs(PythonInteger tabsize) { - int tabsizeAsInt = tabsize.value.intValueExact(); - - int column = 0; - int length = value.length(); - StringBuilder builder = new StringBuilder(); - - for (int i = 0; i < length; i++) { - char character = value.charAt(i); - - if (character == '\n' || character == '\r') { - builder.append(character); - column = 0; - continue; - } - - if (character == '\t') { - int remainder = tabsizeAsInt - (column % tabsizeAsInt); - builder.append(" ".repeat(remainder)); - column += remainder; - continue; - } - - builder.append(character); - column++; - } - - return PythonString.valueOf(builder.toString()); - } - - public PythonInteger findSubstringIndex(PythonString substring) { - return PythonInteger.valueOf(value.indexOf(substring.value)); - } - - public PythonInteger findSubstringIndex(PythonString substring, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int result = value.indexOf(substring.value, startIndex); - - return PythonInteger.valueOf(result); - } - - public PythonInteger findSubstringIndex(PythonString substring, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - int result = value.substring(startIndex, endIndex).indexOf(substring.value); - return PythonInteger.valueOf(result < 0 ? result : result + startIndex); - } - - public PythonInteger rightFindSubstringIndex(PythonString substring) { - return PythonInteger.valueOf(value.lastIndexOf(substring.value)); - } - - public PythonInteger rightFindSubstringIndex(PythonString substring, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int result = value.substring(startIndex).lastIndexOf(substring.value); - - return PythonInteger.valueOf(result < 0 ? result : result + startIndex); - } - - public PythonInteger rightFindSubstringIndex(PythonString substring, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - int result = value.substring(startIndex, endIndex).lastIndexOf(substring.value); - - return PythonInteger.valueOf(result < 0 ? result : result + startIndex); - } - - public PythonString format(List positionalArguments, Map namedArguments) { - return PythonString.valueOf(StringFormatter.format(value, positionalArguments, namedArguments)); - } - - public PythonString formatMap(PythonLikeDict dict) { - return format(Collections.emptyList(), (Map) dict); - } - - public PythonString formatSelf() { - return this; - } - - public PythonString formatSelf(PythonString spec) { - if (spec.value.isEmpty()) { - return this; - } - - DefaultFormatSpec formatSpec = DefaultFormatSpec.fromStringSpec(spec); - - StringBuilder out = new StringBuilder(); - - switch (formatSpec.conversionType.orElse(DefaultFormatSpec.ConversionType.STRING)) { - case STRING: - out.append(value); - break; - default: - throw new ValueError("Invalid conversion type for str: " + formatSpec.conversionType); - } - - StringFormatter.align(out, formatSpec, DefaultFormatSpec.AlignmentOption.LEFT_ALIGN); - return PythonString.valueOf(out.toString()); - } - - public PythonString interpolate(PythonLikeObject object) { - if (object instanceof PythonLikeTuple) { - return interpolate((PythonLikeTuple) object); - } else if (object instanceof PythonLikeDict) { - return interpolate((PythonLikeDict) object); - } else { - return interpolate(PythonLikeTuple.fromItems(object)); - } - } - - public PythonString interpolate(PythonLikeTuple tuple) { - return PythonString.valueOf(StringFormatter.printfInterpolate(value, tuple, StringFormatter.PrintfStringType.STRING)); - } - - public PythonString interpolate(PythonLikeDict dict) { - return PythonString.valueOf(StringFormatter.printfInterpolate(value, dict, StringFormatter.PrintfStringType.STRING)); - } - - public PythonInteger findSubstringIndexOrError(PythonString substring) { - int result = value.indexOf(substring.value); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result); - } - - public PythonInteger findSubstringIndexOrError(PythonString substring, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - - int result = value.indexOf(substring.value, startIndex); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result); - } - - public PythonInteger findSubstringIndexOrError(PythonString substring, PythonInteger start, PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - int result = value.substring(startIndex, endIndex).indexOf(substring.value); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result + startIndex); - } - - public PythonInteger rightFindSubstringIndexOrError(PythonString substring) { - int result = value.lastIndexOf(substring.value); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result); - } - - public PythonInteger rightFindSubstringIndexOrError(PythonString substring, PythonInteger start) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - - int result = value.substring(startIndex).lastIndexOf(substring.value); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result + startIndex); - } - - public PythonInteger rightFindSubstringIndexOrError(PythonString substring, PythonInteger start, - PythonInteger end) { - int startIndex = PythonSlice.asValidStartIntIndexForLength(start, value.length()); - int endIndex = PythonSlice.asValidEndIntIndexForLength(end, value.length()); - - int result = value.substring(startIndex, endIndex).lastIndexOf(substring.value); - if (result == -1) { - throw new ValueError("substring not found"); - } - return PythonInteger.valueOf(result + startIndex); - } - - private PythonBoolean allCharactersHaveProperty(IntPredicate predicate) { - int length = value.length(); - if (length == 0) { - return PythonBoolean.FALSE; - } - - for (int i = 0; i < length; i++) { - char character = value.charAt(i); - - if (!predicate.test(character)) { - return PythonBoolean.FALSE; - } - } - - return PythonBoolean.TRUE; - } - - public PythonBoolean isAlphaNumeric() { - return allCharactersHaveProperty( - character -> Character.isLetter(character) || Character.getNumericValue(character) != -1); - } - - public PythonBoolean isAlpha() { - return allCharactersHaveProperty(Character::isLetter); - } - - public PythonBoolean isAscii() { - if (value.isEmpty()) { - return PythonBoolean.TRUE; - } - return allCharactersHaveProperty(character -> character <= 127); - } - - public PythonBoolean isDecimal() { - return allCharactersHaveProperty(Character::isDigit); - } - - private boolean isSuperscriptOrSubscript(int character) { - String characterName = Character.getName(character); - return characterName.contains("SUPERSCRIPT") || characterName.contains("SUBSCRIPT"); - } - - public PythonBoolean isDigit() { - return allCharactersHaveProperty(character -> { - if (Character.getType(character) == Character.DECIMAL_DIGIT_NUMBER) { - return true; - } - return Character.isDigit(character) || (Character.getNumericValue(character) >= 0 && - isSuperscriptOrSubscript(character)); - }); - } - - public PythonBoolean isIdentifier() { - int length = value.length(); - if (length == 0) { - return PythonBoolean.FALSE; - } - - char firstChar = value.charAt(0); - if (!isPythonIdentifierStart(firstChar)) { - return PythonBoolean.FALSE; - } - - for (int i = 1; i < length; i++) { - char character = value.charAt(i); - - if (!isPythonIdentifierPart(character)) { - return PythonBoolean.FALSE; - } - } - - return PythonBoolean.TRUE; - } - - private static boolean isPythonIdentifierStart(char character) { - if (Character.isLetter(character)) { - return true; - } - if (Character.getType(character) == Character.LETTER_NUMBER) { - return true; - } - - switch (character) { - case '_': - case 0x1885: - case 0x1886: - case 0x2118: - case 0x212E: - case 0x309B: - case 0x309C: - return true; - default: - return false; - } - } - - private static boolean isPythonIdentifierPart(char character) { - if (isPythonIdentifierStart(character)) { - return true; - } - switch (Character.getType(character)) { - case Character.NON_SPACING_MARK: - case Character.COMBINING_SPACING_MARK: - case Character.DECIMAL_DIGIT_NUMBER: - case Character.CONNECTOR_PUNCTUATION: - return true; - } - - switch (character) { - case 0x00B7: - case 0x0387: - case 0x19DA: - return true; - default: - return character >= 0x1369 && character <= 0x1371; - } - } - - private static boolean hasCase(int character) { - return Character.isUpperCase(character) || - Character.isLowerCase(character) || - Character.isTitleCase(character); - } - - private boolean hasCaseCharacters() { - return !allCharactersHaveProperty(character -> !hasCase(character)).getBooleanValue(); - } - - public PythonBoolean isLower() { - if (hasCaseCharacters()) { - return allCharactersHaveProperty(character -> !hasCase(character) || Character.isLowerCase(character)); - } else { - return PythonBoolean.FALSE; - } - } - - public PythonBoolean isNumeric() { - return allCharactersHaveProperty(character -> { - switch (Character.getType(character)) { - case Character.OTHER_NUMBER: - case Character.DECIMAL_DIGIT_NUMBER: - return true; - default: - return !Character.isLetter(character) && Character.getNumericValue(character) != -1; - } - }); - } - - private static boolean isCharacterPrintable(int character) { - if (character == ' ') { - return true; - } - switch (Character.getType(character)) { - // Others - case Character.PRIVATE_USE: - case Character.FORMAT: - case Character.CONTROL: - case Character.UNASSIGNED: - - // Separators - case Character.SPACE_SEPARATOR: - case Character.LINE_SEPARATOR: - case Character.PARAGRAPH_SEPARATOR: - return false; - - default: - return true; - } - } - - public PythonBoolean isPrintable() { - if (value.isEmpty()) { - return PythonBoolean.TRUE; - } - - return allCharactersHaveProperty(PythonString::isCharacterPrintable); - } - - public PythonBoolean isSpace() { - return PythonBoolean.valueOf(!value.isEmpty() && value.isBlank()); - } - - public PythonBoolean isUpper() { - if (hasCaseCharacters()) { - return allCharactersHaveProperty(character -> !hasCase(character) || Character.isUpperCase(character)); - } else { - return PythonBoolean.FALSE; - } - } - - enum CharacterCase { - UNCASED, - LOWER, - UPPER; - - public static CharacterCase getCase(int character) { - if (Character.isLowerCase(character)) { - return LOWER; - } else if (Character.isUpperCase(character)) { - return UPPER; - } else { - return UNCASED; - } - } - - public static int swapCase(int character) { - if (Character.isLowerCase(character)) { - return Character.toUpperCase(character); - } else if (Character.isUpperCase(character)) { - return Character.toLowerCase(character); - } - return character; - } - } - - public PythonBoolean isTitle() { - int length = value.length(); - if (length == 0) { - return PythonBoolean.FALSE; - } - - CharacterCase previousType = CharacterCase.UNCASED; - for (int i = 0; i < length; i++) { - char character = value.charAt(i); - - CharacterCase characterCase = CharacterCase.getCase(character); - if (characterCase == CharacterCase.UNCASED && Character.isLetter(character)) { - return PythonBoolean.FALSE; - } - - switch (previousType) { - case UNCASED: - if (characterCase != CharacterCase.UPPER) { - return PythonBoolean.FALSE; - } - break; - case UPPER: - case LOWER: - if (characterCase == CharacterCase.UPPER) { - return PythonBoolean.FALSE; - } - break; - } - previousType = characterCase; - } - return PythonBoolean.TRUE; - } - - public PythonString join(PythonLikeObject iterable) { - PythonIterator iterator = (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - int index = 0; - StringBuilder out = new StringBuilder(); - - while (iterator.hasNext()) { - PythonLikeObject maybeString = iterator.nextPythonItem(); - if (!(maybeString instanceof PythonString)) { - throw new TypeError("sequence item " + index + ": expected str instance, " - + maybeString.$getType().getTypeName() + " found"); - } - PythonString string = (PythonString) maybeString; - out.append(string.value); - if (iterator.hasNext()) { - out.append(value); - } - index++; - } - - return PythonString.valueOf(out.toString()); - } - - public PythonString strip() { - return PythonString.valueOf(value.strip()); - } - - public PythonString strip(PythonNone ignored) { - return strip(); - } - - public PythonString strip(PythonString toStrip) { - int length = value.length(); - - int start = 0; - int end = length - 1; - - for (; start < length; start++) { - if (toStrip.value.indexOf(value.charAt(start)) == -1) { - break; - } - } - - if (start == length) { - return EMPTY; - } - - for (; end >= start; end--) { - if (toStrip.value.indexOf(value.charAt(end)) == -1) { - break; - } - } - - return PythonString.valueOf(value.substring(start, end + 1)); - } - - public PythonString leftStrip() { - return PythonString.valueOf(value.stripLeading()); - } - - public PythonString leftStrip(PythonNone ignored) { - return leftStrip(); - } - - public PythonString leftStrip(PythonString toStrip) { - int length = value.length(); - for (int i = 0; i < length; i++) { - if (toStrip.value.indexOf(value.charAt(i)) == -1) { - return PythonString.valueOf(value.substring(i)); - } - } - return EMPTY; - } - - public PythonString rightStrip() { - return PythonString.valueOf(value.stripTrailing()); - } - - public PythonString rightStrip(PythonNone ignored) { - return rightStrip(); - } - - public PythonString rightStrip(PythonString toStrip) { - int length = value.length(); - for (int i = length - 1; i >= 0; i--) { - if (toStrip.value.indexOf(value.charAt(i)) == -1) { - return PythonString.valueOf(value.substring(0, i + 1)); - } - } - return EMPTY; - } - - public PythonLikeTuple partition(PythonString seperator) { - int firstIndex = value.indexOf(seperator.value); - if (firstIndex != -1) { - return PythonLikeTuple.fromItems( - PythonString.valueOf(value.substring(0, firstIndex)), - seperator, - PythonString.valueOf(value.substring(firstIndex + seperator.value.length()))); - } else { - return PythonLikeTuple.fromItems( - this, - EMPTY, - EMPTY); - } - } - - public PythonLikeTuple rightPartition(PythonString seperator) { - int lastIndex = value.lastIndexOf(seperator.value); - if (lastIndex != -1) { - return PythonLikeTuple.fromItems( - PythonString.valueOf(value.substring(0, lastIndex)), - seperator, - PythonString.valueOf(value.substring(lastIndex + seperator.value.length()))); - } else { - return PythonLikeTuple.fromItems( - EMPTY, - EMPTY, - this); - } - } - - public PythonString removePrefix(PythonString prefix) { - if (value.startsWith(prefix.value)) { - return new PythonString(value.substring(prefix.value.length())); - } - return this; - } - - public PythonString removeSuffix(PythonString suffix) { - if (value.endsWith(suffix.value)) { - return new PythonString(value.substring(0, value.length() - suffix.value.length())); - } - return this; - } - - public PythonString replaceAll(PythonString old, PythonString replacement) { - return PythonString.valueOf(value.replaceAll(Pattern.quote(old.value), replacement.value)); - } - - public PythonString replaceUpToCount(PythonString old, PythonString replacement, PythonInteger count) { - int countAsInt = count.value.intValueExact(); - if (countAsInt < 0) { // negative count act the same as replace all - return replaceAll(old, replacement); - } - - Matcher matcher = Pattern.compile(Pattern.quote(old.value)).matcher(value); - StringBuilder out = new StringBuilder(); - int start = 0; - while (countAsInt > 0) { - if (matcher.find()) { - out.append(value, start, matcher.start()); - out.append(replacement.value); - start = matcher.end(); - } else { - break; - } - countAsInt--; - } - out.append(value.substring(start)); - return PythonString.valueOf(out.toString()); - } - - public PythonLikeList split() { - return Arrays.stream(value.stripLeading().split("\\s+")) - .map(PythonString::valueOf) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList split(PythonNone ignored) { - return split(); - } - - public PythonLikeList split(PythonString seperator) { - return Arrays.stream(value.split(Pattern.quote(seperator.value), -1)) - .map(PythonString::valueOf) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList split(PythonString seperator, PythonInteger maxSplits) { - int maxSplitsAsInt = maxSplits.value.intValueExact(); - if (maxSplitsAsInt == -1) { - return split(seperator); - } - - return Arrays.stream(value.split(Pattern.quote(seperator.value), maxSplitsAsInt + 1)) - .map(PythonString::valueOf) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList split(PythonNone ignored, PythonInteger maxSplits) { - int maxSplitsAsInt = maxSplits.value.intValueExact(); - if (maxSplitsAsInt == -1) { - return split(); - } - - return Arrays.stream(value.stripLeading().split("\\s+", maxSplitsAsInt + 1)) - .map(PythonString::valueOf) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList rightSplit() { - return split(); - } - - public PythonLikeList rightSplit(PythonNone ignored) { - return split(); - } - - public PythonLikeList rightSplit(PythonString seperator) { - return split(seperator); - } - - public PythonLikeList rightSplit(PythonString seperator, PythonInteger maxSplits) { - int maxSplitsAsInt = maxSplits.value.intValueExact(); - if (maxSplitsAsInt == -1) { - return split(seperator); - } - - String reversedValue = new StringBuilder(value.stripTrailing()).reverse().toString(); - String reversedSeperator = new StringBuilder(seperator.value).reverse().toString(); - - return Arrays.stream(reversedValue.split(Pattern.quote(reversedSeperator), maxSplitsAsInt + 1)) - .map(reversedPart -> PythonString.valueOf(new StringBuilder(reversedPart).reverse().toString())) - .collect(Collectors.collectingAndThen(Collectors.toCollection(PythonLikeList::new), l -> { - Collections.reverse(l); - return l; - })); - } - - public PythonLikeList rightSplit(PythonNone ignored, PythonInteger maxSplits) { - int maxSplitsAsInt = maxSplits.value.intValueExact(); - if (maxSplitsAsInt == -1) { - return split(); - } - - String reversedValue = new StringBuilder(value.stripTrailing()).reverse().toString(); - - return Arrays.stream(reversedValue.split("\\s+", maxSplitsAsInt + 1)) - .map(reversedPart -> PythonString.valueOf(new StringBuilder(reversedPart).reverse().toString())) - .collect(Collectors.collectingAndThen(Collectors.toCollection(PythonLikeList::new), l -> { - Collections.reverse(l); - return l; - })); - } - - public PythonLikeList splitLines() { - if (value.isEmpty()) { - return new PythonLikeList<>(); - } - return Arrays.stream(value.split("\\R")) - .map(PythonString::valueOf) - .collect(Collectors.toCollection(PythonLikeList::new)); - } - - public PythonLikeList splitLines(PythonBoolean keepEnds) { - if (!keepEnds.getBooleanValue()) { - return splitLines(); - } - - // Use lookahead so the newline is included in the result - return Arrays.stream(value.split("(?<=\\R)")) - .map(PythonString::valueOf) - .collect(Collectors.collectingAndThen(Collectors.toCollection(PythonLikeList::new), l -> { - int i; - for (i = 0; i < l.size() - 1; i++) { - // lookbehind cause it to split \r\n into two seperate - // lines; need to combine consecutive lines where the first ends with \r - // and the second starts with \n to get expected behavior - if (l.get(i).value.endsWith("\r") && l.get(i + 1).value.startsWith("\n")) { - l.set(i, PythonString.valueOf(l.get(i).value + l.remove(i + 1).value)); - i--; - } - } - - // Remove trailing empty string - // i = l.size() - 1 - if (!l.isEmpty() && l.get(i).value.isEmpty()) { - l.remove(i); - } - - return l; - })); - } - - public PythonString translate(PythonLikeObject object) { - return PythonString.valueOf(value.codePoints() - .flatMap(codePoint -> { - try { - PythonLikeObject translated = - BinaryDunderBuiltin.GET_ITEM.invoke(object, PythonInteger.valueOf(codePoint)); - if (translated == PythonNone.INSTANCE) { - return IntStream.empty(); - } - - if (translated instanceof PythonInteger) { - return IntStream.of(((PythonInteger) translated).value.intValueExact()); - } - - if (translated instanceof PythonString) { - return ((PythonString) translated).value.codePoints(); - } - - throw new TypeError("character mapping must return integer, None or str"); - } catch (LookupError e) { - return IntStream.of(codePoint); - } - }).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()); - } - - public PythonString zfill(PythonInteger width) { - int widthAsInt = width.value.intValueExact(); - if (widthAsInt <= value.length()) { - return this; - } - - int leftPadding = widthAsInt - value.length(); - if (!value.isEmpty() && (value.charAt(0) == '+' || value.charAt(0) == '-')) { - return PythonString.valueOf(value.charAt(0) + "0".repeat(leftPadding) + value.substring(1)); - } else { - return PythonString.valueOf("0".repeat(leftPadding) + value); - } - } - - @Override - public int compareTo(PythonString pythonString) { - return value.compareTo(pythonString.value); - } - - @Override - public PythonString $method$__format__(PythonLikeObject specObject) { - PythonString spec; - if (specObject == PythonNone.INSTANCE) { - return formatSelf(); - } else if (specObject instanceof PythonString) { - spec = (PythonString) specObject; - } else { - throw new TypeError("__format__ argument 0 has incorrect type (expecting str or None)"); - } - return formatSelf(spec); - } - - public PythonString repr() { - return PythonString.valueOf("'" + value.codePoints() - .flatMap(character -> { - if (character == '\\') { - return IntStream.of('\\', '\\'); - } - if (isCharacterPrintable(character)) { - return IntStream.of(character); - } else { - switch (character) { - case '\r': - return IntStream.of('\\', 'r'); - case '\n': - return IntStream.of('\\', 'n'); - case '\t': - return IntStream.of('\\', 't'); - default: { - if (character < 0xFFFF) { - return String.format("u%04x", character).codePoints(); - } else { - return String.format("U%08x", character).codePoints(); - } - - } - } - } - }) - .collect(StringBuilder::new, - StringBuilder::appendCodePoint, StringBuilder::append) - .toString() + "'"); - } - - public PythonString asString() { - return this; - } - - @Override - public PythonString $method$__str__() { - return this; - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object o) { - if (o instanceof String) { - return value.equals(o); - } else if (o instanceof PythonString) { - return ((PythonString) o).value.equals(value); - } else { - return false; - } - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSuperObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSuperObject.java deleted file mode 100644 index fe787c58..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSuperObject.java +++ /dev/null @@ -1,82 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.builtins.TernaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; - -public class PythonSuperObject extends AbstractPythonLikeObject { - public static final PythonLikeType $TYPE = BuiltinTypes.SUPER_TYPE; - - public final PythonLikeType previousType; - public final PythonLikeObject instance; - - public PythonSuperObject(PythonLikeType previousType) { - super(BuiltinTypes.SUPER_TYPE); - this.previousType = previousType; - this.instance = null; - } - - public PythonSuperObject(PythonLikeType previousType, PythonLikeObject instance) { - super(BuiltinTypes.SUPER_TYPE); - this.previousType = previousType; - this.instance = instance; - } - - @Override - public PythonLikeObject $method$__getattribute__(PythonString pythonName) { - List mro; - if (instance != null) { - if (instance instanceof PythonLikeType) { - mro = ((PythonLikeType) instance).MRO; - } else { - mro = instance.$getType().MRO; - } - } else { - mro = previousType.MRO; - } - - String name = pythonName.value; - for (int currentIndex = mro.indexOf(previousType) + 1; currentIndex < mro.size(); currentIndex++) { - PythonLikeType candidate = mro.get(currentIndex); - - PythonLikeObject typeResult = candidate.$getAttributeOrNull(name); - if (typeResult != null) { - - if (typeResult instanceof PythonLikeFunction && !(typeResult instanceof PythonLikeType)) { - try { - Object methodInstance = - candidate.getJavaClass().getField(PythonClassTranslator.getJavaMethodName(name)).get(null); - typeResult = new GeneratedFunctionMethodReference(methodInstance, - methodInstance.getClass().getDeclaredMethods()[0], - Map.of(), - typeResult.$getType()); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { - // ignore - } - } - - PythonLikeObject maybeDescriptor = typeResult.$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - if (maybeDescriptor == null) { - maybeDescriptor = typeResult.$getType().$getAttributeOrNull(PythonTernaryOperator.GET.dunderMethod); - } - - if (maybeDescriptor != null) { - if (!(maybeDescriptor instanceof PythonLikeFunction)) { - throw new UnsupportedOperationException("'" + maybeDescriptor.$getType() + "' is not callable"); - } - return TernaryDunderBuiltin.GET_DESCRIPTOR.invoke(typeResult, - (instance != null) ? instance : PythonNone.INSTANCE, - candidate); - } - return typeResult; - } - } - throw new AttributeError("Cannot find attribute " + pythonName.repr() + " in super(" + previousType + ")."); - } - -} \ No newline at end of file diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java deleted file mode 100644 index 32b05efb..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java +++ /dev/null @@ -1,59 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.util.Iterator; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; - -public class DelegatePythonIterator extends AbstractPythonLikeObject implements PythonIterator { - static { - PythonOverloadImplementor.deferDispatchesFor(DelegatePythonIterator::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.NEXT, PythonIterator.class.getMethod("nextPythonItem")); - BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonIterator.class.getMethod("getIterator")); - return BuiltinTypes.ITERATOR_TYPE; - } - - private final Iterator delegate; - - public DelegatePythonIterator(Iterator delegate) { - super(BuiltinTypes.ITERATOR_TYPE); - this.delegate = delegate; - } - - public DelegatePythonIterator(PythonLikeType type) { - super(type); - this.delegate = this; - } - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public T next() { - if (!delegate.hasNext()) { - throw new StopIteration(); - } - return delegate.next(); - } - - public PythonLikeObject nextPythonItem() { - if (!delegate.hasNext()) { - throw new StopIteration(); - } - return (PythonLikeObject) delegate.next(); - } - - public DelegatePythonIterator getIterator() { - return this; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java deleted file mode 100644 index 3921cdaa..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.util.Iterator; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public interface PythonIterator extends PythonLikeObject, Iterator { - PythonLikeObject nextPythonItem(); - - PythonIterator getIterator(); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java deleted file mode 100644 index 5377f0e2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java +++ /dev/null @@ -1,414 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Stream; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.view.DictItemView; -import ai.timefold.jpyinterpreter.types.collections.view.DictKeyView; -import ai.timefold.jpyinterpreter.types.collections.view.DictValueView; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.KeyError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.JavaStringMapMirror; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningCloneable; - -import org.apache.commons.collections4.OrderedMap; -import org.apache.commons.collections4.map.LinkedMap; - -public class PythonLikeDict extends AbstractPythonLikeObject - implements Map, - PlanningCloneable>, - Iterable { - public final OrderedMap delegate; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeDict::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.DICT_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - PythonLikeDict out = new PythonLikeDict(); - if (positionalArguments.isEmpty()) { - out.putAll(namedArguments); - return out; - } else if (positionalArguments.size() == 1) { - PythonLikeObject from = positionalArguments.get(0); - if (from instanceof Map) { - out.putAll((Map) from); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(from); - while (iterator.hasNext()) { - List item = (List) iterator.next(); - out.put((PythonLikeObject) item.get(0), (PythonLikeObject) item.get(1)); - } - } - out.putAll(namedArguments); - return out; - } else { - throw new ValueError("dict takes 0 or 1 positional arguments, got " + positionalArguments.size()); - } - })); - - // Unary operators - BuiltinTypes.DICT_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonLikeDict.class.getMethod("getKeyIterator")); - BuiltinTypes.DICT_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonLikeDict.class.getMethod("getSize")); - BuiltinTypes.DICT_TYPE.addUnaryMethod(PythonUnaryOperator.REVERSED, PythonLikeDict.class.getMethod("reversed")); - - // Binary operators - BuiltinTypes.DICT_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonLikeDict.class.getMethod("getItemOrError", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ITEM, - PythonLikeDict.class.getMethod("removeItemOrError", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonLikeDict.class.getMethod("isKeyInDict", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - PythonLikeDict.class.getMethod("binaryOr", PythonLikeDict.class)); - BuiltinTypes.DICT_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_OR, - PythonLikeDict.class.getMethod("binaryInplaceOr", PythonLikeDict.class)); - - // Ternary operators - BuiltinTypes.DICT_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ITEM, - PythonLikeDict.class.getMethod("setItem", PythonLikeObject.class, PythonLikeObject.class)); - - // Other - BuiltinTypes.DICT_TYPE.addMethod("clear", PythonLikeDict.class.getMethod("clearDict")); - BuiltinTypes.DICT_TYPE.addMethod("copy", PythonLikeDict.class.getMethod("copy")); - BuiltinTypes.DICT_TYPE.addMethod("get", PythonLikeDict.class.getMethod("getItemOrNone", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("get", - PythonLikeDict.class.getMethod("getItemOrDefault", PythonLikeObject.class, PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("items", PythonLikeDict.class.getMethod("getItems")); - BuiltinTypes.DICT_TYPE.addMethod("keys", PythonLikeDict.class.getMethod("getKeyView")); - BuiltinTypes.DICT_TYPE.addMethod("pop", PythonLikeDict.class.getMethod("popItemOrError", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("pop", - PythonLikeDict.class.getMethod("popItemOrDefault", PythonLikeObject.class, PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("popitem", PythonLikeDict.class.getMethod("popLast")); - BuiltinTypes.DICT_TYPE.addMethod("setdefault", PythonLikeDict.class.getMethod("setIfAbsent", PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("setdefault", - PythonLikeDict.class.getMethod("setIfAbsent", PythonLikeObject.class, PythonLikeObject.class)); - BuiltinTypes.DICT_TYPE.addMethod("update", PythonLikeDict.class.getMethod("update", PythonLikeDict.class)); - BuiltinTypes.DICT_TYPE.addMethod("update", PythonLikeDict.class.getMethod("update", PythonLikeObject.class)); - // TODO: Keyword update - BuiltinTypes.DICT_TYPE.addMethod("values", PythonLikeDict.class.getMethod("getValueView")); - - return BuiltinTypes.DICT_TYPE; - } - - public PythonLikeDict() { - super(BuiltinTypes.DICT_TYPE); - delegate = new LinkedMap<>(); - } - - public PythonLikeDict(int size) { - super(BuiltinTypes.DICT_TYPE); - delegate = new LinkedMap<>(size); - } - - public PythonLikeDict(OrderedMap source) { - super(BuiltinTypes.DICT_TYPE); - delegate = source; - } - - @Override - public PythonLikeDict createNewInstance() { - return new PythonLikeDict<>(); - } - - public static PythonLikeDict mirror(Map globals) { - return new PythonLikeDict<>(new JavaStringMapMirror(globals)); - } - - public PythonLikeTuple toFlattenKeyValueTuple() { - return PythonLikeTuple.fromItems((PythonLikeObject[]) delegate.entrySet().stream() - .flatMap(entry -> { - var mapEntry = (Map.Entry) entry; - return Stream.of(mapEntry.getKey(), mapEntry.getValue()); - }) - .toArray(PythonLikeObject[]::new)); - } - - public PythonLikeDict copy() { - return new PythonLikeDict<>(new LinkedMap<>(delegate)); - } - - public PythonLikeDict concatToNew(PythonLikeDict other) { - PythonLikeDict result = new PythonLikeDict(); - result.putAll(delegate); - result.putAll(other); - return result; - } - - public PythonLikeDict concatToSelf(PythonLikeDict other) { - this.putAll(other); - return this; - } - - public PythonInteger getSize() { - return PythonInteger.valueOf(delegate.size()); - } - - public DelegatePythonIterator getKeyIterator() { - return new DelegatePythonIterator<>(delegate.keySet().iterator()); - } - - public PythonLikeObject getItemOrError(PythonLikeObject key) { - PythonLikeObject out = (PythonLikeObject) delegate.get(key); - if (out == null) { - throw new KeyError(key.toString()); - } - return out; - } - - public PythonLikeObject getItemOrNone(PythonLikeObject key) { - var out = (PythonLikeObject) delegate.get(key); - if (out == null) { - return PythonNone.INSTANCE; - } - return out; - } - - public PythonLikeObject getItemOrDefault(PythonLikeObject key, PythonLikeObject defaultValue) { - PythonLikeObject out = (PythonLikeObject) delegate.get(key); - if (out == null) { - return defaultValue; - } - return out; - } - - public DictItemView getItems() { - return new DictItemView(this); - } - - public DictKeyView getKeyView() { - return new DictKeyView(this); - } - - public PythonNone setItem(PythonLikeObject key, PythonLikeObject value) { - delegate.put(key, value); - return PythonNone.INSTANCE; - } - - public PythonNone removeItemOrError(PythonLikeObject key) { - if (delegate.remove(key) == null) { - throw new KeyError(key.toString()); - } - return PythonNone.INSTANCE; - } - - public PythonBoolean isKeyInDict(PythonLikeObject key) { - return PythonBoolean.valueOf(delegate.containsKey(key)); - } - - public PythonNone clearDict() { - delegate.clear(); - return PythonNone.INSTANCE; - } - - public PythonLikeObject popItemOrError(PythonLikeObject key) { - var out = (PythonLikeObject) delegate.remove(key); - if (out == null) { - throw new KeyError(key.toString()); - } - return out; - } - - public PythonLikeObject popItemOrDefault(PythonLikeObject key, PythonLikeObject defaultValue) { - var out = (PythonLikeObject) delegate.remove(key); - if (out == null) { - return defaultValue; - } - return out; - } - - public PythonLikeObject popLast() { - if (delegate.isEmpty()) { - throw new KeyError("popitem(): dictionary is empty"); - } - - var lastKey = (K) delegate.lastKey(); - return PythonLikeTuple.fromItems(lastKey, (V) delegate.remove(lastKey)); - } - - public DelegatePythonIterator reversed() { - if (delegate.isEmpty()) { - return new DelegatePythonIterator<>(Collections.emptyIterator()); - } - - final var lastKey = (PythonLikeObject) delegate.lastKey(); - return new DelegatePythonIterator<>(new Iterator<>() { - PythonLikeObject current = lastKey; - - @Override - public boolean hasNext() { - return current != null; - } - - @Override - public PythonLikeObject next() { - var out = current; - current = (PythonLikeObject) delegate.previousKey(current); - return out; - } - }); - } - - public PythonLikeObject setIfAbsent(PythonLikeObject key) { - var value = (PythonLikeObject) delegate.get(key); - if (value != null) { - return value; - } - delegate.put(key, PythonNone.INSTANCE); - return PythonNone.INSTANCE; - } - - public PythonLikeObject setIfAbsent(PythonLikeObject key, PythonLikeObject defaultValue) { - var value = (V) delegate.get(key); - if (value != null) { - return value; - } - delegate.put(key, defaultValue); - return defaultValue; - } - - public PythonNone update(PythonLikeDict other) { - delegate.putAll(other.delegate); - return PythonNone.INSTANCE; - } - - public PythonNone update(PythonLikeObject iterable) { - Iterator> iterator = - (Iterator>) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - - while (iterator.hasNext()) { - List keyValuePair = iterator.next(); - delegate.put(keyValuePair.get(0), keyValuePair.get(1)); - } - - return PythonNone.INSTANCE; - } - - public DictValueView getValueView() { - return new DictValueView(this); - } - - public PythonLikeDict binaryOr(PythonLikeDict other) { - var out = new PythonLikeDict(); - out.delegate.putAll(delegate); - out.delegate.putAll(other.delegate); - return out; - } - - public PythonLikeDict binaryInplaceOr(PythonLikeDict other) { - delegate.putAll(other.delegate); - return this; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return delegate.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return delegate.containsValue(value); - } - - @Override - public V get(Object key) { - return (V) delegate.get(key); - } - - @Override - public PythonLikeObject put(PythonLikeObject key, PythonLikeObject value) { - return (PythonLikeObject) delegate.put(key, value); - } - - @Override - public V remove(Object o) { - return (V) delegate.remove(o); - } - - @Override - public void putAll(Map map) { - delegate.putAll(map); - } - - @Override - public void clear() { - delegate.clear(); - } - - @Override - public Set keySet() { - return delegate.keySet(); - } - - @Override - public Collection values() { - return delegate.values(); - } - - @Override - public Set> entrySet() { - return delegate.entrySet(); - } - - @Override - public Iterator iterator() { - return delegate.keySet().iterator(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Map) { - Map other = (Map) o; - if (other.size() != this.size()) { - return false; - } - return this.entrySet().containsAll(other.entrySet()); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return delegate.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java deleted file mode 100644 index fee99d67..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java +++ /dev/null @@ -1,351 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Objects; -import java.util.Set; -import java.util.function.Predicate; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -// issubclass(set, frozenset) and issubclass(frozenset, set) are both False in Python -public class PythonLikeFrozenSet extends AbstractPythonLikeObject implements Set { - public final Set delegate; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeFrozenSet::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.FROZEN_SET_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.size() == 0) { - return new PythonLikeFrozenSet(); - } else if (positionalArguments.size() == 1) { - return new PythonLikeFrozenSet(positionalArguments.get(0)); - } else { - throw new ValueError("frozenset expects 0 or 1 arguments, got " + positionalArguments.size()); - } - }); - - // Unary - BuiltinTypes.FROZEN_SET_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, - PythonLikeFrozenSet.class.getMethod("getLength")); - BuiltinTypes.FROZEN_SET_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - PythonLikeFrozenSet.class.getMethod("getIterator")); - - // Binary - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonLikeFrozenSet.class.getMethod("containsItem", PythonLikeObject.class)); - - // Other - BuiltinTypes.FROZEN_SET_TYPE.addMethod("isdisjoint", - PythonLikeFrozenSet.class.getMethod("isDisjoint", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("isdisjoint", - PythonLikeFrozenSet.class.getMethod("isDisjoint", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("issubset", - PythonLikeFrozenSet.class.getMethod("isSubset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonLikeFrozenSet.class.getMethod("isSubset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonLikeFrozenSet.class.getMethod("isStrictSubset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("issubset", - PythonLikeFrozenSet.class.getMethod("isSubset", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonLikeFrozenSet.class.getMethod("isSubset", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonLikeFrozenSet.class.getMethod("isStrictSubset", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("issuperset", - PythonLikeFrozenSet.class.getMethod("isSuperset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonLikeFrozenSet.class.getMethod("isSuperset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonLikeFrozenSet.class.getMethod("isStrictSuperset", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("issuperset", - PythonLikeFrozenSet.class.getMethod("isSuperset", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonLikeFrozenSet.class.getMethod("isSuperset", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonLikeFrozenSet.class.getMethod("isStrictSuperset", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("union", PythonLikeFrozenSet.class.getMethod("union", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("union", - PythonLikeFrozenSet.class.getMethod("union", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - PythonLikeFrozenSet.class.getMethod("union", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - PythonLikeFrozenSet.class.getMethod("union", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("intersection", - PythonLikeFrozenSet.class.getMethod("intersection", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("intersection", - PythonLikeFrozenSet.class.getMethod("intersection", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - PythonLikeFrozenSet.class.getMethod("intersection", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - PythonLikeFrozenSet.class.getMethod("intersection", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("difference", - PythonLikeFrozenSet.class.getMethod("difference", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("difference", - PythonLikeFrozenSet.class.getMethod("difference", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonLikeFrozenSet.class.getMethod("difference", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonLikeFrozenSet.class.getMethod("difference", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("symmetric_difference", - PythonLikeFrozenSet.class.getMethod("symmetricDifference", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addMethod("symmetric_difference", - PythonLikeFrozenSet.class.getMethod("symmetricDifference", PythonLikeFrozenSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - PythonLikeFrozenSet.class.getMethod("symmetricDifference", PythonLikeSet.class)); - BuiltinTypes.FROZEN_SET_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - PythonLikeFrozenSet.class.getMethod("symmetricDifference", PythonLikeFrozenSet.class)); - - BuiltinTypes.FROZEN_SET_TYPE.addMethod("copy", PythonLikeFrozenSet.class.getMethod("copy")); - - return BuiltinTypes.FROZEN_SET_TYPE; - } - - private static UnsupportedOperationException modificationError() { - return new UnsupportedOperationException("frozenset cannot be modified once created"); - } - - public PythonLikeFrozenSet() { - super(BuiltinTypes.FROZEN_SET_TYPE); - delegate = new HashSet<>(); - } - - public PythonLikeFrozenSet(PythonLikeObject iterable) { - super(BuiltinTypes.FROZEN_SET_TYPE); - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - delegate = new HashSet<>(); - iterator.forEachRemaining(delegate::add); - } - - // Required for bytecode generation - @SuppressWarnings("unused") - public void reverseAdd(PythonLikeObject item) { - delegate.add(item); - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(delegate.size()); - } - - public PythonBoolean containsItem(PythonLikeObject query) { - return PythonBoolean.valueOf(delegate.contains(query)); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator(delegate.iterator()); - } - - public PythonBoolean isDisjoint(PythonLikeSet other) { - return PythonBoolean.valueOf(Collections.disjoint(delegate, other.delegate)); - } - - public PythonBoolean isDisjoint(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(Collections.disjoint(delegate, other.delegate)); - } - - public PythonBoolean isSubset(PythonLikeSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate)); - } - - public PythonBoolean isSubset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate)); - } - - public PythonBoolean isStrictSubset(PythonLikeSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate) && !delegate.containsAll(other.delegate)); - } - - public PythonBoolean isStrictSubset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate) && !delegate.containsAll(other.delegate)); - } - - public PythonBoolean isSuperset(PythonLikeSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate)); - } - - public PythonBoolean isSuperset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate)); - } - - public PythonBoolean isStrictSuperset(PythonLikeSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate) && !other.delegate.containsAll(delegate)); - } - - public PythonBoolean isStrictSuperset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate) && !other.delegate.containsAll(delegate)); - } - - public PythonLikeFrozenSet union(PythonLikeSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.addAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet union(PythonLikeFrozenSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.addAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet intersection(PythonLikeSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.retainAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet intersection(PythonLikeFrozenSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.retainAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet difference(PythonLikeSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.removeAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet difference(PythonLikeFrozenSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - out.delegate.removeAll(other.delegate); - return out; - } - - public PythonLikeFrozenSet symmetricDifference(PythonLikeSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - other.delegate.stream() // for each item in other - .filter(Predicate.not(out.delegate::add)) // 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; - } - - public PythonLikeFrozenSet symmetricDifference(PythonLikeFrozenSet other) { - PythonLikeFrozenSet out = new PythonLikeFrozenSet(); - out.delegate.addAll(delegate); - other.delegate.stream() // for each item in other - .filter(Predicate.not(out.delegate::add)) // 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; - } - - public PythonLikeFrozenSet copy() { - return this; // frozenset are immutable, thus copy return self to duplicate behavior in Python - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator iterator() { - return delegate.iterator(); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public T[] toArray(T[] ts) { - return delegate.toArray(ts); - } - - @Override - public boolean containsAll(Collection collection) { - return delegate.containsAll(collection); - } - - @Override - public boolean add(PythonLikeObject pythonLikeObject) { - throw modificationError(); - } - - @Override - public boolean remove(Object o) { - throw modificationError(); - } - - @Override - public boolean addAll(Collection collection) { - throw modificationError(); - } - - @Override - public boolean removeAll(Collection collection) { - throw modificationError(); - } - - @Override - public boolean retainAll(Collection collection) { - throw modificationError(); - } - - @Override - public void clear() { - throw modificationError(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Set) { - Set other = (Set) o; - if (other.size() != this.size()) { - return false; - } - return containsAll(other) && other.containsAll(this); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java deleted file mode 100644 index a5b4c321..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java +++ /dev/null @@ -1,608 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Objects; -import java.util.RandomAccess; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonTernaryOperator; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonSlice; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningCloneable; - -public class PythonLikeList extends AbstractPythonLikeObject implements List, - PlanningCloneable>, - RandomAccess { - final List delegate; - private int remainderToAdd; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeList::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.LIST_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.size() == 0) { - return new PythonLikeList(); - } else if (positionalArguments.size() == 1) { - PythonLikeList out = new PythonLikeList(); - out.extend(positionalArguments.get(0)); - return out; - } else { - throw new ValueError("list expects 0 or 1 arguments, got " + positionalArguments.size()); - } - }); - - // Unary methods - BuiltinTypes.LIST_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonLikeList.class.getMethod("length")); - BuiltinTypes.LIST_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonLikeList.class.getMethod("getIterator")); - BuiltinTypes.LIST_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - PythonLikeList.class.getMethod("getRepresentation")); - - // Binary methods - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonLikeList.class.getMethod("concatToNew", PythonLikeList.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_ADD, - PythonLikeList.class.getMethod("concatToSelf", PythonLikeList.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonLikeList.class.getMethod("multiplyToNew", PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_MULTIPLY, - PythonLikeList.class.getMethod("multiplyToSelf", PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonLikeList.class.getMethod("getItem", PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonLikeList.class.getMethod("getSlice", PythonSlice.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ITEM, - PythonLikeList.class.getMethod("deleteItem", PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.DELETE_ITEM, - PythonLikeList.class.getMethod("deleteSlice", PythonSlice.class)); - BuiltinTypes.LIST_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonLikeList.class.getMethod("containsItem", PythonLikeObject.class)); - - // Ternary methods - BuiltinTypes.LIST_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ITEM, - PythonLikeList.class.getMethod("setItem", PythonInteger.class, PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addTernaryMethod(PythonTernaryOperator.SET_ITEM, - PythonLikeList.class.getMethod("setSlice", PythonSlice.class, PythonLikeObject.class)); - - // Other - BuiltinTypes.LIST_TYPE.addMethod("append", PythonLikeList.class.getMethod("append", PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("extend", PythonLikeList.class.getMethod("extend", PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("insert", - PythonLikeList.class.getMethod("insert", PythonInteger.class, PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("remove", PythonLikeList.class.getMethod("remove", PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("clear", PythonLikeList.class.getMethod("clearList")); - BuiltinTypes.LIST_TYPE.addMethod("copy", PythonLikeList.class.getMethod("copy")); - BuiltinTypes.LIST_TYPE.addMethod("count", PythonLikeList.class.getMethod("count", PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("index", PythonLikeList.class.getMethod("index", PythonLikeObject.class)); - BuiltinTypes.LIST_TYPE.addMethod("index", - PythonLikeList.class.getMethod("index", PythonLikeObject.class, PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addMethod("index", - PythonLikeList.class.getMethod("index", PythonLikeObject.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addMethod("pop", PythonLikeList.class.getMethod("pop")); - BuiltinTypes.LIST_TYPE.addMethod("pop", PythonLikeList.class.getMethod("pop", PythonInteger.class)); - BuiltinTypes.LIST_TYPE.addMethod("reverse", PythonLikeList.class.getMethod("reverse")); - BuiltinTypes.LIST_TYPE.addMethod("sort", PythonLikeList.class.getMethod("sort")); - - return BuiltinTypes.LIST_TYPE; - } - - public PythonLikeList() { - super(BuiltinTypes.LIST_TYPE); - delegate = new ArrayList<>(); - remainderToAdd = 0; - } - - public PythonLikeList(int size) { - super(BuiltinTypes.LIST_TYPE); - delegate = new ArrayList<>(size); - remainderToAdd = size; - for (int i = 0; i < size; i++) { - delegate.add(null); - } - } - - public PythonLikeList(List delegate) { - super(BuiltinTypes.LIST_TYPE); - this.delegate = delegate; - remainderToAdd = 0; - } - - @Override - public PythonLikeList createNewInstance() { - return new PythonLikeList<>(); - } - - // Required for bytecode generation - @SuppressWarnings("unused") - public void reverseAdd(PythonLikeObject object) { - delegate.set(remainderToAdd - 1, object); - remainderToAdd--; - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator(delegate.iterator()); - } - - public PythonLikeList copy() { - PythonLikeList copy = new PythonLikeList(); - copy.addAll(delegate); - return copy; - } - - public PythonLikeList concatToNew(PythonLikeList other) { - PythonLikeList result = new PythonLikeList(); - result.addAll(delegate); - result.addAll(other); - return result; - } - - public PythonLikeList concatToSelf(PythonLikeList other) { - this.addAll(other); - return this; - } - - public PythonLikeList multiplyToNew(PythonInteger times) { - if (times.value.compareTo(BigInteger.ZERO) <= 0) { - return new PythonLikeList(); - } - - PythonLikeList result = new PythonLikeList(); - int timesAsInt = times.value.intValueExact(); - - for (int i = 0; i < timesAsInt; i++) { - result.addAll(delegate); - } - - return result; - } - - public PythonLikeList multiplyToSelf(PythonInteger times) { - if (times.value.compareTo(BigInteger.ZERO) <= 0) { - delegate.clear(); - return this; - } - List copy = new ArrayList<>(delegate); - int timesAsInt = times.value.intValueExact() - 1; - - for (int i = 0; i < timesAsInt; i++) { - delegate.addAll(copy); - } - - return this; - } - - public PythonInteger length() { - return PythonInteger.valueOf(delegate.size()); - } - - public PythonInteger index(PythonLikeObject item) { - int result = delegate.indexOf(item); - - if (result != -1) { - return PythonInteger.valueOf(result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public PythonInteger index(PythonLikeObject item, PythonInteger start) { - int startAsInt = start.value.intValueExact(); - if (startAsInt < 0) { - startAsInt = delegate.size() + startAsInt; - } - - List searchList = delegate.subList(startAsInt, delegate.size()); - int result = searchList.indexOf(item); - if (result != -1) { - return PythonInteger.valueOf(startAsInt + result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public PythonInteger index(PythonLikeObject item, PythonInteger start, PythonInteger end) { - int startAsInt = start.value.intValueExact(); - int endAsInt = end.value.intValueExact(); - - if (startAsInt < 0) { - startAsInt = delegate.size() + startAsInt; - } - - if (endAsInt < 0) { - endAsInt = delegate.size() + endAsInt; - } - - List searchList = delegate.subList(startAsInt, endAsInt); - int result = searchList.indexOf(item); - if (result != -1) { - return PythonInteger.valueOf(startAsInt + result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public PythonLikeObject getItem(PythonInteger index) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - indexAsInt = delegate.size() + index.value.intValueExact(); - } - - if (indexAsInt < 0 || indexAsInt >= delegate.size()) { - throw new IndexError("list index out of range"); - } - - return (PythonLikeObject) delegate.get(indexAsInt); - } - - public PythonLikeList getSlice(PythonSlice slice) { - int length = delegate.size(); - - PythonLikeList out = new PythonLikeList(); - - slice.iterate(length, (i, processed) -> { - out.add(delegate.get(i)); - }); - - return out; - } - - public PythonLikeObject setItem(PythonInteger index, PythonLikeObject value) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - indexAsInt = delegate.size() + index.value.intValueExact(); - } - - if (indexAsInt < 0 || indexAsInt >= delegate.size()) { - throw new IndexError("list index out of range"); - } - - delegate.set(indexAsInt, value); - return PythonNone.INSTANCE; - } - - public PythonLikeObject setSlice(PythonSlice slice, PythonLikeObject iterable) { - int length = delegate.size(); - int start = slice.getStartIndex(length); - int stop = slice.getStopIndex(length); - int step = slice.getStrideLength(); - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - - if (step == 1) { - delegate.subList(start, stop).clear(); - int offset = 0; - while (iterator.hasNext()) { - PythonLikeObject item = iterator.next(); - delegate.add(start + offset, item); - offset++; - } - } else { - List temp = new ArrayList<>(); - iterator.forEachRemaining(temp::add); - if (temp.size() != slice.getSliceSize(length)) { - throw new ValueError("attempt to assign sequence of size " + temp.size() + " to extended slice of size " - + slice.getSliceSize(length)); - } - - slice.iterate(length, (i, processed) -> { - delegate.set(i, temp.get(processed)); - }); - } - - return PythonNone.INSTANCE; - } - - public PythonNone deleteItem(PythonInteger index) { - if (index.value.compareTo(BigInteger.ZERO) < 0) { - delegate.remove(delegate.size() + index.value.intValueExact()); - } else { - delegate.remove(index.value.intValueExact()); - } - return PythonNone.INSTANCE; - } - - public PythonNone deleteSlice(PythonSlice slice) { - int length = delegate.size(); - int start = slice.getStartIndex(length); - int stop = slice.getStopIndex(length); - int step = slice.getStrideLength(); - - if (step == 1) { - delegate.subList(start, stop).clear(); - } else { - if (step > 0) { - // need to account for removed items because we are moving up the list, - // (as removing items shift elements down) - slice.iterate(length, (i, processed) -> { - delegate.remove(i - processed); - }); - } else { - // Since we are moving down the list (starting at the higher value), - // the elements being removed stay in the same place, so we do not need - // to account for processed elements - slice.iterate(length, (i, processed) -> { - delegate.remove(i); - }); - } - } - - return PythonNone.INSTANCE; - } - - public PythonNone remove(PythonLikeObject item) { - if (!delegate.remove(item)) { - throw new ValueError("list.remove(x): x not in list"); - } - return PythonNone.INSTANCE; - } - - public PythonNone insert(PythonInteger index, PythonLikeObject item) { - int indexAsInt = PythonSlice.asIntIndexForLength(index, delegate.size()); - - if (indexAsInt < 0) { - indexAsInt = 0; - } - - if (indexAsInt > delegate.size()) { - indexAsInt = delegate.size(); - } - - delegate.add(indexAsInt, item); - return PythonNone.INSTANCE; - } - - public PythonLikeObject pop() { - if (delegate.isEmpty()) { - throw new IndexError("pop from empty list"); - } - return (PythonLikeObject) delegate.remove(delegate.size() - 1); - } - - public PythonLikeObject pop(PythonInteger index) { - if (delegate.isEmpty()) { - throw new IndexError("pop from empty list"); - } - - int indexAsInt = index.value.intValueExact(); - if (indexAsInt < 0) { - indexAsInt = delegate.size() + indexAsInt; - } - - if (indexAsInt >= delegate.size() || indexAsInt < 0) { - throw new IndexError("pop index out of range"); - } - - return (PythonLikeObject) delegate.remove(indexAsInt); - } - - public PythonBoolean containsItem(PythonLikeObject item) { - return PythonBoolean.valueOf(delegate.contains(item)); - } - - public PythonInteger count(PythonLikeObject search) { - long count = 0; - for (Object x : delegate) { - if (Objects.equals(search, x)) { - count++; - } - } - return new PythonInteger(count); - } - - public PythonNone append(PythonLikeObject item) { - delegate.add(item); - return PythonNone.INSTANCE; - } - - public PythonNone extend(PythonLikeObject item) { - if (item instanceof Collection) { - delegate.addAll((List) item); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(item); - iterator.forEachRemaining(this::add); - } - return PythonNone.INSTANCE; - } - - public PythonNone reverse() { - Collections.reverse(delegate); - return PythonNone.INSTANCE; - } - - public PythonNone sort() { - Collections.sort(delegate); - return PythonNone.INSTANCE; - } - - public PythonNone clearList() { - delegate.clear(); - return PythonNone.INSTANCE; - } - - public PythonString getRepresentation() { - return PythonString.valueOf(toString()); - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator iterator() { - return delegate.iterator(); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - public Object[] toArray(Object[] ts) { - return delegate.toArray(ts); - } - - @Override - public boolean add(Object pythonLikeObject) { - return delegate.add(pythonLikeObject); - } - - @Override - public boolean remove(Object o) { - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection collection) { - return delegate.containsAll(collection); - } - - @Override - public boolean addAll(Collection collection) { - return delegate.addAll(collection); - } - - @Override - public boolean addAll(int i, Collection collection) { - return delegate.addAll(i, collection); - } - - @Override - public boolean removeAll(Collection collection) { - return delegate.removeAll(collection); - } - - @Override - public boolean retainAll(Collection collection) { - return delegate.retainAll(collection); - } - - @Override - public void clear() { - delegate.clear(); - } - - @Override - public T get(int i) { - return (T) delegate.get(i); - } - - @Override - public Object set(int i, Object pythonLikeObject) { - return delegate.set(i, pythonLikeObject); - } - - @Override - public void add(int i, Object pythonLikeObject) { - delegate.add(i, pythonLikeObject); - } - - @Override - public T remove(int i) { - return (T) delegate.remove(i); - } - - @Override - public int indexOf(Object o) { - return delegate.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return delegate.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return delegate.listIterator(); - } - - @Override - public ListIterator listIterator(int i) { - return delegate.listIterator(i); - } - - @Override - public List subList(int i, int i1) { - return delegate.subList(i, i1); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder(); - out.append('['); - for (int i = 0; i < delegate.size() - 1; i++) { - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke((PythonLikeObject) delegate.get(i))); - out.append(", "); - } - if (!delegate.isEmpty()) { - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke((PythonLikeObject) delegate.get(delegate.size() - 1))); - } - out.append(']'); - - return out.toString(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof List) { - List other = (List) o; - if (other.size() != delegate.size()) { - return false; - } - int itemCount = delegate.size(); - for (int i = 0; i < itemCount; i++) { - if (!Objects.equals(get(i), other.get(i))) { - return false; - } - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - public List getDelegate() { - return delegate; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java deleted file mode 100644 index 6eb168b9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java +++ /dev/null @@ -1,477 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Objects; -import java.util.Set; -import java.util.function.Predicate; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.KeyError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningCloneable; - -public class PythonLikeSet extends AbstractPythonLikeObject implements Set, - PlanningCloneable> { - public final Set delegate; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeSet::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.SET_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.size() == 0) { - return new PythonLikeSet(); - } else if (positionalArguments.size() == 1) { - PythonLikeSet out = new PythonLikeSet(); - out.update(positionalArguments.get(0)); - return out; - } else { - throw new ValueError("set expects 0 or 1 arguments, got " + positionalArguments.size()); - } - }); - - // Unary - BuiltinTypes.SET_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonLikeSet.class.getMethod("getLength")); - BuiltinTypes.SET_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonLikeSet.class.getMethod("getIterator")); - - // Binary - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonLikeSet.class.getMethod("containsItem", PythonLikeObject.class)); - - // Other - BuiltinTypes.SET_TYPE.addMethod("isdisjoint", PythonLikeSet.class.getMethod("isDisjoint", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("isdisjoint", PythonLikeSet.class.getMethod("isDisjoint", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("issubset", PythonLikeSet.class.getMethod("isSubset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonLikeSet.class.getMethod("isSubset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonLikeSet.class.getMethod("isStrictSubset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("issubset", PythonLikeSet.class.getMethod("isSubset", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonLikeSet.class.getMethod("isSubset", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonLikeSet.class.getMethod("isStrictSubset", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("issuperset", PythonLikeSet.class.getMethod("isSuperset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonLikeSet.class.getMethod("isSuperset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonLikeSet.class.getMethod("isStrictSuperset", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("issuperset", PythonLikeSet.class.getMethod("isSuperset", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonLikeSet.class.getMethod("isSuperset", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonLikeSet.class.getMethod("isStrictSuperset", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("union", PythonLikeSet.class.getMethod("union", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("union", PythonLikeSet.class.getMethod("union", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - PythonLikeSet.class.getMethod("union", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - PythonLikeSet.class.getMethod("union", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("intersection", PythonLikeSet.class.getMethod("intersection", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("intersection", - PythonLikeSet.class.getMethod("intersection", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - PythonLikeSet.class.getMethod("intersection", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - PythonLikeSet.class.getMethod("intersection", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("difference", PythonLikeSet.class.getMethod("difference", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("difference", PythonLikeSet.class.getMethod("difference", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonLikeSet.class.getMethod("difference", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonLikeSet.class.getMethod("difference", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("symmetric_difference", - PythonLikeSet.class.getMethod("symmetricDifference", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addMethod("symmetric_difference", - PythonLikeSet.class.getMethod("symmetricDifference", PythonLikeFrozenSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - PythonLikeSet.class.getMethod("symmetricDifference", PythonLikeSet.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - PythonLikeSet.class.getMethod("symmetricDifference", PythonLikeFrozenSet.class)); - - BuiltinTypes.SET_TYPE.addMethod("update", PythonLikeSet.class.getMethod("update", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_OR, - PythonLikeSet.class.getMethod("updateWithResult", PythonLikeObject.class)); - - BuiltinTypes.SET_TYPE.addMethod("intersection_update", - PythonLikeSet.class.getMethod("intersectionUpdate", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_AND, - PythonLikeSet.class.getMethod("intersectionUpdateWithResult", PythonLikeObject.class)); - - BuiltinTypes.SET_TYPE.addMethod("difference_update", - PythonLikeSet.class.getMethod("differenceUpdate", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_SUBTRACT, - PythonLikeSet.class.getMethod("differenceUpdateWithResult", PythonLikeObject.class)); - - BuiltinTypes.SET_TYPE.addMethod("symmetric_difference_update", - PythonLikeSet.class.getMethod("symmetricDifferenceUpdate", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addBinaryMethod(PythonBinaryOperator.INPLACE_XOR, - PythonLikeSet.class.getMethod("symmetricDifferenceUpdateWithResult", PythonLikeObject.class)); - - BuiltinTypes.SET_TYPE.addMethod("add", PythonLikeSet.class.getMethod("addItem", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addMethod("remove", PythonLikeSet.class.getMethod("removeOrError", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addMethod("discard", PythonLikeSet.class.getMethod("discard", PythonLikeObject.class)); - BuiltinTypes.SET_TYPE.addMethod("pop", PythonLikeSet.class.getMethod("pop")); - BuiltinTypes.SET_TYPE.addMethod("clear", PythonLikeSet.class.getMethod("clearSet")); - BuiltinTypes.SET_TYPE.addMethod("copy", PythonLikeSet.class.getMethod("copy")); - - return BuiltinTypes.SET_TYPE; - } - - public PythonLikeSet() { - super(BuiltinTypes.SET_TYPE); - delegate = new HashSet<>(); - } - - public PythonLikeSet(int size) { - super(BuiltinTypes.SET_TYPE); - delegate = new HashSet<>(size); - } - - @Override - public PythonLikeSet createNewInstance() { - return new PythonLikeSet<>(); - } - - // Required for bytecode generation - @SuppressWarnings("unused") - public void reverseAdd(T item) { - delegate.add(item); - } - - public PythonBoolean isDisjoint(PythonLikeSet other) { - return PythonBoolean.valueOf(Collections.disjoint(delegate, other.delegate)); - } - - public PythonBoolean isDisjoint(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(Collections.disjoint(delegate, other.delegate)); - } - - public PythonBoolean isSubset(PythonLikeSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate)); - } - - public PythonBoolean isSubset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate)); - } - - public PythonBoolean isStrictSubset(PythonLikeSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate) && !delegate.containsAll(other.delegate)); - } - - public PythonBoolean isStrictSubset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(other.delegate.containsAll(delegate) && !delegate.containsAll(other.delegate)); - } - - public PythonBoolean isSuperset(PythonLikeSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate)); - } - - public PythonBoolean isSuperset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate)); - } - - public PythonBoolean isStrictSuperset(PythonLikeSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate) && !other.delegate.containsAll(delegate)); - } - - public PythonBoolean isStrictSuperset(PythonLikeFrozenSet other) { - return PythonBoolean.valueOf(delegate.containsAll(other.delegate) && !other.delegate.containsAll(delegate)); - } - - public PythonLikeSet union(PythonLikeSet other) { - var out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.addAll(other.delegate); - return out; - } - - public PythonLikeSet union(PythonLikeFrozenSet other) { - var out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.addAll(other.delegate); - return out; - } - - public PythonLikeSet intersection(PythonLikeSet other) { - var out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.retainAll(other.delegate); - return out; - } - - public PythonLikeSet intersection(PythonLikeFrozenSet other) { - var out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.retainAll(other.delegate); - return out; - } - - public PythonLikeSet difference(PythonLikeSet other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.removeAll(other.delegate); - return out; - } - - public PythonLikeSet difference(PythonLikeFrozenSet other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(delegate); - out.delegate.removeAll(other.delegate); - return out; - } - - public PythonLikeSet symmetricDifference(PythonLikeSet other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(delegate); - other.delegate.stream() // for each item in other - .filter(Predicate.not(out.delegate::add)) // 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; - } - - public PythonLikeSet symmetricDifference(PythonLikeFrozenSet other) { - var out = new PythonLikeSet(); - out.delegate.addAll(delegate); - other.delegate.stream() // for each item in other - .filter(Predicate.not(item -> out.delegate.add(item))) // 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; - } - - public PythonLikeSet updateWithResult(PythonLikeObject collection) { - if (collection instanceof Collection) { - delegate.addAll((Collection) collection); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(collection); - iterator.forEachRemaining(delegate::add); - } - return this; - } - - public PythonNone update(PythonLikeObject collection) { - updateWithResult(collection); - return PythonNone.INSTANCE; - } - - public PythonLikeSet intersectionUpdateWithResult(PythonLikeObject collection) { - if (collection instanceof Collection) { - delegate.retainAll((Collection) collection); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(collection); - Set temp = new HashSet<>(); - iterator.forEachRemaining(temp::add); - delegate.retainAll(temp); - } - return this; - } - - public PythonNone intersectionUpdate(PythonLikeObject collection) { - intersectionUpdateWithResult(collection); - return PythonNone.INSTANCE; - } - - public PythonLikeSet differenceUpdateWithResult(PythonLikeObject collection) { - if (collection instanceof Collection) { - delegate.removeAll((Collection) collection); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(collection); - iterator.forEachRemaining(delegate::remove); - } - return this; - } - - public PythonNone differenceUpdate(PythonLikeObject collection) { - differenceUpdateWithResult(collection); - return PythonNone.INSTANCE; - } - - public PythonLikeSet symmetricDifferenceUpdateWithResult(PythonLikeObject collection) { - if (collection instanceof Collection) { - Collection otherSet = (Collection) collection; - Set temp = new HashSet<>(delegate); - temp.retainAll(otherSet); - delegate.addAll(otherSet); - delegate.removeAll(temp); - } else { - Iterator iterator = (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(collection); - Set encountered = new HashSet<>(delegate); - while (iterator.hasNext()) { - PythonLikeObject item = iterator.next(); - if (encountered.contains(item)) { - continue; - } - - if (delegate.contains(item)) { - delegate.remove(item); - } else { - delegate.add(item); - } - - encountered.add(item); - } - } - return this; - } - - public PythonNone symmetricDifferenceUpdate(PythonLikeObject collection) { - symmetricDifferenceUpdateWithResult(collection); - return PythonNone.INSTANCE; - } - - public PythonNone addItem(PythonLikeObject pythonLikeObject) { - delegate.add(pythonLikeObject); - return PythonNone.INSTANCE; - } - - public PythonNone discard(PythonLikeObject object) { - delegate.remove(object); - return PythonNone.INSTANCE; - } - - public PythonNone removeOrError(PythonLikeObject object) { - if (!delegate.remove(object)) { - throw new KeyError("set (" + this + ") does not contain the specified element (" + object + ")."); - } - return PythonNone.INSTANCE; - } - - public PythonLikeObject pop() { - if (delegate.isEmpty()) { - throw new KeyError("set (" + this + ") is empty."); - } - PythonLikeObject out = (PythonLikeObject) delegate.iterator().next(); - delegate.remove(out); - return out; - } - - public PythonLikeSet copy() { - PythonLikeSet copy = new PythonLikeSet(); - copy.addAll(delegate); - return copy; - } - - public PythonNone clearSet() { - delegate.clear(); - return PythonNone.INSTANCE; - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(delegate.size()); - } - - public PythonBoolean containsItem(PythonLikeObject query) { - return PythonBoolean.valueOf(delegate.contains(query)); - } - - // Java Set methods - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator iterator() { - return delegate.iterator(); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator(delegate.iterator()); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public T[] toArray(T[] ts) { - return (T[]) delegate.toArray(ts); - } - - @Override - public boolean add(PythonLikeObject pythonLikeObject) { - return delegate.add(pythonLikeObject); - } - - @Override - public boolean remove(Object o) { - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection collection) { - return delegate.containsAll(collection); - } - - @Override - public boolean addAll(Collection collection) { - return delegate.addAll(collection); - } - - @Override - public boolean removeAll(Collection collection) { - return delegate.removeAll(collection); - } - - @Override - public boolean retainAll(Collection collection) { - return delegate.retainAll(collection); - } - - @Override - public void clear() { - delegate.clear(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Set) { - Set other = (Set) o; - if (other.size() != this.size()) { - return false; - } - return containsAll(other) && other.containsAll(this); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java deleted file mode 100644 index 23b5b17f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java +++ /dev/null @@ -1,457 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Objects; -import java.util.RandomAccess; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonSlice; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.IndexError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningCloneable; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonLikeTuple extends AbstractPythonLikeObject implements List, - PlanningCloneable>, - PythonLikeComparable, - PlanningImmutable, - RandomAccess { - public static PythonLikeTuple EMPTY = PythonLikeTuple.fromList(Collections.emptyList()); - - final List delegate; - private int remainderToAdd; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonLikeTuple::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.TUPLE_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.isEmpty()) { - return new PythonLikeTuple(); - } else if (positionalArguments.size() == 1) { - PythonLikeTuple out = new PythonLikeTuple(); - PythonLikeObject iterable = positionalArguments.get(0); - if (iterable instanceof Collection) { - out.delegate.addAll((Collection) iterable); - } else { - Iterator iterator = - (Iterator) UnaryDunderBuiltin.ITERATOR.invoke(iterable); - iterator.forEachRemaining(out.delegate::add); - } - return out; - } else { - throw new ValueError("tuple takes 0 or 1 arguments, got " + positionalArguments.size()); - } - }); - // Unary - BuiltinTypes.TUPLE_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, PythonLikeTuple.class.getMethod("getLength")); - BuiltinTypes.TUPLE_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonLikeTuple.class.getMethod("getIterator")); - - // Binary - BuiltinTypes.TUPLE_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonLikeTuple.class.getMethod("concatToNew", PythonLikeTuple.class)); - BuiltinTypes.TUPLE_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonLikeTuple.class.getMethod("multiplyToNew", PythonInteger.class)); - BuiltinTypes.TUPLE_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonLikeTuple.class.getMethod("getItem", PythonInteger.class)); - BuiltinTypes.TUPLE_TYPE.addBinaryMethod(PythonBinaryOperator.GET_ITEM, - PythonLikeTuple.class.getMethod("getSlice", PythonSlice.class)); - BuiltinTypes.TUPLE_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - PythonLikeTuple.class.getMethod("containsItem", PythonLikeObject.class)); - - // Comparisons - PythonLikeComparable.setup(BuiltinTypes.TUPLE_TYPE); - - // Other - BuiltinTypes.TUPLE_TYPE.addMethod("index", PythonLikeTuple.class.getMethod("index", PythonLikeObject.class)); - BuiltinTypes.TUPLE_TYPE.addMethod("index", - PythonLikeTuple.class.getMethod("index", PythonLikeObject.class, PythonInteger.class)); - BuiltinTypes.TUPLE_TYPE.addMethod("index", - PythonLikeTuple.class.getMethod("index", PythonLikeObject.class, PythonInteger.class, PythonInteger.class)); - BuiltinTypes.TUPLE_TYPE.addMethod("count", PythonLikeTuple.class.getMethod("count", PythonLikeObject.class)); - - return BuiltinTypes.TUPLE_TYPE; - } - - public PythonLikeTuple() { - super(BuiltinTypes.TUPLE_TYPE); - delegate = new ArrayList<>(); - remainderToAdd = 0; - } - - public PythonLikeTuple(int size) { - super(BuiltinTypes.TUPLE_TYPE); - delegate = new ArrayList<>(size); - remainderToAdd = size; - for (int i = 0; i < size; i++) { - delegate.add(null); - } - } - - @Override - public PythonLikeTuple createNewInstance() { - return new PythonLikeTuple<>(); - } - - public static PythonLikeTuple fromItems(T... items) { - PythonLikeTuple result = new PythonLikeTuple<>(); - Collections.addAll(result, items); - return result; - } - - public static PythonLikeTuple fromList(List other) { - PythonLikeTuple result = new PythonLikeTuple<>(); - result.addAll(other); - return result; - } - - public PythonLikeTuple concatToNew(PythonLikeTuple other) { - if (delegate.isEmpty()) { - return other; - } else if (other.delegate.isEmpty()) { - return this; - } - - PythonLikeTuple result = new PythonLikeTuple(); - result.addAll(delegate); - result.addAll(other); - return result; - } - - public PythonLikeTuple multiplyToNew(PythonInteger times) { - if (times.value.compareTo(BigInteger.ZERO) <= 0) { - if (delegate.isEmpty()) { - return this; - } - return new PythonLikeTuple(); - } - - if (times.value.equals(BigInteger.ONE)) { - return this; - } - - PythonLikeTuple result = new PythonLikeTuple(); - int timesAsInt = times.value.intValueExact(); - - for (int i = 0; i < timesAsInt; i++) { - result.addAll(delegate); - } - - return result; - } - - public PythonInteger getLength() { - return PythonInteger.valueOf(delegate.size()); - } - - public PythonBoolean containsItem(PythonLikeObject item) { - return PythonBoolean.valueOf(delegate.contains(item)); - } - - public DelegatePythonIterator getIterator() { - return new DelegatePythonIterator(delegate.iterator()); - } - - public DelegatePythonIterator getReversedIterator() { - - final ListIterator listIterator = delegate.listIterator(delegate.size()); - return new DelegatePythonIterator<>(new Iterator<>() { - @Override - public boolean hasNext() { - return listIterator.hasPrevious(); - } - - @Override - public Object next() { - return listIterator.previous(); - } - }); - } - - public PythonLikeObject getItem(PythonInteger index) { - int indexAsInt = index.value.intValueExact(); - - if (indexAsInt < 0) { - indexAsInt = delegate.size() + index.value.intValueExact(); - } - - if (indexAsInt < 0 || indexAsInt >= delegate.size()) { - throw new IndexError("list index out of range"); - } - - return (PythonLikeObject) delegate.get(indexAsInt); - } - - public PythonLikeTuple getSlice(PythonSlice slice) { - int length = delegate.size(); - - PythonLikeTuple out = new PythonLikeTuple(); - - slice.iterate(length, (i, processed) -> { - out.add((PythonLikeObject) delegate.get(i)); - }); - - return out; - } - - public PythonInteger count(PythonLikeObject search) { - long count = 0; - for (var x : delegate) { - if (Objects.equals(search, x)) { - count++; - } - } - return new PythonInteger(count); - } - - public PythonInteger index(PythonLikeObject item) { - int result = delegate.indexOf(item); - - if (result != -1) { - return PythonInteger.valueOf(result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public PythonInteger index(PythonLikeObject item, PythonInteger start) { - int startAsInt = start.value.intValueExact(); - if (startAsInt < 0) { - startAsInt = delegate.size() + startAsInt; - } - - List searchList = delegate.subList(startAsInt, delegate.size()); - int result = searchList.indexOf(item); - if (result != -1) { - return PythonInteger.valueOf(startAsInt + result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public PythonInteger index(PythonLikeObject item, PythonInteger start, PythonInteger end) { - int startAsInt = start.value.intValueExact(); - int endAsInt = end.value.intValueExact(); - - if (startAsInt < 0) { - startAsInt = delegate.size() + startAsInt; - } - - if (endAsInt < 0) { - endAsInt = delegate.size() + endAsInt; - } - - List searchList = delegate.subList(startAsInt, endAsInt); - int result = searchList.indexOf(item); - if (result != -1) { - return PythonInteger.valueOf(startAsInt + result); - } else { - throw new ValueError(item + " is not in list"); - } - } - - public void reverseAdd(PythonLikeObject object) { - delegate.set(remainderToAdd - 1, object); - remainderToAdd--; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator iterator() { - return delegate.iterator(); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public T[] toArray(T[] ts) { - return (T[]) delegate.toArray(ts); - } - - @Override - public boolean add(PythonLikeObject pythonLikeObject) { - return delegate.add(pythonLikeObject); - } - - @Override - public boolean remove(Object o) { - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection collection) { - return delegate.containsAll(collection); - } - - @Override - public boolean addAll(Collection collection) { - return delegate.addAll(collection); - } - - @Override - public boolean addAll(int i, Collection collection) { - return delegate.addAll(i, collection); - } - - @Override - public boolean removeAll(Collection collection) { - return delegate.removeAll(collection); - } - - @Override - public boolean retainAll(Collection collection) { - return delegate.retainAll(collection); - } - - @Override - public void clear() { - delegate.clear(); - } - - @Override - public T get(int i) { - return (T) delegate.get(i); - } - - @Override - public T set(int i, T pythonLikeObject) { - return (T) delegate.set(i, pythonLikeObject); - } - - @Override - public void add(int i, PythonLikeObject pythonLikeObject) { - delegate.add(i, pythonLikeObject); - } - - @Override - public T remove(int i) { - return (T) delegate.remove(i); - } - - @Override - public int indexOf(Object o) { - return delegate.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return delegate.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return delegate.listIterator(); - } - - @Override - public ListIterator listIterator(int i) { - return delegate.listIterator(i); - } - - @Override - public List subList(int i, int i1) { - return delegate.subList(i, i1); - } - - @Override - public boolean equals(Object o) { - if (o instanceof List) { - List other = (List) o; - if (other.size() != this.size()) { - return false; - } - int itemCount = size(); - for (int i = 0; i < itemCount; i++) { - if (!Objects.equals(get(i), other.get(i))) { - return false; - } - } - return true; - } - return false; - } - - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public int compareTo(PythonLikeTuple other) { - int ownLength = delegate.size(); - int otherLength = other.size(); - int commonLength = Math.min(ownLength, otherLength); - for (int i = 0; i < commonLength; i++) { - Object ownItem = delegate.get(i); - Object otherItem = other.delegate.get(i); - if (ownItem instanceof Comparable ownComparable) { - if (otherItem instanceof Comparable otherComparable) { - int result = ownComparable.compareTo(otherComparable); - if (result != 0) { - return result; - } - } else { - throw new TypeError("Tuple %s does not support comparisons since item (%s) at index (%d) is not comparable." - .formatted(other, otherItem, i)); - } - } else { - throw new TypeError("Tuple %s does not support comparisons since item (%s) at index (%d) is not comparable." - .formatted(this, ownItem, i)); - } - } - return ownLength - otherLength; - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return delegate.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java deleted file mode 100644 index d628f6ef..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java +++ /dev/null @@ -1,206 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections.view; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.IteratorUtils; - -public class DictItemView extends AbstractPythonLikeObject { - public final static PythonLikeType $TYPE = BuiltinTypes.DICT_ITEM_VIEW_TYPE; - - final PythonLikeDict mapping; - final Set> entrySet; - - static { - PythonOverloadImplementor.deferDispatchesFor(DictItemView::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Unary - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, - DictItemView.class.getMethod("getItemsSize")); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - DictItemView.class.getMethod("getItemsIterator")); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REVERSED, - DictItemView.class.getMethod("getReversedItemIterator")); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - DictItemView.class.getMethod("toRepresentation")); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - DictItemView.class.getMethod("toRepresentation")); - - // Binary - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - DictItemView.class.getMethod("containsItem", PythonLikeObject.class)); - - // Set methods - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addMethod("isdisjoint", - DictItemView.class.getMethod("isDisjoint", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - DictItemView.class.getMethod("isSubset", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - DictItemView.class.getMethod("isStrictSubset", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - DictItemView.class.getMethod("isSuperset", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - DictItemView.class.getMethod("isStrictSuperset", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - DictItemView.class.getMethod("union", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - DictItemView.class.getMethod("intersection", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - DictItemView.class.getMethod("difference", DictItemView.class)); - BuiltinTypes.DICT_ITEM_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - DictItemView.class.getMethod("symmetricDifference", DictItemView.class)); - - return BuiltinTypes.DICT_ITEM_VIEW_TYPE; - } - - public DictItemView(PythonLikeDict mapping) { - super(BuiltinTypes.DICT_ITEM_VIEW_TYPE); - this.mapping = mapping; - this.entrySet = mapping.delegate.entrySet(); - $setAttribute("mapping", mapping); - } - - private List getEntriesAsTuples() { - List out = new ArrayList<>(entrySet.size()); - for (Map.Entry entry : entrySet) { - out.add(PythonLikeTuple.fromItems(entry.getKey(), entry.getValue())); - } - return out; - } - - public PythonInteger getItemsSize() { - return PythonInteger.valueOf(entrySet.size()); - } - - public DelegatePythonIterator getItemsIterator() { - return new DelegatePythonIterator<>( - IteratorUtils.iteratorMap(entrySet.iterator(), - entry -> PythonLikeTuple.fromItems(entry.getKey(), entry.getValue()))); - } - - public PythonBoolean containsItem(PythonLikeObject o) { - if (o instanceof PythonLikeTuple) { - PythonLikeTuple item = (PythonLikeTuple) o; - if (item.size() != 2) { - return PythonBoolean.FALSE; - } - Map.Entry entry = new AbstractMap.SimpleEntry<>(item.get(0), item.get(1)); - return PythonBoolean.valueOf(entrySet.contains(entry)); - } else { - return PythonBoolean.FALSE; - } - } - - public DelegatePythonIterator getReversedItemIterator() { - return new DelegatePythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), - key -> PythonLikeTuple.fromItems(key, (PythonLikeObject) mapping.delegate.get(key)))); - } - - public PythonBoolean isDisjoint(DictItemView other) { - return PythonBoolean.valueOf(Collections.disjoint(entrySet, other.entrySet)); - } - - public PythonBoolean isSubset(DictItemView other) { - return PythonBoolean.valueOf(other.entrySet.containsAll(entrySet)); - } - - public PythonBoolean isStrictSubset(DictItemView other) { - return PythonBoolean.valueOf(other.entrySet.containsAll(entrySet) && !entrySet.containsAll(other.entrySet)); - } - - public PythonBoolean isSuperset(DictItemView other) { - return PythonBoolean.valueOf(entrySet.containsAll(other.entrySet)); - } - - public PythonBoolean isStrictSuperset(DictItemView other) { - return PythonBoolean.valueOf(entrySet.containsAll(other.entrySet) && !other.entrySet.containsAll(entrySet)); - } - - public PythonLikeSet union(DictItemView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(getEntriesAsTuples()); - out.delegate.addAll(other.getEntriesAsTuples()); - return out; - } - - public PythonLikeSet intersection(DictItemView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(getEntriesAsTuples()); - out.delegate.retainAll(other.getEntriesAsTuples()); - return out; - } - - public PythonLikeSet difference(DictItemView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(getEntriesAsTuples()); - other.getEntriesAsTuples().forEach(out.delegate::remove); - return out; - } - - public PythonLikeSet symmetricDifference(DictItemView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(getEntriesAsTuples()); - other.getEntriesAsTuples().stream() // for each item in other - .filter(Predicate.not(item -> out.delegate.add(item))) // 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; - } - - @Override - public boolean equals(Object o) { - if (o instanceof DictItemView) { - DictItemView other = (DictItemView) o; - return entrySet.equals(other.entrySet); - } - return false; - } - - @Override - public int hashCode() { - return entrySet.hashCode(); - } - - public PythonString toRepresentation() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("dict_items(["); - - for (Map.Entry entry : entrySet) { - out.append("("); - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke(entry.getKey())); - out.append(", "); - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke(entry.getValue())); - out.append("), "); - } - out.delete(out.length() - 2, out.length()); - out.append("])"); - - return out.toString(); - } -} 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 deleted file mode 100644 index 3a1f86f8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java +++ /dev/null @@ -1,174 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections.view; - -import java.util.Collections; -import java.util.Set; -import java.util.function.Predicate; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class DictKeyView extends AbstractPythonLikeObject { - public final static PythonLikeType $TYPE = BuiltinTypes.DICT_KEY_VIEW_TYPE; - - final PythonLikeDict mapping; - final Set keySet; - - static { - PythonOverloadImplementor.deferDispatchesFor(DictKeyView::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Unary - BuiltinTypes.DICT_KEY_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, DictKeyView.class.getMethod("getKeysSize")); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - DictKeyView.class.getMethod("getKeysIterator")); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REVERSED, - DictKeyView.class.getMethod("getReversedKeyIterator")); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - DictKeyView.class.getMethod("toRepresentation")); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - DictKeyView.class.getMethod("toRepresentation")); - - // Binary - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - DictKeyView.class.getMethod("containsKey", PythonLikeObject.class)); - - // Set methods - BuiltinTypes.DICT_KEY_VIEW_TYPE.addMethod("isdisjoint", DictKeyView.class.getMethod("isDisjoint", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - DictKeyView.class.getMethod("isSubset", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.LESS_THAN, - DictKeyView.class.getMethod("isStrictSubset", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - DictKeyView.class.getMethod("isSuperset", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.GREATER_THAN, - DictKeyView.class.getMethod("isStrictSuperset", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.OR, - DictKeyView.class.getMethod("union", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.AND, - DictKeyView.class.getMethod("intersection", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - DictKeyView.class.getMethod("difference", DictKeyView.class)); - BuiltinTypes.DICT_KEY_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.XOR, - DictKeyView.class.getMethod("symmetricDifference", DictKeyView.class)); - - return BuiltinTypes.DICT_KEY_VIEW_TYPE; - } - - public DictKeyView(PythonLikeDict mapping) { - super(BuiltinTypes.DICT_KEY_VIEW_TYPE); - this.mapping = mapping; - this.keySet = mapping.delegate.keySet(); - $setAttribute("mapping", mapping); - } - - public PythonInteger getKeysSize() { - return PythonInteger.valueOf(keySet.size()); - } - - public DelegatePythonIterator getKeysIterator() { - return new DelegatePythonIterator<>(keySet.iterator()); - } - - public PythonBoolean containsKey(PythonLikeObject key) { - return PythonBoolean.valueOf(keySet.contains(key)); - } - - public DelegatePythonIterator getReversedKeyIterator() { - return mapping.reversed(); - } - - public PythonBoolean isDisjoint(DictKeyView other) { - return PythonBoolean.valueOf(Collections.disjoint(keySet, other.keySet)); - } - - public PythonBoolean isSubset(DictKeyView other) { - return PythonBoolean.valueOf(other.keySet.containsAll(keySet)); - } - - public PythonBoolean isStrictSubset(DictKeyView other) { - return PythonBoolean.valueOf(other.keySet.containsAll(keySet) && !keySet.containsAll(other.keySet)); - } - - public PythonBoolean isSuperset(DictKeyView other) { - return PythonBoolean.valueOf(keySet.containsAll(other.keySet)); - } - - public PythonBoolean isStrictSuperset(DictKeyView other) { - return PythonBoolean.valueOf(keySet.containsAll(other.keySet) && !other.keySet.containsAll(keySet)); - } - - public PythonLikeSet union(DictKeyView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(keySet); - out.delegate.addAll(other.keySet); - return out; - } - - public PythonLikeSet intersection(DictKeyView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(keySet); - out.delegate.retainAll(other.keySet); - return out; - } - - public PythonLikeSet difference(DictKeyView other) { - PythonLikeSet out = new PythonLikeSet(); - out.delegate.addAll(keySet); - out.delegate.removeAll(other.keySet); - return out; - } - - public PythonLikeSet symmetricDifference(DictKeyView other) { - var out = new PythonLikeSet<>(); - out.delegate.addAll(keySet); - other.keySet.stream() // for each item in other - .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; - } - - @Override - public boolean equals(Object o) { - if (o instanceof DictKeyView other) { - return keySet.equals(other.keySet); - } - return false; - } - - @Override - public int hashCode() { - return keySet.hashCode(); - } - - public PythonString toRepresentation() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("dict_keys(["); - - for (PythonLikeObject key : keySet) { - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke(key)); - out.append(", "); - } - out.delete(out.length() - 2, out.length()); - out.append("])"); - - return out.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java deleted file mode 100644 index 5a424a7d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java +++ /dev/null @@ -1,90 +0,0 @@ -package ai.timefold.jpyinterpreter.types.collections.view; - -import java.util.Collection; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.IteratorUtils; - -public class DictValueView extends AbstractPythonLikeObject { - public final static PythonLikeType $TYPE = BuiltinTypes.DICT_VALUE_VIEW_TYPE; - - final PythonLikeDict mapping; - final Collection valueCollection; - - static { - PythonOverloadImplementor.deferDispatchesFor(DictValueView::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Unary - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.LENGTH, - DictValueView.class.getMethod("getValuesSize")); - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, - DictValueView.class.getMethod("getValueIterator")); - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REVERSED, - DictValueView.class.getMethod("getReversedValueIterator")); - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - DictValueView.class.getMethod("toRepresentation")); - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - DictValueView.class.getMethod("toRepresentation")); - - // Binary - BuiltinTypes.DICT_VALUE_VIEW_TYPE.addBinaryMethod(PythonBinaryOperator.CONTAINS, - DictValueView.class.getMethod("containsValue", PythonLikeObject.class)); - - return BuiltinTypes.DICT_VALUE_VIEW_TYPE; - } - - public DictValueView(PythonLikeDict mapping) { - super(BuiltinTypes.DICT_VALUE_VIEW_TYPE); - this.mapping = mapping; - this.valueCollection = mapping.delegate.values(); - $setAttribute("mapping", mapping); - } - - public PythonInteger getValuesSize() { - return PythonInteger.valueOf(valueCollection.size()); - } - - public DelegatePythonIterator getValueIterator() { - return new DelegatePythonIterator<>(valueCollection.iterator()); - } - - public PythonBoolean containsValue(PythonLikeObject value) { - return PythonBoolean.valueOf(valueCollection.contains(value)); - } - - public DelegatePythonIterator getReversedValueIterator() { - return new DelegatePythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), mapping::get)); - } - - public PythonString toRepresentation() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("dict_values(["); - - for (PythonLikeObject value : valueCollection) { - out.append(UnaryDunderBuiltin.REPRESENTATION.invoke(value)); - out.append(", "); - } - out.delete(out.length() - 2, out.length()); - out.append("])"); - - return out.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDate.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDate.java deleted file mode 100644 index 673b75e3..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDate.java +++ /dev/null @@ -1,411 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import static ai.timefold.jpyinterpreter.types.datetime.PythonDateTime.DATE_TIME_TYPE; - -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.temporal.IsoFields; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -/** - * Python docs: date objects - */ -public class PythonDate> extends AbstractPythonLikeObject implements PythonLikeComparable, - PlanningImmutable { - static final long EPOCH_ORDINAL_OFFSET = Duration.between(LocalDateTime.of(LocalDate.of(0, 12, 31), LocalTime.MIDNIGHT), - LocalDateTime.of(LocalDate.ofEpochDay(0), LocalTime.MIDNIGHT)).toDays(); - - // Ex: Wed Jun 9 04:26:40 1993 - static final DateTimeFormatter C_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy"); - public static PythonLikeType DATE_TYPE = new PythonLikeType("date", - PythonDate.class); - - public static PythonLikeType $TYPE = DATE_TYPE; - - static { - try { - PythonLikeComparable.setup(DATE_TYPE); - registerMethods(); - - DATE_TYPE.$setAttribute("min", new PythonDate(LocalDate.of(1, 1, 1))); - DATE_TYPE.$setAttribute("max", new PythonDate(LocalDate.of(9999, 12, 31))); - DATE_TYPE.$setAttribute("resolution", new PythonTimeDelta(Duration.ofDays(1))); - - PythonOverloadImplementor.createDispatchesFor(DATE_TYPE); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private static void registerMethods() throws NoSuchMethodException { - // Constructor - DATE_TYPE.addConstructor(ArgumentSpec.forFunctionReturning("date", PythonDate.class.getName()) - .addArgument("year", PythonInteger.class.getName()) - .addArgument("month", PythonInteger.class.getName()) - .addArgument("day", PythonInteger.class.getName()) - .asStaticPythonFunctionSignature(PythonDate.class.getMethod("of", PythonInteger.class, - PythonInteger.class, PythonInteger.class))); - // Unary Operators - DATE_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - PythonDate.class.getMethod("toPythonString")); - - // Binary Operators - DATE_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonDate.class.getMethod("add_time_delta", PythonTimeDelta.class)); - DATE_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonDate.class.getMethod("subtract_time_delta", PythonTimeDelta.class)); - DATE_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonDate.class.getMethod("subtract_date", PythonDate.class)); - - // Methods - DATE_TYPE.addMethod("replace", - ArgumentSpec.forFunctionReturning("replace", PythonDate.class.getName()) - .addNullableArgument("year", PythonInteger.class.getName()) - .addNullableArgument("month", PythonInteger.class.getName()) - .addNullableArgument("day", PythonInteger.class.getName()) - .asPythonFunctionSignature(PythonDate.class.getMethod("replace", PythonInteger.class, - PythonInteger.class, PythonInteger.class))); - DATE_TYPE.addMethod("timetuple", - PythonDate.class.getMethod("timetuple")); // TODO: use time.struct_time type - - DATE_TYPE.addMethod("toordinal", - PythonDate.class.getMethod("to_ordinal")); - DATE_TYPE.addMethod("weekday", - PythonDate.class.getMethod("weekday")); - - DATE_TYPE.addMethod("isoweekday", - PythonDate.class.getMethod("iso_weekday")); - - DATE_TYPE.addMethod("isoweekday", - PythonDate.class.getMethod("iso_weekday")); - - DATE_TYPE.addMethod("isocalendar", - PythonDate.class.getMethod("iso_calendar")); - - DATE_TYPE.addMethod("isoformat", - PythonDate.class.getMethod("iso_format")); - - DATE_TYPE.addMethod("strftime", - ArgumentSpec.forFunctionReturning("strftime", PythonString.class.getName()) - .addArgument("format", PythonString.class.getName()) - .asPythonFunctionSignature(PythonDate.class.getMethod("strftime", PythonString.class))); - - DATE_TYPE.addMethod("ctime", - PythonDate.class.getMethod("ctime")); - - // Class methods - DATE_TYPE.addMethod("today", - ArgumentSpec.forFunctionReturning("today", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .asClassPythonFunctionSignature(PythonDate.class.getMethod("today", - PythonLikeType.class))); - - DATE_TYPE.addMethod("fromtimestamp", - ArgumentSpec.forFunctionReturning("fromtimestamp", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("timestamp", PythonNumber.class.getName()) - .asClassPythonFunctionSignature(PythonDate.class.getMethod("from_timestamp", - PythonLikeType.class, - PythonNumber.class))); - - DATE_TYPE.addMethod("fromordinal", - ArgumentSpec.forFunctionReturning("fromordinal", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("ordinal", PythonInteger.class.getName()) - .asClassPythonFunctionSignature(PythonDate.class.getMethod("from_ordinal", - PythonLikeType.class, PythonInteger.class))); - - DATE_TYPE.addMethod("fromisoformat", - ArgumentSpec.forFunctionReturning("fromisoformat", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("date_string", PythonString.class.getName()) - .asClassPythonFunctionSignature(PythonDate.class.getMethod("from_iso_format", - PythonLikeType.class, PythonString.class))); - - DATE_TYPE.addMethod("fromisocalendar", - ArgumentSpec.forFunctionReturning("fromisocalendar", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("year", PythonInteger.class.getName()) - .addArgument("month", PythonInteger.class.getName()) - .addArgument("day", PythonInteger.class.getName()) - .asClassPythonFunctionSignature(PythonDate.class.getMethod("from_iso_calendar", PythonLikeType.class, - PythonInteger.class, PythonInteger.class, PythonInteger.class))); - } - - final LocalDate localDate; - - public final PythonInteger year; - public final PythonInteger month; - public final PythonInteger day; - - public PythonDate(LocalDate localDate) { - this(DATE_TYPE, localDate); - } - - public PythonDate(PythonLikeType type, LocalDate localDate) { - super(type); - this.localDate = localDate; - - this.year = PythonInteger.valueOf(localDate.getYear()); - this.month = PythonInteger.valueOf(localDate.getMonthValue()); - this.day = PythonInteger.valueOf(localDate.getDayOfMonth()); - } - - public static PythonDate of(PythonInteger year, PythonInteger month, PythonInteger day) { - return of(year.value.intValueExact(), month.value.intValueExact(), day.value.intValueExact()); - } - - public static PythonDate of(int year, int month, int day) { - if (month < 1 || month > 12) { - throw new ValueError("month must be between 1 and 12"); - } - if (!YearMonth.of(year, month).isValidDay(day)) { - throw new ValueError("day must be between 1 and " + YearMonth.of(year, month).lengthOfMonth()); - } - return new PythonDate(LocalDate.of(year, month, day)); - } - - @Override - public PythonLikeObject $getAttributeOrNull(String name) { - switch (name) { - case "year": - return year; - case "month": - return month; - case "day": - return day; - default: - return super.$getAttributeOrNull(name); - } - } - - public static PythonDate today() { - return new PythonDate(LocalDate.now()); - } - - public static PythonDate today(PythonLikeType dateType) { - if (dateType == DATE_TYPE) { - return today(); - } else if (dateType == DATE_TIME_TYPE) { - return today(); - } else { - throw new TypeError("Unknown date type: " + dateType); - } - } - - public static PythonDate from_timestamp(PythonLikeType dateType, PythonNumber timestamp) { - if (dateType == DATE_TYPE) { - if (timestamp instanceof PythonInteger) { - return from_timestamp((PythonInteger) timestamp); - } else { - return from_timestamp((PythonFloat) timestamp); - } - } else if (dateType == DATE_TIME_TYPE) { - return PythonDateTime.from_timestamp(dateType, timestamp, PythonNone.INSTANCE); - } else { - throw new TypeError("Unknown date type: " + dateType); - } - } - - // Python timestamp is in the current System timezone - public static PythonDate from_timestamp(PythonInteger timestamp) { - return new PythonDate(LocalDate.ofInstant(Instant.ofEpochSecond(timestamp.getValue().longValue()), - ZoneId.systemDefault())); - } - - public static PythonDate from_timestamp(PythonFloat timestamp) { - return new PythonDate(LocalDate.ofInstant(Instant.ofEpochMilli( - Math.round(timestamp.getValue().doubleValue() * 1000)), - ZoneId.systemDefault())); - } - - public static PythonDate from_ordinal(PythonLikeType dateType, PythonInteger ordinal) { - if (dateType == DATE_TYPE) { - return from_ordinal(ordinal); - } else if (dateType == DATE_TIME_TYPE) { - return PythonDateTime.from_ordinal(ordinal); - } else { - throw new TypeError("Unknown date type: " + dateType); - } - } - - public static PythonDate from_ordinal(PythonInteger ordinal) { - return new PythonDate(LocalDate.ofEpochDay(ordinal.getValue().longValue() - EPOCH_ORDINAL_OFFSET)); - } - - public static PythonDate from_iso_format(PythonLikeType dateType, PythonString dateString) { - if (dateType == DATE_TYPE) { - return from_iso_format(dateString); - } else if (dateType == DATE_TIME_TYPE) { - return PythonDateTime.from_iso_format(dateString); - } else { - throw new TypeError("Unknown date type: " + dateType); - } - } - - public static PythonDate from_iso_format(PythonString dateString) { - return new PythonDate(LocalDate.parse(dateString.getValue())); - } - - public static PythonDate from_iso_calendar(PythonLikeType dateType, PythonInteger year, PythonInteger week, - PythonInteger day) { - if (dateType == DATE_TYPE) { - return from_iso_calendar(year, week, day); - } else if (dateType == DATE_TIME_TYPE) { - return PythonDateTime.from_iso_calendar(year, week, day); - } else { - throw new TypeError("Unknown date type: " + dateType); - } - } - - public static PythonDate from_iso_calendar(PythonInteger year, PythonInteger week, PythonInteger day) { - int isoYear = year.getValue().intValue(); - int dayInIsoYear = (week.getValue().intValue() * 7) + day.getValue().intValue(); - int correction = LocalDate.of(isoYear, 1, 4).getDayOfWeek().getValue() + 3; - int ordinalDate = dayInIsoYear - correction; - if (ordinalDate <= 0) { - int daysInYear = LocalDate.ofYearDay(isoYear - 1, 1).lengthOfYear(); - return new PythonDate(LocalDate.ofYearDay(isoYear - 1, ordinalDate + daysInYear)); - } else if (ordinalDate > LocalDate.ofYearDay(isoYear, 1).lengthOfYear()) { - int daysInYear = LocalDate.ofYearDay(isoYear, 1).lengthOfYear(); - return new PythonDate(LocalDate.ofYearDay(isoYear + 1, ordinalDate - daysInYear)); - } else { - return new PythonDate(LocalDate.ofYearDay(isoYear, ordinalDate)); - } - } - - public PythonDate add_time_delta(PythonTimeDelta summand) { - return new PythonDate(localDate.plusDays(summand.duration.toDays())); - } - - public PythonDate subtract_time_delta(PythonTimeDelta subtrahend) { - return new PythonDate(localDate.minusDays(subtrahend.duration.toDays())); - } - - public PythonTimeDelta subtract_date(PythonDate subtrahend) { - return new PythonTimeDelta(Duration.ofDays(localDate.toEpochDay() - subtrahend.localDate.toEpochDay())); - } - - public PythonDate replace(PythonInteger year, PythonInteger month, PythonInteger day) { - if (year == null) { - year = this.year; - } - - if (month == null) { - month = this.month; - } - - if (day == null) { - day = this.day; - } - - return new PythonDate(LocalDate.of(year.getValue().intValue(), - month.getValue().intValue(), - day.getValue().intValue())); - } - - public PythonLikeTuple timetuple() { - PythonInteger yday = - to_ordinal().subtract(PythonDate.of(year.value.intValueExact(), 1, 1).to_ordinal()).add(PythonInteger.ONE); - return PythonLikeTuple.fromItems(year, month, day, - PythonInteger.ZERO, PythonInteger.ZERO, PythonInteger.ZERO, - weekday(), yday, PythonInteger.valueOf(-1)); - } - - public PythonInteger to_ordinal() { - return PythonInteger.valueOf(localDate.toEpochDay() + EPOCH_ORDINAL_OFFSET); - } - - public PythonInteger weekday() { - return PythonInteger.valueOf(localDate.getDayOfWeek().getValue() - 1); - } - - public PythonInteger iso_weekday() { - return PythonInteger.valueOf(localDate.getDayOfWeek().getValue()); - } - - public PythonLikeTuple iso_calendar() { - PythonInteger year = PythonInteger.valueOf(IsoFields.WEEK_BASED_YEAR.getFrom(localDate)); - PythonInteger week = PythonInteger.valueOf(IsoFields.WEEK_OF_WEEK_BASED_YEAR.getFrom(localDate)); - PythonInteger day = PythonInteger.valueOf(localDate.getDayOfWeek().getValue()); - - return PythonLikeTuple.fromItems(year, week, day); - } - - public PythonString iso_format() { - return new PythonString(localDate.toString()); - } - - public PythonString toPythonString() { - return new PythonString(toString()); - } - - public PythonString ctime() { - return new PythonString(localDate.atStartOfDay().format(C_TIME_FORMATTER).replaceAll("(\\D)\\.", "$1")); - } - - public PythonString strftime(PythonString format) { - var formatter = PythonDateTimeFormatter.getDateTimeFormatter(format.value); - return PythonString.valueOf(formatter.format(localDate)); - } - - @Override - public int compareTo(T date) { - return localDate.compareTo(date.localDate); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonDate that = (PythonDate) o; - return localDate.equals(that.localDate); - } - - @Override - public String toString() { - return iso_format().value; - } - - @Override - public int hashCode() { - return localDate.hashCode(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } - - @Override - public PythonString $method$__str__() { - return iso_format(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTime.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTime.java deleted file mode 100644 index 9b9b7775..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTime.java +++ /dev/null @@ -1,777 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import java.time.Clock; -import java.time.DateTimeException; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.TextStyle; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalQuery; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -/** - * Python docs: datetime objects - */ -public class PythonDateTime extends PythonDate implements PlanningImmutable { - // Taken from https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat - private static final Pattern ISO_FORMAT_PATTERN = Pattern.compile("^(?\\d\\d\\d\\d)-(?\\d\\d)-(?\\d\\d)" + - "(.(?\\d\\d)" + - "(:(?\\d\\d)" + - "(:(?\\d\\d)" + - "(\\.(?\\d\\d\\d)" + - "(?\\d\\d\\d)?" + - ")?)?)?)?" + - "(\\+(?\\d\\d):(?\\d\\d)" + - "(:(?\\d\\d)" + - "(\\.(?\\d\\d\\d\\d\\d\\d)" + - ")?)?)?$"); - - private static final int NANOS_PER_SECOND = 1_000_000_000; - public static PythonLikeType DATE_TIME_TYPE = new PythonLikeType("datetime", - PythonDateTime.class, - List.of(DATE_TYPE)); - - public static PythonLikeType $TYPE = DATE_TIME_TYPE; - - static { - try { - PythonLikeComparable.setup(DATE_TIME_TYPE); - registerMethods(); - - DATE_TIME_TYPE.$setAttribute("min", new PythonDateTime(LocalDate.of(1, 1, 1), - LocalTime.MAX)); - DATE_TIME_TYPE.$setAttribute("max", new PythonDateTime(LocalDate.of(9999, 12, 31), - LocalTime.MIN)); - DATE_TIME_TYPE.$setAttribute("resolution", new PythonTimeDelta(Duration.ofNanos(1000L))); - - PythonOverloadImplementor.createDispatchesFor(DATE_TIME_TYPE); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private static void registerMethods() throws NoSuchMethodException { - // Constructor - DATE_TIME_TYPE.addConstructor(ArgumentSpec.forFunctionReturning("datetime", PythonDateTime.class.getName()) - .addArgument("year", PythonInteger.class.getName()) - .addArgument("month", PythonInteger.class.getName()) - .addArgument("day", PythonInteger.class.getName()) - .addArgument("hour", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("minute", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("second", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("microsecond", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("tzinfo", PythonLikeObject.class.getName(), PythonNone.INSTANCE) - .addKeywordOnlyArgument("fold", PythonInteger.class.getName(), PythonInteger.ZERO) - .asPythonFunctionSignature( - PythonDateTime.class.getMethod("of", PythonInteger.class, PythonInteger.class, PythonInteger.class, - PythonInteger.class, PythonInteger.class, PythonInteger.class, PythonInteger.class, - PythonLikeObject.class, PythonInteger.class))); - - // Class methods - // Date handles today, - DATE_TIME_TYPE.addMethod("now", - ArgumentSpec.forFunctionReturning("now", PythonDateTime.class.getName()) - .addArgument("datetime_type", PythonLikeType.class.getName()) - .addArgument("tzinfo", PythonLikeObject.class.getName(), PythonNone.INSTANCE) - .asClassPythonFunctionSignature( - PythonDateTime.class.getMethod("now", - PythonLikeType.class, - PythonLikeObject.class))); - - DATE_TIME_TYPE.addMethod("utcnow", - ArgumentSpec.forFunctionReturning("now", PythonDateTime.class.getName()) - .addArgument("datetime_type", PythonLikeType.class.getName()) - .asClassPythonFunctionSignature( - PythonDateTime.class.getMethod("utc_now", - PythonLikeType.class))); - - DATE_TIME_TYPE.addMethod("fromtimestamp", - ArgumentSpec.forFunctionReturning("fromtimestamp", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("timestamp", PythonNumber.class.getName()) - .addArgument("tzinfo", PythonLikeObject.class.getName(), PythonNone.INSTANCE) - .asClassPythonFunctionSignature(PythonDateTime.class.getMethod("from_timestamp", - PythonLikeType.class, - PythonNumber.class, - PythonLikeObject.class))); - - DATE_TIME_TYPE.addMethod("strptime", - ArgumentSpec.forFunctionReturning("strptime", PythonDateTime.class.getName()) - .addArgument("datetime_type", PythonLikeType.class.getName()) - .addArgument("date_string", PythonString.class.getName()) - .addArgument("format", PythonString.class.getName()) - .asClassPythonFunctionSignature(PythonDateTime.class.getMethod("strptime", - PythonLikeType.class, - PythonString.class, - PythonString.class))); - - DATE_TIME_TYPE.addMethod("utcfromtimestamp", - ArgumentSpec.forFunctionReturning("utcfromtimestamp", PythonDate.class.getName()) - .addArgument("date_type", PythonLikeType.class.getName()) - .addArgument("timestamp", PythonNumber.class.getName()) - .asClassPythonFunctionSignature(PythonDateTime.class.getMethod("utc_from_timestamp", - PythonLikeType.class, - PythonNumber.class))); - - DATE_TIME_TYPE.addMethod("combine", - ArgumentSpec.forFunctionReturning("combine", PythonDateTime.class.getName()) - .addArgument("datetime_type", PythonLikeType.class.getName()) - .addArgument("date", PythonDate.class.getName()) - .addArgument("time", PythonTime.class.getName()) - .addNullableArgument("tzinfo", PythonLikeObject.class.getName()) - .asClassPythonFunctionSignature( - PythonDateTime.class.getMethod("combine", - PythonLikeType.class, PythonDate.class, - PythonTime.class, PythonLikeObject.class))); - - // Unary Operators - DATE_TIME_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - PythonDateTime.class.getMethod("toPythonString")); - - // Binary Operators - DATE_TIME_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonDateTime.class.getMethod("add_time_delta", PythonTimeDelta.class)); - DATE_TIME_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonDateTime.class.getMethod("subtract_time_delta", PythonTimeDelta.class)); - DATE_TIME_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonDateTime.class.getMethod("subtract_date_time", PythonDateTime.class)); - - // Instance methods - DATE_TIME_TYPE.addMethod("replace", - ArgumentSpec.forFunctionReturning("replace", PythonDate.class.getName()) - .addNullableArgument("year", PythonInteger.class.getName()) - .addNullableArgument("month", PythonInteger.class.getName()) - .addNullableArgument("day", PythonInteger.class.getName()) - .addNullableArgument("hour", PythonInteger.class.getName()) - .addNullableArgument("minute", PythonInteger.class.getName()) - .addNullableArgument("second", PythonInteger.class.getName()) - .addNullableArgument("microsecond", PythonInteger.class.getName()) - .addNullableArgument("tzinfo", PythonLikeObject.class.getName()) - .addNullableKeywordOnlyArgument("fold", PythonInteger.class.getName()) - .asPythonFunctionSignature(PythonDateTime.class.getMethod("replace", PythonInteger.class, - PythonInteger.class, PythonInteger.class, - PythonInteger.class, PythonInteger.class, PythonInteger.class, - PythonInteger.class, PythonLikeObject.class, PythonInteger.class))); - DATE_TIME_TYPE.addMethod("timetuple", - PythonDateTime.class.getMethod("timetuple")); // TODO: use time.struct_time type - - DATE_TIME_TYPE.addMethod("utctimetuple", - PythonDateTime.class.getMethod("utctimetuple")); // TODO: use time.struct_time type - - DATE_TIME_TYPE.addMethod("date", - PythonDateTime.class.getMethod("date")); - DATE_TIME_TYPE.addMethod("time", - PythonDateTime.class.getMethod("time")); - - DATE_TIME_TYPE.addMethod("timetz", - PythonDateTime.class.getMethod("timetz")); - - DATE_TIME_TYPE.addMethod("astimezone", - PythonDateTime.class.getMethod("astimezone", PythonTzinfo.class)); - - DATE_TIME_TYPE.addMethod("timestamp", - PythonDateTime.class.getMethod("timestamp")); - - DATE_TIME_TYPE.addMethod("tzname", - PythonDateTime.class.getMethod("tzname")); - - DATE_TIME_TYPE.addMethod("utcoffset", - PythonDateTime.class.getMethod("utcoffset")); - - DATE_TIME_TYPE.addMethod("dst", - PythonDateTime.class.getMethod("dst")); - - DATE_TIME_TYPE.addMethod("isoformat", - ArgumentSpec.forFunctionReturning("isoformat", PythonString.class.getName()) - .addArgument("sep", PythonString.class.getName(), PythonString.valueOf("T")) - .addArgument("timespec", PythonString.class.getName(), PythonString.valueOf("auto")) - .asPythonFunctionSignature( - PythonDateTime.class.getMethod("iso_format", PythonString.class, PythonString.class))); - - DATE_TIME_TYPE.addMethod("strftime", - ArgumentSpec.forFunctionReturning("strftime", PythonString.class.getName()) - .addArgument("format", PythonString.class.getName()) - .asPythonFunctionSignature(PythonDateTime.class.getMethod("strftime", PythonString.class))); - - DATE_TIME_TYPE.addMethod("ctime", - PythonDateTime.class.getMethod("ctime")); - - // The following virtual methods are inherited from date: - // toordinal, weekday, isoweekday, isocalendar - } - - final Temporal dateTime; - final ZoneId zoneId; - - public final PythonInteger hour; - public final PythonInteger minute; - public final PythonInteger second; - public final PythonInteger microsecond; - public final PythonInteger fold; - public final PythonLikeObject tzinfo; - - public PythonDateTime(ZonedDateTime zonedDateTime) { - this(zonedDateTime.toLocalDate(), zonedDateTime.toLocalTime(), zonedDateTime.getZone(), - zonedDateTime.equals(zonedDateTime.withEarlierOffsetAtOverlap()) ? 0 : 1); - } - - public PythonDateTime(LocalDateTime localDateTime) { - this(localDateTime.toLocalDate(), localDateTime.toLocalTime(), (ZoneId) null, 0); - } - - public PythonDateTime(LocalDate localDate, LocalTime localTime) { - this(localDate, localTime, (ZoneId) null, 0); - } - - public PythonDateTime(LocalDate localDate, LocalTime localTime, ZoneId zoneId) { - this(localDate, localTime, zoneId, 0); - } - - public PythonDateTime(LocalDate localDate, LocalTime localTime, PythonLikeObject tzinfo, int fold) { - this(localDate, localTime, - (tzinfo instanceof PythonTzinfo) ? ((PythonTzinfo) tzinfo).zoneId : null, - fold); - } - - public PythonDateTime(LocalDate localDate, LocalTime localTime, ZoneId zoneId, int fold) { - super(DATE_TIME_TYPE, localDate); - - this.zoneId = zoneId; - if (zoneId == null) { - dateTime = LocalDateTime.of(localDate, localTime); - } else { - dateTime = ZonedDateTime.of(localDate, localTime, zoneId); - } - - hour = PythonInteger.valueOf(localTime.getHour()); - minute = PythonInteger.valueOf(localTime.getMinute()); - second = PythonInteger.valueOf(localTime.getSecond()); - microsecond = PythonInteger.valueOf(localTime.getNano() / 1000); // Micro = Nano // 1000 - tzinfo = zoneId == null ? PythonNone.INSTANCE : new PythonTzinfo(zoneId); - this.fold = PythonInteger.valueOf(fold); - } - - public static PythonDateTime of(PythonInteger year, PythonInteger month, PythonInteger day, PythonInteger hour, - PythonInteger minute, PythonInteger second, - PythonInteger microsecond, PythonLikeObject tzinfo, PythonInteger fold) { - if (month.value.intValueExact() < 1 || month.value.intValueExact() > 12) { - throw new ValueError("month must be between 1 and 12"); - } - if (!YearMonth.of(year.value.intValueExact(), month.value.intValueExact()).isValidDay(day.value.intValueExact())) { - throw new ValueError("day must be between 1 and " - + YearMonth.of(year.value.intValueExact(), month.value.intValueExact()).lengthOfMonth()); - } - if (hour.value.intValueExact() < 0 || hour.value.intValueExact() >= 24) { - throw new ValueError("hour must be in range 0 <= hour < 24"); - } - if (minute.value.intValueExact() < 0 || minute.value.intValueExact() >= 60) { - throw new ValueError("minute must be in range 0 <= minute < 60"); - } - if (second.value.intValueExact() < 0 || second.value.intValueExact() >= 60) { - throw new ValueError("second must be in range 0 <= second < 60"); - } - if (microsecond.value.intValueExact() < 0 || microsecond.value.intValueExact() >= 1000000) { - throw new ValueError("microsecond must be in range 0 <= microsecond < 1000000"); - } - if (fold.value.intValueExact() != 0 && fold.value.intValueExact() != 1) { - throw new ValueError("fold must be in [0, 1]"); - } - - return new PythonDateTime( - LocalDate.of(year.value.intValueExact(), month.value.intValueExact(), day.value.intValueExact()), - LocalTime.of(hour.value.intValueExact(), minute.value.intValueExact(), second.value.intValueExact(), - microsecond.value.intValueExact() * 1000), - (tzinfo != PythonNone.INSTANCE) ? ((PythonTzinfo) tzinfo).zoneId : null, fold.value.intValueExact()); - } - - public static PythonDateTime of(int year, int month, int day, int hour, int minute, int second, - int microsecond, String tzname, int fold) { - return new PythonDateTime(LocalDate.of(year, month, day), LocalTime.of(hour, minute, second, microsecond * 1000), - (tzname != null) ? ZoneId.of(tzname) : null, fold); - } - - @Override - public PythonLikeObject $getAttributeOrNull(String name) { - switch (name) { - case "hour": - return hour; - case "minute": - return minute; - case "second": - return second; - case "microsecond": - return microsecond; - case "fold": - return fold; - case "tzinfo": - return tzinfo; - default: - return super.$getAttributeOrNull(name); - } - } - - public static PythonDateTime now(PythonLikeType type, PythonLikeObject tzinfo) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type: " + type); - } - LocalDateTime result = LocalDateTime.now(); - return new PythonDateTime(result.toLocalDate(), result.toLocalTime(), - tzinfo == PythonNone.INSTANCE ? null : ((PythonTzinfo) tzinfo).zoneId); - } - - public static PythonDateTime utc_now(PythonLikeType type) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type: " + type); - } - LocalDateTime result = LocalDateTime.now(Clock.systemUTC()); - return new PythonDateTime(result.toLocalDate(), result.toLocalTime(), - null); - } - - public static PythonDateTime from_ordinal(PythonInteger ordinal) { - return new PythonDateTime(LocalDate.ofEpochDay(ordinal.getValue().longValue() - EPOCH_ORDINAL_OFFSET), - LocalTime.MIDNIGHT, null); - } - - public static PythonDateTime from_timestamp(PythonLikeType type, PythonNumber timestamp, PythonLikeObject tzinfo) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type: " + type); - } - if (timestamp instanceof PythonInteger) { - return from_timestamp((PythonInteger) timestamp, tzinfo); - } else { - return from_timestamp((PythonFloat) timestamp, tzinfo); - } - } - - public static PythonDateTime from_timestamp(PythonInteger timestamp, PythonLikeObject tzinfo) { - Instant instant = Instant.ofEpochSecond(timestamp.getValue().longValue()); - if (tzinfo == PythonNone.INSTANCE) { - LocalDateTime result = instant.atZone(ZoneId.systemDefault()).toLocalDateTime(); - return new PythonDateTime(result); - } else { - ZoneId zoneId = ((PythonTzinfo) tzinfo).zoneId; - LocalDateTime result = instant.atZone(zoneId).toLocalDateTime(); - return new PythonDateTime(result.toLocalDate(), result.toLocalTime(), zoneId); - } - } - - public static PythonDateTime from_timestamp(PythonFloat timestamp, PythonLikeObject tzinfo) { - long epochSeconds = (long) Math.floor(timestamp.getValue().doubleValue()); - double remainder = timestamp.getValue().doubleValue() - epochSeconds; - int nanos = (int) Math.round(remainder * NANOS_PER_SECOND); - Instant instant = Instant.ofEpochSecond(timestamp.getValue().longValue(), nanos); - - if (tzinfo == PythonNone.INSTANCE) { - LocalDateTime result = instant.atZone(ZoneId.systemDefault()).toLocalDateTime(); - return new PythonDateTime(result); - } else { - ZoneId zoneId = ((PythonTzinfo) tzinfo).zoneId; - LocalDateTime result = instant.atZone(zoneId).toLocalDateTime(); - return new PythonDateTime(result.toLocalDate(), result.toLocalTime(), zoneId); - } - } - - public static PythonDateTime utc_from_timestamp(PythonLikeType type, PythonNumber timestamp) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type: " + type); - } - if (timestamp instanceof PythonInteger) { - return utc_from_timestamp((PythonInteger) timestamp); - } else { - return utc_from_timestamp((PythonFloat) timestamp); - } - } - - public static PythonDateTime utc_from_timestamp(PythonInteger timestamp) { - return new PythonDateTime(LocalDateTime.ofEpochSecond(timestamp.getValue().longValue(), 0, - ZoneOffset.UTC)); - } - - public static PythonDateTime utc_from_timestamp(PythonFloat timestamp) { - long epochSeconds = (long) Math.floor(timestamp.getValue().doubleValue()); - double remainder = timestamp.getValue().doubleValue() - epochSeconds; - int nanos = (int) Math.round(remainder * 1_000_000_000); - return new PythonDateTime(LocalDateTime.ofEpochSecond(timestamp.getValue().longValue(), nanos, - ZoneOffset.UTC)); - } - - public static PythonDateTime combine(PythonLikeType type, - PythonDate pythonDate, PythonTime pythonTime, - PythonLikeObject tzinfo) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type " + type.getTypeName()); - } - if (tzinfo == null) { - tzinfo = pythonTime.tzinfo; - } - return new PythonDateTime(pythonDate.localDate, pythonTime.localTime, tzinfo, - pythonTime.fold.getValue().intValue()); - } - - public static PythonDateTime from_iso_format(PythonString dateString) { - Matcher matcher = ISO_FORMAT_PATTERN.matcher(dateString.getValue()); - if (!matcher.find()) { - throw new IllegalArgumentException("String \"" + dateString.getValue() + "\" is not an isoformat string"); - } - - String year = matcher.group("year"); - String month = matcher.group("month"); - String day = matcher.group("day"); - - String hour = matcher.group("hour"); - String minute = matcher.group("minute"); - String second = matcher.group("second"); - String microHigh = matcher.group("microHigh"); - String microLow = matcher.group("microLow"); - - String timezoneHour = matcher.group("timezoneHour"); - String timezoneMinute = matcher.group("timezoneMinute"); - String timezoneSecond = matcher.group("timezoneSecond"); - String timezoneMicro = matcher.group("timezoneMicro"); - - LocalDate date = LocalDate.of(Integer.parseInt(year), - Integer.parseInt(month), - Integer.parseInt(day)); - - int hoursPart = 0; - int minutePart = 0; - int secondPart = 0; - int microPart = 0; - - if (hour != null) { - hoursPart = Integer.parseInt(hour); - } - if (minute != null) { - minutePart = Integer.parseInt(minute); - } - if (second != null) { - secondPart = Integer.parseInt(second); - } - if (microHigh != null) { - if (microLow != null) { - microPart = Integer.parseInt(microHigh + microLow); - } else { - microPart = 1000 * Integer.parseInt(microHigh); - } - } - - LocalTime time = LocalTime.of(hoursPart, minutePart, secondPart, microPart * 1000); - - if (timezoneHour == null) { - return new PythonDateTime(date, time); - } - - int timezoneHourPart = Integer.parseInt(timezoneHour); - int timezoneMinutePart = Integer.parseInt(timezoneMinute); - int timezoneSecondPart = 0; - int timezoneMicroPart = 0; - - if (timezoneSecond != null) { - timezoneSecondPart = Integer.parseInt(timezoneSecond); - } - - if (timezoneMicro != null) { - timezoneMicroPart = Integer.parseInt(timezoneMicro); - } - - // TODO: ZoneOffset does not support nanos - ZoneOffset timezone = ZoneOffset.ofHoursMinutesSeconds(timezoneHourPart, timezoneMinutePart, timezoneSecondPart); - return new PythonDateTime(date, time, timezone); - } - - public static PythonDate from_iso_calendar(PythonInteger year, PythonInteger week, PythonInteger day) { - int isoYear = year.getValue().intValue(); - int dayInIsoYear = (week.getValue().intValue() * 7) + day.getValue().intValue(); - int correction = LocalDate.of(isoYear, 1, 4).getDayOfWeek().getValue() + 3; - int ordinalDate = dayInIsoYear - correction; - if (ordinalDate <= 0) { - int daysInYear = LocalDate.ofYearDay(isoYear - 1, 1).lengthOfYear(); - return new PythonDateTime(LocalDate.ofYearDay(isoYear - 1, ordinalDate + daysInYear), LocalTime.MIN); - } else if (ordinalDate > LocalDate.ofYearDay(isoYear, 1).lengthOfYear()) { - int daysInYear = LocalDate.ofYearDay(isoYear, 1).lengthOfYear(); - return new PythonDateTime(LocalDate.ofYearDay(isoYear + 1, ordinalDate - daysInYear), LocalTime.MIN); - } else { - return new PythonDateTime(LocalDate.ofYearDay(isoYear, ordinalDate), LocalTime.MIN); - } - } - - private static T tryParseOrNull(DateTimeFormatter formatter, String text, TemporalQuery query) { - try { - return formatter.parse(text, query); - } catch (DateTimeException e) { - return null; - } - } - - public static PythonDateTime strptime(PythonLikeType type, PythonString date_string, PythonString format) { - if (type != DATE_TIME_TYPE) { - throw new TypeError("Unknown datetime type (" + type + ")."); - } - var formatter = PythonDateTimeFormatter.getDateTimeFormatter(format.value); - var asZonedDateTime = tryParseOrNull(formatter, date_string.value, ZonedDateTime::from); - if (asZonedDateTime != null) { - return new PythonDateTime(asZonedDateTime); - } - var asLocalDateTime = tryParseOrNull(formatter, date_string.value, LocalDateTime::from); - if (asLocalDateTime != null) { - return new PythonDateTime(asLocalDateTime); - } - var asLocalDate = tryParseOrNull(formatter, date_string.value, LocalDate::from); - if (asLocalDate != null) { - return new PythonDateTime(asLocalDate.atTime(LocalTime.MIDNIGHT)); - } - var asLocalTime = tryParseOrNull(formatter, date_string.value, LocalTime::from); - if (asLocalTime != null) { - return new PythonDateTime(asLocalTime.atDate(LocalDate.of(1900, 1, 1))); - } - throw new ValueError("data " + date_string.repr() + " does not match the format " + format.repr()); - } - - public PythonDateTime add_time_delta(PythonTimeDelta summand) { - if (dateTime instanceof LocalDateTime) { - return new PythonDateTime(((LocalDateTime) dateTime).plus(summand.duration)); - } else { - return new PythonDateTime(((ZonedDateTime) dateTime).plus(summand.duration)); - } - } - - public PythonDateTime subtract_time_delta(PythonTimeDelta subtrahend) { - if (dateTime instanceof LocalDateTime) { - return new PythonDateTime(((LocalDateTime) dateTime).minus(subtrahend.duration)); - } else { - return new PythonDateTime(((ZonedDateTime) dateTime).minus(subtrahend.duration)); - } - } - - public PythonTimeDelta subtract_date_time(PythonDateTime subtrahend) { - return new PythonTimeDelta(Duration.between(subtrahend.dateTime, dateTime)); - } - - @Override - public int compareTo(PythonDateTime other) { - if (dateTime instanceof LocalDateTime) { - return ((LocalDateTime) dateTime).compareTo((LocalDateTime) other.dateTime); - } else { - return ((ZonedDateTime) dateTime).compareTo((ZonedDateTime) other.dateTime); - } - } - - public PythonDate> date() { - if (dateTime instanceof LocalDateTime) { - return new PythonDate<>(((LocalDateTime) dateTime).toLocalDate()); - } else { - return new PythonDate<>(((ZonedDateTime) dateTime).toLocalDate()); - } - } - - public PythonTime time() { - if (dateTime instanceof LocalDateTime) { - return new PythonTime(((LocalDateTime) dateTime).toLocalTime(), null, fold.getValue().intValue()); - } else { - return new PythonTime(((ZonedDateTime) dateTime).toLocalTime(), null, fold.getValue().intValue()); - } - } - - public PythonTime timetz() { - if (dateTime instanceof LocalDateTime) { - return new PythonTime(((LocalDateTime) dateTime).toLocalTime(), null, fold.getValue().intValue()); - } else { - ZonedDateTime zonedDateTime = (ZonedDateTime) dateTime; - return new PythonTime(zonedDateTime.toLocalTime(), zonedDateTime.getZone(), fold.getValue().intValue()); - } - } - - public PythonDateTime replace(PythonInteger year, PythonInteger month, PythonInteger day, - PythonInteger hour, PythonInteger minute, PythonInteger second, - PythonInteger microsecond, PythonLikeObject tzinfo, PythonInteger fold) { - if (year == null) { - year = this.year; - } - - if (month == null) { - month = this.month; - } - - if (day == null) { - day = this.day; - } - - if (hour == null) { - hour = this.hour; - } - - if (minute == null) { - minute = this.minute; - } - - if (second == null) { - second = this.second; - } - - if (microsecond == null) { - microsecond = this.microsecond; - } - - if (tzinfo == null) { - tzinfo = this.tzinfo; - } - - if (fold == null) { - fold = this.fold; - } - - return new PythonDateTime(LocalDate.of(year.getValue().intValue(), - month.getValue().intValue(), - day.getValue().intValue()), - LocalTime.of(hour.getValue().intValue(), - minute.getValue().intValue(), - second.getValue().intValue(), - microsecond.getValue().intValue() * 1000), - tzinfo, - fold.getValue().intValue()); - } - - public PythonDateTime astimezone(PythonTzinfo zoneId) { - throw new UnsupportedOperationException(); // TODO - } - - public PythonLikeObject utcoffset() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return new PythonTimeDelta(Duration.ofSeconds( - zoneId.getRules().getOffset(((ZonedDateTime) dateTime).toInstant()).getTotalSeconds())); - } - - public PythonLikeObject dst() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return new PythonTimeDelta(zoneId.getRules().getDaylightSavings(((ZonedDateTime) dateTime).toInstant())); - } - - public PythonLikeObject tzname() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return PythonString.valueOf(zoneId.getRules().getOffset(((ZonedDateTime) dateTime).toInstant()) - .getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())); - } - - @Override - public PythonLikeTuple timetuple() { - PythonInteger yday = - to_ordinal().subtract(PythonDate.of(year.value.intValueExact(), 1, 1).to_ordinal()).add(PythonInteger.ONE); - PythonInteger dst; - if (zoneId != null) { - dst = zoneId.getRules().isDaylightSavings(((ZonedDateTime) dateTime).toInstant()) ? PythonInteger.ONE - : PythonInteger.ZERO; - } else { - dst = PythonInteger.valueOf(-1); - } - return PythonLikeTuple.fromItems( - year, month, day, - hour, minute, second, - weekday(), yday, dst); - } - - public PythonLikeTuple utctimetuple() { - if (zoneId == null) { - return timetuple(); - } else { - ZonedDateTime utcDateTime = ((ZonedDateTime) dateTime).withZoneSameInstant(ZoneOffset.UTC); - return new PythonDateTime(utcDateTime.toLocalDateTime()).timetuple(); - } - } - - public PythonFloat timestamp() { - if (dateTime instanceof LocalDateTime) { - LocalDateTime localDateTime = (LocalDateTime) dateTime; - return PythonFloat.valueOf(localDateTime.toInstant(ZoneId.systemDefault() - .getRules() - .getOffset(localDateTime)) - .toEpochMilli() / 1000.0); - } else { - return PythonFloat.valueOf(((ZonedDateTime) dateTime).toInstant().toEpochMilli() / 1000.0); - } - } - - public PythonString iso_format() { - return iso_format(PythonString.valueOf("T"), PythonString.valueOf("auto")); - } - - public PythonString iso_format(PythonString sep, PythonString timespec) { - return new PythonString(localDate.toString() + sep.value + time().isoformat(timespec).value); - } - - @Override - public PythonString toPythonString() { - return iso_format(PythonString.valueOf(" "), PythonString.valueOf("auto")); - } - - @Override - public PythonString ctime() { - if (dateTime instanceof LocalDateTime) { - return new PythonString(((LocalDateTime) dateTime).format(C_TIME_FORMATTER).replaceAll("(\\D)\\.", "$1")); - } else { - return new PythonString(((ZonedDateTime) dateTime).format(C_TIME_FORMATTER).replaceAll("(\\D)\\.", "$1")); - } - } - - @Override - public PythonString strftime(PythonString format) { - var formatter = PythonDateTimeFormatter.getDateTimeFormatter(format.value); - return PythonString.valueOf(formatter.format(dateTime)); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonDateTime that = (PythonDateTime) o; - return dateTime.equals(that.dateTime); - } - - @Override - public int hashCode() { - return dateTime.hashCode(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeFormatter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeFormatter.java deleted file mode 100644 index 92127ec4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeFormatter.java +++ /dev/null @@ -1,122 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import java.time.DayOfWeek; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.FormatStyle; -import java.time.format.TextStyle; -import java.time.temporal.ChronoField; -import java.time.temporal.WeekFields; -import java.util.regex.Pattern; - -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -/** - * Based on the format specified - * in - * the datetime documentation. - */ -public class PythonDateTimeFormatter { - private final static Pattern DIRECTIVE_PATTERN = Pattern.compile("([^%]*)%(.)"); - - static DateTimeFormatter getDateTimeFormatter(String pattern) { - DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); - var matcher = DIRECTIVE_PATTERN.matcher(pattern); - int endIndex = 0; - while (matcher.find()) { - var literalPart = matcher.group(1); - builder.appendLiteral(literalPart); - endIndex = matcher.end(); - - char directive = matcher.group(2).charAt(0); - switch (directive) { - case 'a' -> { - builder.appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT); - } - case 'A' -> { - builder.appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL); - } - case 'w' -> { - builder.appendValue(ChronoField.DAY_OF_WEEK); - } - case 'd' -> { - builder.appendValue(ChronoField.DAY_OF_MONTH, 2); - } - case 'b' -> { - builder.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT); - } - case 'B' -> { - builder.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL); - } - case 'm' -> { - builder.appendValue(ChronoField.MONTH_OF_YEAR, 2); - } - case 'y' -> { - builder.appendPattern("uu"); - } - case 'Y' -> { - builder.appendValue(ChronoField.YEAR); - } - case 'H' -> { - builder.appendValue(ChronoField.HOUR_OF_DAY, 2); - } - case 'I' -> { - builder.appendValue(ChronoField.HOUR_OF_AMPM, 2); - } - case 'p' -> { - builder.appendText(ChronoField.AMPM_OF_DAY); - } - case 'M' -> { - builder.appendValue(ChronoField.MINUTE_OF_HOUR, 2); - } - case 'S' -> { - builder.appendValue(ChronoField.SECOND_OF_MINUTE, 2); - } - case 'f' -> { - builder.appendValue(ChronoField.MICRO_OF_SECOND, 6); - } - case 'z' -> { - builder.appendOffset("+HHmmss", ""); - } - case 'Z' -> { - builder.appendZoneOrOffsetId(); - } - case 'j' -> { - builder.appendValue(ChronoField.DAY_OF_YEAR, 3); - } - case 'U' -> { - builder.appendValue(WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfYear(), 2); - } - case 'W' -> { - builder.appendValue(WeekFields.of(DayOfWeek.MONDAY, 7).weekOfYear(), 2); - } - case 'c' -> { - builder.appendLocalized(FormatStyle.MEDIUM, FormatStyle.MEDIUM); - } - case 'x' -> { - builder.appendLocalized(FormatStyle.MEDIUM, null); - } - case 'X' -> { - builder.appendLocalized(null, FormatStyle.MEDIUM); - } - case '%' -> { - builder.appendLiteral("%"); - } - case 'G' -> { - builder.appendValue(WeekFields.of(DayOfWeek.MONDAY, 4).weekBasedYear()); - } - case 'u' -> { - builder.appendValue(WeekFields.of(DayOfWeek.MONDAY, 4).dayOfWeek(), 1); - } - case 'V' -> { - builder.appendValue(WeekFields.of(DayOfWeek.MONDAY, 4).weekOfYear(), 2); - } - default -> { - throw new ValueError("Invalid directive (" + directive + ") in format string (" + pattern + ")."); - } - } - } - builder.appendLiteral(pattern.substring(endIndex)); - return builder.toFormatter(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTime.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTime.java deleted file mode 100644 index f8436461..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTime.java +++ /dev/null @@ -1,355 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.format.TextStyle; -import java.time.temporal.ChronoField; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonTime extends AbstractPythonLikeObject implements PlanningImmutable { - // Taken from https://docs.python.org/3/library/datetime.html#datetime.time.fromisoformat - private static final Pattern ISO_FORMAT_PATTERN = Pattern.compile("^(?\\d\\d)" + - "(:(?\\d\\d)" + - "(:(?\\d\\d)" + - "(\\.(?\\d\\d\\d)" + - "(?\\d\\d\\d)?" + - ")?)?)?" + - "(\\+(?\\d\\d):(?\\d\\d)" + - "(:(?\\d\\d)" + - "(:\\.(?\\d\\d\\d\\d\\d\\d)" + - ")?)?)?$"); - public static PythonLikeType TIME_TYPE = new PythonLikeType("time", - PythonTime.class); - - public static PythonLikeType $TYPE = TIME_TYPE; - - static { - try { - registerMethods(); - - TIME_TYPE.$setAttribute("min", new PythonTime(LocalTime.MAX)); - TIME_TYPE.$setAttribute("max", new PythonTime(LocalTime.MIN)); - TIME_TYPE.$setAttribute("resolution", new PythonTimeDelta(Duration.ofNanos(1000L))); - - PythonOverloadImplementor.createDispatchesFor(TIME_TYPE); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private static void registerMethods() throws NoSuchMethodException { - TIME_TYPE.addConstructor(ArgumentSpec.forFunctionReturning("datetime.time", PythonTime.class.getName()) - .addArgument("hour", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("minute", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("second", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("microsecond", PythonInteger.class.getName(), PythonInteger.ZERO) - .addArgument("tzinfo", PythonLikeObject.class.getName(), PythonNone.INSTANCE) - .addKeywordOnlyArgument("fold", PythonInteger.class.getName(), PythonInteger.ZERO) - .asPythonFunctionSignature( - PythonTime.class.getMethod("of", PythonInteger.class, PythonInteger.class, - PythonInteger.class, PythonInteger.class, PythonLikeObject.class, - PythonInteger.class))); - - TIME_TYPE.addMethod("fromisoformat", - ArgumentSpec.forFunctionReturning("fromisoformat", PythonTime.class.getName()) - .addArgument("time_string", PythonString.class.getName()) - .asStaticPythonFunctionSignature(PythonTime.class.getMethod("from_iso_format", PythonString.class))); - - TIME_TYPE.addMethod("replace", - ArgumentSpec.forFunctionReturning("replace", PythonTime.class.getName()) - .addNullableArgument("hour", PythonInteger.class.getName()) - .addNullableArgument("minute", PythonInteger.class.getName()) - .addNullableArgument("second", PythonInteger.class.getName()) - .addNullableArgument("microsecond", PythonInteger.class.getName()) - .addNullableArgument("tzinfo", PythonLikeObject.class.getName()) - .addNullableKeywordOnlyArgument("fold", PythonInteger.class.getName()) - .asPythonFunctionSignature(PythonTime.class.getMethod("replace", PythonInteger.class, - PythonInteger.class, PythonInteger.class, - PythonInteger.class, PythonLikeObject.class, - PythonInteger.class))); - - TIME_TYPE.addMethod("isoformat", - ArgumentSpec.forFunctionReturning("isoformat", PythonString.class.getName()) - .addArgument("timespec", PythonString.class.getName(), PythonString.valueOf("auto")) - .asPythonFunctionSignature(PythonTime.class.getMethod("isoformat", PythonString.class))); - - TIME_TYPE.addMethod("strftime", - ArgumentSpec.forFunctionReturning("strftime", PythonString.class.getName()) - .addArgument("format", PythonString.class.getName()) - .asPythonFunctionSignature(PythonTime.class.getMethod("strftime", PythonString.class))); - - TIME_TYPE.addMethod("tzname", - PythonTime.class.getMethod("tzname")); - - TIME_TYPE.addMethod("utcoffset", - PythonTime.class.getMethod("utcoffset")); - - TIME_TYPE.addMethod("dst", - PythonTime.class.getMethod("dst")); - - } - - final LocalTime localTime; - final ZoneId zoneId; - - public final PythonInteger hour; - public final PythonInteger minute; - public final PythonInteger second; - public final PythonInteger microsecond; - public final PythonInteger fold; - public final PythonLikeObject tzinfo; - - public PythonTime(LocalTime localTime) { - this(localTime, null, 0); - } - - public PythonTime(LocalTime localTime, ZoneId zoneId) { - this(localTime, zoneId, 0); - } - - public PythonTime(LocalTime localTime, ZoneId zoneId, int fold) { - super(TIME_TYPE); - - this.localTime = localTime; - this.zoneId = zoneId; - - hour = PythonInteger.valueOf(localTime.getHour()); - minute = PythonInteger.valueOf(localTime.getMinute()); - second = PythonInteger.valueOf(localTime.getSecond()); - microsecond = PythonInteger.valueOf(localTime.getNano() / 1000); // Micro = Nano // 1000 - tzinfo = zoneId == null ? PythonNone.INSTANCE : new PythonTzinfo(zoneId); - this.fold = PythonInteger.valueOf(fold); - } - - @Override - public PythonLikeObject $getAttributeOrNull(String name) { - switch (name) { - case "hour": - return hour; - case "minute": - return minute; - case "second": - return second; - case "microsecond": - return microsecond; - case "tzinfo": - return tzinfo; - case "fold": - return fold; - default: - return super.$getAttributeOrNull(name); - } - } - - public static PythonTime of(PythonInteger hour, PythonInteger minute, PythonInteger second, PythonInteger microsecond, - PythonLikeObject tzinfo, PythonInteger fold) { - return of(hour.value.intValueExact(), minute.value.intValueExact(), second.value.intValueExact(), - microsecond.value.intValueExact(), (tzinfo == PythonNone.INSTANCE) ? null : ((PythonTzinfo) tzinfo).zoneId, - fold.value.intValueExact()); - } - - public static PythonTime of(int hour, int minute, int second, int microsecond, ZoneId zoneId, int fold) { - if (hour < 0 || hour >= 24) { - throw new ValueError("hour must be in range 0 <= hour < 24"); - } - if (minute < 0 || minute >= 60) { - throw new ValueError("minute must be in range 0 <= minute < 60"); - } - if (second < 0 || second >= 60) { - throw new ValueError("second must be in range 0 <= second < 60"); - } - if (microsecond < 0 || microsecond >= 1000000) { - throw new ValueError("microsecond must be in range 0 <= microsecond < 1000000"); - } - if (fold != 0 && fold != 1) { - throw new ValueError("fold must be in [0, 1]"); - } - return new PythonTime(LocalTime.of(hour, minute, second, microsecond * 1000), - zoneId, fold); - } - - public static PythonTime from_iso_format(PythonString dateString) { - Matcher matcher = ISO_FORMAT_PATTERN.matcher(dateString.getValue()); - if (!matcher.find()) { - throw new ValueError("String \"" + dateString.getValue() + "\" is not an isoformat string"); - } - - String hour = matcher.group("hour"); - String minute = matcher.group("minute"); - String second = matcher.group("second"); - String microHigh = matcher.group("microHigh"); - String microLow = matcher.group("microLow"); - - String timezoneHour = matcher.group("timezoneHour"); - String timezoneMinute = matcher.group("timezoneMinute"); - String timezoneSecond = matcher.group("timezoneSecond"); - String timezoneMicro = matcher.group("timezoneMicro"); - - int hoursPart = 0; - int minutePart = 0; - int secondPart = 0; - int microPart = 0; - - if (hour != null && !hour.isEmpty()) { - hoursPart = Integer.parseInt(hour); - } - if (minute != null && !minute.isEmpty()) { - minutePart = Integer.parseInt(minute); - } - if (second != null && !second.isEmpty()) { - secondPart = Integer.parseInt(second); - } - if (microHigh != null && !microHigh.isEmpty()) { - if (microLow != null && !microLow.isEmpty()) { - microPart = Integer.parseInt(microHigh + microLow); - } else { - microPart = 1000 * Integer.parseInt(microHigh); - } - } - - LocalTime time = LocalTime.of(hoursPart, minutePart, secondPart, microPart * 1000); - - if (timezoneHour == null || timezoneHour.isEmpty()) { - return new PythonTime(time); - } - - int timezoneHourPart = Integer.parseInt(timezoneHour); - int timezoneMinutePart = Integer.parseInt(timezoneMinute); - int timezoneSecondPart = 0; - int timezoneMicroPart = 0; - - if (timezoneSecond != null) { - timezoneSecondPart = Integer.parseInt(timezoneSecond); - } - - if (timezoneMicro != null) { - timezoneMicroPart = Integer.parseInt(timezoneMicro); - } - - // TODO: ZoneOffset does not support nanos - ZoneOffset timezone = ZoneOffset.ofHoursMinutesSeconds(timezoneHourPart, timezoneMinutePart, timezoneSecondPart); - return new PythonTime(time, timezone); - } - - public PythonTime replace(PythonInteger hour, PythonInteger minute, PythonInteger second, - PythonInteger microsecond, PythonLikeObject tzinfo, PythonInteger fold) { - if (hour == null) { - hour = this.hour; - } - - if (minute == null) { - minute = this.minute; - } - - if (second == null) { - second = this.second; - } - - if (microsecond == null) { - microsecond = this.microsecond; - } - - if (tzinfo == null) { - tzinfo = (zoneId != null) ? new PythonTzinfo(zoneId) : PythonNone.INSTANCE; - } - - if (fold == null) { - fold = this.fold; - } - - return of(hour, minute, second, microsecond, tzinfo, fold); - } - - public PythonLikeObject utcoffset() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return new PythonTimeDelta(Duration.ofSeconds(zoneId.getRules().getOffset(Instant.ofEpochMilli(0L)).getTotalSeconds())); - } - - public PythonLikeObject dst() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return new PythonTimeDelta(zoneId.getRules().getDaylightSavings(Instant.ofEpochMilli(0L))); - } - - public PythonLikeObject tzname() { - if (zoneId == null) { - return PythonNone.INSTANCE; - } - return PythonString.valueOf(zoneId.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())); - } - - public PythonString isoformat(PythonString formatSpec) { - final String result; - switch (formatSpec.value) { - case "auto": - if (microsecond.value.equals(BigInteger.ZERO)) { - result = String.format("%02d:%02d:%02d", localTime.getHour(), localTime.getMinute(), localTime.getSecond()); - } else { - result = String.format("%02d:%02d:%02d.%06d", localTime.getHour(), localTime.getMinute(), - localTime.getSecond(), - localTime.get(ChronoField.MICRO_OF_SECOND)); - } - break; - case "hours": - result = String.format("%02d", localTime.getHour()); - break; - case "minutes": - result = String.format("%02d:%02d", localTime.getHour(), localTime.getMinute()); - break; - case "seconds": - result = String.format("%02d:%02d:%02d", localTime.getHour(), localTime.getMinute(), localTime.getSecond()); - break; - case "milliseconds": - result = String.format("%02d:%02d:%02d.%03d", localTime.getHour(), localTime.getMinute(), localTime.getSecond(), - localTime.get(ChronoField.MILLI_OF_SECOND)); - break; - case "microseconds": - result = String.format("%02d:%02d:%02d.%06d", localTime.getHour(), localTime.getMinute(), localTime.getSecond(), - localTime.get(ChronoField.MICRO_OF_SECOND)); - break; - default: - throw new ValueError("Invalid timespec: " + formatSpec.repr()); - } - return PythonString.valueOf(result); - } - - public PythonString strftime(PythonString formatSpec) { - var formatter = PythonDateTimeFormatter.getDateTimeFormatter(formatSpec.value); - return PythonString.valueOf(formatter.format(localTime)); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return localTime.toString(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDelta.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDelta.java deleted file mode 100644 index 5b6fb0bb..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDelta.java +++ /dev/null @@ -1,415 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import java.math.BigInteger; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.ZeroDivisionError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.util.arguments.ArgumentSpec; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -/** - * Python docs: timedelta-objects - */ -public class PythonTimeDelta extends AbstractPythonLikeObject implements PythonLikeComparable, - PlanningImmutable { - private static final int NANOS_IN_SECOND = 1_000_000_000; - private static final int SECONDS_IN_DAY = 86400; // 24 * 60 * 60 - - public static PythonLikeType TIME_DELTA_TYPE = new PythonLikeType("timedelta", - PythonTimeDelta.class); - - public static PythonLikeType $TYPE = TIME_DELTA_TYPE; - - static { - try { - PythonLikeComparable.setup(TIME_DELTA_TYPE); - registerMethods(); - - TIME_DELTA_TYPE.$setAttribute("min", new PythonTimeDelta(Duration.ofDays(-999999999))); - TIME_DELTA_TYPE.$setAttribute("max", new PythonTimeDelta(Duration.ofDays(1000000000) - .minusNanos(1000))); - TIME_DELTA_TYPE.$setAttribute("resolution", new PythonTimeDelta(Duration.ofNanos(1000))); - - PythonOverloadImplementor.createDispatchesFor(TIME_DELTA_TYPE); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private static void registerMethods() throws NoSuchMethodException { - // Constructor - TIME_DELTA_TYPE.addConstructor(ArgumentSpec.forFunctionReturning("timedelta", PythonTimeDelta.class.getName()) - .addArgument("days", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("seconds", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("microseconds", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("milliseconds", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("minutes", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("hours", PythonNumber.class.getName(), PythonInteger.ZERO) - .addArgument("weeks", PythonNumber.class.getName(), PythonInteger.ZERO) - .asPythonFunctionSignature(PythonTimeDelta.class.getMethod("of", PythonNumber.class, PythonNumber.class, - PythonNumber.class, PythonNumber.class, PythonNumber.class, PythonNumber.class, PythonNumber.class))); - - // Unary - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.POSITIVE, - PythonTimeDelta.class.getMethod("pos")); - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.NEGATIVE, - PythonTimeDelta.class.getMethod("negate")); - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.ABS, - PythonTimeDelta.class.getMethod("abs")); - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.AS_BOOLEAN, - PythonTimeDelta.class.getMethod("isZero")); - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - PythonTimeDelta.class.getMethod("toPythonString")); - TIME_DELTA_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - PythonTimeDelta.class.getMethod("toPythonRepr")); - - // Binary - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.ADD, - PythonTimeDelta.class.getMethod("add_time_delta", PythonTimeDelta.class)); - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonTimeDelta.class.getMethod("subtract_time_delta", PythonTimeDelta.class)); - - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonTimeDelta.class.getMethod("get_integer_multiple", PythonInteger.class)); - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonTimeDelta.class.getMethod("get_float_multiple", PythonFloat.class)); - - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonTimeDelta.class.getMethod("divide_time_delta", PythonTimeDelta.class)); - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonTimeDelta.class.getMethod("divide_integer", PythonInteger.class)); - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonTimeDelta.class.getMethod("divide_float", PythonFloat.class)); - - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonTimeDelta.class.getMethod("floor_divide_time_delta", PythonTimeDelta.class)); - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonTimeDelta.class.getMethod("floor_divide_integer", PythonInteger.class)); - - TIME_DELTA_TYPE.addBinaryMethod(PythonBinaryOperator.MODULO, - PythonTimeDelta.class.getMethod("remainder_time_delta", PythonTimeDelta.class)); - - // Methods - TIME_DELTA_TYPE.addMethod("total_seconds", PythonTimeDelta.class.getMethod("total_seconds")); - } - - final Duration duration; - - public final PythonInteger days; - public final PythonInteger seconds; - public final PythonInteger microseconds; - - public PythonTimeDelta(Duration duration) { - super(TIME_DELTA_TYPE); - this.duration = duration; - - if (duration.isNegative()) { - if (duration.getSeconds() % SECONDS_IN_DAY != 0 || - duration.getNano() != 0) { - days = PythonInteger.valueOf(duration.toDays() - 1); - seconds = PythonInteger.valueOf((SECONDS_IN_DAY + (duration.toSeconds() % SECONDS_IN_DAY) % SECONDS_IN_DAY)); - } else { - days = PythonInteger.valueOf(duration.toDays()); - seconds = PythonInteger.valueOf(Math.abs(duration.toSeconds() % SECONDS_IN_DAY)); - } - } else { - days = PythonInteger.valueOf(duration.toDays()); - seconds = PythonInteger.valueOf(Math.abs(duration.toSeconds() % SECONDS_IN_DAY)); - } - microseconds = PythonInteger.valueOf(duration.toNanosPart() / 1000); - } - - @Override - public PythonLikeObject $getAttributeOrNull(String name) { - switch (name) { - case "days": - return days; - case "seconds": - return seconds; - case "microseconds": - return microseconds; - default: - return super.$getAttributeOrNull(name); - } - } - - public static PythonTimeDelta of(int days, int seconds, int microseconds) { - return new PythonTimeDelta(Duration.ofDays(days).plusSeconds(seconds) - .plusNanos(microseconds * 1000L)); - } - - public static PythonTimeDelta of(PythonNumber days, PythonNumber seconds, PythonNumber microseconds, - PythonNumber milliseconds, PythonNumber minutes, PythonNumber hours, - PythonNumber weeks) { - Duration out = Duration.ZERO; - out = addToDuration(out, days, ChronoUnit.DAYS); - out = addToDuration(out, seconds, ChronoUnit.SECONDS); - out = addToDuration(out, microseconds, ChronoUnit.MICROS); - out = addToDuration(out, milliseconds, ChronoUnit.MILLIS); - out = addToDuration(out, minutes, ChronoUnit.MINUTES); - out = addToDuration(out, hours, ChronoUnit.HOURS); - if (weeks instanceof PythonInteger) { // weeks is an estimated duration; cannot use addToDuration - out = out.plusDays(weeks.getValue().longValue() * 7); - } else if (weeks instanceof PythonFloat) { - out = out.plusNanos(Math.round(Duration.ofDays(7L).toNanos() * weeks.getValue().doubleValue())); - } else { - throw new ValueError("Amount for weeks is not a float or integer."); - } - return new PythonTimeDelta(out); - } - - private static Duration addToDuration(Duration duration, PythonNumber amount, TemporalUnit temporalUnit) { - if (amount instanceof PythonInteger) { - return duration.plus(amount.getValue().longValue(), temporalUnit); - } else if (amount instanceof PythonFloat) { - return duration.plusNanos(Math.round(temporalUnit.getDuration().toNanos() * amount.getValue().doubleValue())); - } else { - throw new IllegalArgumentException("Amount for " + temporalUnit.toString() + " is not a float or integer."); - } - } - - public PythonFloat total_seconds() { - return PythonFloat.valueOf((double) duration.toNanos() / NANOS_IN_SECOND); - } - - public PythonTimeDelta add_time_delta(PythonTimeDelta other) { - return new PythonTimeDelta(duration.plus(other.duration)); - } - - public PythonTimeDelta subtract_time_delta(PythonTimeDelta other) { - return new PythonTimeDelta(duration.minus(other.duration)); - } - - public PythonTimeDelta get_integer_multiple(PythonInteger multiple) { - return new PythonTimeDelta(duration.multipliedBy(multiple.getValue().longValue())); - } - - public PythonTimeDelta get_float_multiple(PythonFloat multiple) { - double multipleAsDouble = multiple.getValue().doubleValue(); - long flooredMultiple = (long) Math.floor(multipleAsDouble); - double fractionalPart = multipleAsDouble - flooredMultiple; - long nanos = duration.toNanos(); - double fractionalNanos = fractionalPart * nanos; - long fractionalNanosInMicroResolution = Math.round(fractionalNanos / 1000) * 1000; - - return new PythonTimeDelta(duration.multipliedBy(flooredMultiple) - .plus(Duration.ofNanos(fractionalNanosInMicroResolution))); - } - - public PythonFloat divide_time_delta(PythonTimeDelta divisor) { - if (divisor.duration.equals(Duration.ZERO)) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - return PythonFloat.valueOf((double) duration.toNanos() / divisor.duration.toNanos()); - } - - public PythonTimeDelta divide_integer(PythonInteger divisor) { - if (divisor.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - return new PythonTimeDelta(duration.dividedBy(divisor.getValue().longValue())); - } - - public PythonTimeDelta divide_float(PythonFloat divisor) { - if (divisor.value == 0.0) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - double fractionalNanos = duration.toNanos() / divisor.getValue().doubleValue(); - return new PythonTimeDelta(Duration.ofNanos(Math.round(fractionalNanos / 1000) * 1000)); - } - - public PythonInteger floor_divide_time_delta(PythonTimeDelta divisor) { - if (divisor.duration.equals(Duration.ZERO)) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - - long amount = duration.dividedBy(divisor.duration); - if (divisor.duration.multipliedBy(amount).equals(duration)) { - // division exact - return PythonInteger.valueOf(amount); - } - - // division not exact - // Java use round to zero; Python use floor - // If both operands have the same sign, result is positive, and round to zero = floor - // If operands have different signs, the result is negative, and round to zero = floor + 1 - if (duration.isNegative() == divisor.duration.isNegative()) { - // same sign - return PythonInteger.valueOf(amount); - } else { - // different sign - return PythonInteger.valueOf(amount - 1); - } - } - - public PythonTimeDelta floor_divide_integer(PythonInteger divisor) { - if (divisor.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - return new PythonTimeDelta(duration.dividedBy(divisor.getValue().longValue())); - } - - public PythonTimeDelta remainder_time_delta(PythonTimeDelta divisor) { - boolean leftIsNegative = duration.isNegative(); - int rightHandSign = divisor.duration.compareTo(Duration.ZERO); - - if (rightHandSign == 0) { - throw new ZeroDivisionError("timedelta division or modulo by zero"); - } - - long floorDivisionResult = duration.abs().dividedBy(divisor.abs().duration); - Duration remainder; - - if (rightHandSign > 0) { - // Need a positive result - if (leftIsNegative) { - remainder = divisor.duration.plus(duration.plus(divisor.duration.multipliedBy(floorDivisionResult))); - } else { - remainder = duration.minus(divisor.duration.multipliedBy(floorDivisionResult)); - } - } else { - // Need a negative result - if (leftIsNegative) { - remainder = duration.minus(divisor.duration.multipliedBy(floorDivisionResult)); - } else { - remainder = divisor.duration.plus(duration.plus(divisor.duration.multipliedBy(floorDivisionResult))); - } - } - return new PythonTimeDelta(remainder); - } - - public PythonTimeDelta pos() { - return this; - } - - public PythonTimeDelta negate() { - return new PythonTimeDelta(duration.negated()); - } - - public PythonTimeDelta abs() { - return new PythonTimeDelta(duration.abs()); - } - - public PythonString toPythonString() { - return new PythonString(toString()); - } - - public PythonString toPythonRepr() { - StringBuilder out = new StringBuilder("datetime.timedelta("); - if (!days.value.equals(BigInteger.ZERO)) { - out.append("days=").append(days); - } - if (!seconds.value.equals(BigInteger.ZERO)) { - if (out.charAt(out.length() - 1) != '(') { - out.append(", "); - } - out.append("seconds=").append(seconds); - } - if (!microseconds.value.equals(BigInteger.ZERO)) { - if (out.charAt(out.length() - 1) != '(') { - out.append(", "); - } - out.append("microseconds=").append(microseconds); - } - - if (out.charAt(out.length() - 1) == '(') { - // No content; do a attribute-less zero - out.append("0"); - } - out.append(")"); - return PythonString.valueOf(out.toString()); - } - - public PythonBoolean isZero() { - return PythonBoolean.valueOf(duration.isZero()); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder(); - long daysPart = duration.toDaysPart(); - Duration durationAfterDay = duration.minusDays(daysPart); - if (duration.isNegative() && !Duration.ofDays(1).multipliedBy(daysPart).equals(duration)) { - daysPart = daysPart - 1; - durationAfterDay = durationAfterDay.plus(Duration.ofDays(1)); - } - - if (daysPart != 0) { - out.append(daysPart); - out.append(" day"); - if (daysPart > 1 || daysPart < -1) { - out.append('s'); - } - out.append(", "); - } - int hours = durationAfterDay.toHoursPart(); - out.append(hours); - out.append(':'); - - int minutes = durationAfterDay.toMinutesPart(); - out.append(String.format("%02d", minutes)); - out.append(':'); - - int seconds = durationAfterDay.toSecondsPart(); - out.append(String.format("%02d", seconds)); - - int micros = durationAfterDay.toNanosPart() / 1000; - if (micros != 0) { - out.append(String.format(".%06d", micros)); - } - - return out.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonTimeDelta that = (PythonTimeDelta) o; - return duration.equals(that.duration); - } - - @Override - public int hashCode() { - return duration.hashCode(); - } - - @Override - public PythonString $method$__repr__() { - return toPythonRepr(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } - - @Override - public int compareTo(PythonTimeDelta pythonTimeDelta) { - return duration.compareTo(pythonTimeDelta.duration); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTzinfo.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTzinfo.java deleted file mode 100644 index ff1cbc54..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/datetime/PythonTzinfo.java +++ /dev/null @@ -1,60 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import java.time.ZoneId; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonTzinfo extends AbstractPythonLikeObject implements PlanningImmutable { - public static PythonLikeType TZ_INFO_TYPE = new PythonLikeType("tzinfo", - PythonTzinfo.class); - - public static PythonLikeType $TYPE = TZ_INFO_TYPE; - - static { - try { - registerMethods(); - PythonOverloadImplementor.createDispatchesFor(TZ_INFO_TYPE); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private static void registerMethods() throws NoSuchMethodException { - TZ_INFO_TYPE.addMethod("utcoffset", new PythonFunctionSignature(new MethodDescriptor( - PythonTzinfo.class.getMethod("utcoffset", PythonLikeObject.class)), - PythonTimeDelta.TIME_DELTA_TYPE, BuiltinTypes.BASE_TYPE)); - TZ_INFO_TYPE.addMethod("dst", new PythonFunctionSignature(new MethodDescriptor( - PythonTzinfo.class.getMethod("dst", PythonLikeObject.class)), - PythonTimeDelta.TIME_DELTA_TYPE, BuiltinTypes.BASE_TYPE)); - TZ_INFO_TYPE.addMethod("tzname", new PythonFunctionSignature(new MethodDescriptor( - PythonTzinfo.class.getMethod("tzname", PythonLikeObject.class)), - BuiltinTypes.STRING_TYPE, BuiltinTypes.BASE_TYPE)); - } - - final ZoneId zoneId; - - public PythonTzinfo(ZoneId zoneId) { - super(TZ_INFO_TYPE); - this.zoneId = zoneId; - } - - public PythonTimeDelta utcoffset(PythonLikeObject dateTime) { - throw new UnsupportedOperationException(); // TODO - } - - public PythonTimeDelta dst(PythonLikeObject dateTime) { - throw new UnsupportedOperationException(); // TODO - } - - public PythonString tzname(PythonLikeObject dateTime) { - throw new UnsupportedOperationException(); // TODO - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/AttributeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/AttributeError.java deleted file mode 100644 index caaac635..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/AttributeError.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class AttributeError extends PythonException { - public final static PythonLikeType ATTRIBUTE_ERROR_TYPE = - new PythonLikeType("AttributeError", AttributeError.class, List.of(EXCEPTION_TYPE)), - $TYPE = ATTRIBUTE_ERROR_TYPE; - - static { - ATTRIBUTE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new AttributeError(ATTRIBUTE_ERROR_TYPE, - positionalArguments))); - } - - public AttributeError() { - super(ATTRIBUTE_ERROR_TYPE); - } - - public AttributeError(String message) { - super(ATTRIBUTE_ERROR_TYPE, message); - } - - public AttributeError(PythonLikeType type, List args) { - super(type, args); - } - -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/BufferError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/BufferError.java deleted file mode 100644 index ba6e5c4e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/BufferError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class BufferError extends PythonException { - final public static PythonLikeType BUFFER_ERROR_TYPE = - new PythonLikeType("BufferError", BufferError.class, List.of(EXCEPTION_TYPE)), - $TYPE = BUFFER_ERROR_TYPE; - - static { - BUFFER_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new BufferError(BUFFER_ERROR_TYPE, - positionalArguments))); - } - - public BufferError(PythonLikeType type) { - super(type); - } - - public BufferError(PythonLikeType type, String message) { - super(type, message); - } - - public BufferError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/CPythonException.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/CPythonException.java deleted file mode 100644 index 8811bd5d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/CPythonException.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Python class for general exceptions. Equivalent to Java's {@link RuntimeException} - */ -public class CPythonException extends PythonException { - public final static PythonLikeType CPYTHON_EXCEPTION_TYPE = - new PythonLikeType("CPython", CPythonException.class, List.of(EXCEPTION_TYPE)), - $TYPE = CPYTHON_EXCEPTION_TYPE; - - public CPythonException() { - super(CPYTHON_EXCEPTION_TYPE); - } - - public CPythonException(String message) { - super(CPYTHON_EXCEPTION_TYPE, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/GeneratorExit.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/GeneratorExit.java deleted file mode 100644 index e5813d06..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/GeneratorExit.java +++ /dev/null @@ -1,52 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; - -/** - * Error thrown when a user of a generator indicates a Generator should close - */ -public class GeneratorExit extends PythonException { - public static final PythonLikeType GENERATOR_EXIT_TYPE = new PythonLikeType("GeneratorExit", - GeneratorExit.class, List.of(EXCEPTION_TYPE)), - $TYPE = GENERATOR_EXIT_TYPE; - - static { - GENERATOR_EXIT_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new GeneratorExit(GENERATOR_EXIT_TYPE, - positionalArguments))); - } - - private final PythonLikeObject value; - - public GeneratorExit() { - this(PythonNone.INSTANCE); - } - - public GeneratorExit(PythonLikeObject value) { - this(GENERATOR_EXIT_TYPE, List.of(value)); - } - - public GeneratorExit(PythonLikeType type, List args) { - super(type, args); - if (args.size() > 0) { - value = args.get(0); - } else { - value = PythonNone.INSTANCE; - } - } - - /** - * This exception acts as a signal, and should be low cost - * - * @return this - */ - @Override - public synchronized Throwable fillInStackTrace() { - // Do nothing - return this; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ImportError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ImportError.java deleted file mode 100644 index 56c2a01e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ImportError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ImportError extends PythonBaseException { - final public static PythonLikeType IMPORT_ERROR_TYPE = - new PythonLikeType("ImportError", ImportError.class, List.of(BASE_EXCEPTION_TYPE)), - $TYPE = IMPORT_ERROR_TYPE; - - static { - IMPORT_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ImportError(IMPORT_ERROR_TYPE, - positionalArguments))); - } - - public ImportError(PythonLikeType type) { - super(type); - } - - public ImportError(PythonLikeType type, String message) { - super(type, message); - } - - public ImportError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ModuleNotFoundError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ModuleNotFoundError.java deleted file mode 100644 index 2b8b3861..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ModuleNotFoundError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ModuleNotFoundError extends ImportError { - final public static PythonLikeType MODULE_NOT_FOUND_ERROR_TYPE = - new PythonLikeType("ModuleNotFoundError", ModuleNotFoundError.class, List.of(IMPORT_ERROR_TYPE)), - $TYPE = MODULE_NOT_FOUND_ERROR_TYPE; - - static { - MODULE_NOT_FOUND_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new ModuleNotFoundError(MODULE_NOT_FOUND_ERROR_TYPE, positionalArguments))); - } - - public ModuleNotFoundError(PythonLikeType type) { - super(type); - } - - public ModuleNotFoundError(PythonLikeType type, String message) { - super(type, message); - } - - public ModuleNotFoundError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NameError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NameError.java deleted file mode 100644 index c7ebb243..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NameError.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class NameError extends PythonException { - public final static PythonLikeType NAME_ERROR_TYPE = - new PythonLikeType("NameError", NameError.class, List.of(EXCEPTION_TYPE)), - $TYPE = NAME_ERROR_TYPE; - - static { - NAME_ERROR_TYPE - .setConstructor(((positionalArguments, namedArguments, callerInstance) -> new NameError(NAME_ERROR_TYPE, - positionalArguments))); - } - - public NameError() { - super(NAME_ERROR_TYPE); - } - - public NameError(String message) { - super(NAME_ERROR_TYPE, message); - } - - public NameError(PythonLikeType type, List args) { - super(type, args); - } - - public NameError(PythonLikeType type) { - super(type); - } - - public NameError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NotImplementedError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NotImplementedError.java deleted file mode 100644 index 35fdd533..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/NotImplementedError.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class NotImplementedError extends RuntimeError { - final public static PythonLikeType NOT_IMPLEMENTED_ERROR_TYPE = - new PythonLikeType("NotImplementedError", NotImplementedError.class, List.of(RUNTIME_ERROR_TYPE)), - $TYPE = NOT_IMPLEMENTED_ERROR_TYPE; - - static { - NOT_IMPLEMENTED_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new NotImplementedError(NOT_IMPLEMENTED_ERROR_TYPE, positionalArguments))); - } - - public NotImplementedError() { - super(NOT_IMPLEMENTED_ERROR_TYPE); - } - - public NotImplementedError(String message) { - super(NOT_IMPLEMENTED_ERROR_TYPE, message); - } - - public NotImplementedError(PythonLikeType type) { - super(type); - } - - public NotImplementedError(PythonLikeType type, String message) { - super(type, message); - } - - public NotImplementedError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonAssertionError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonAssertionError.java deleted file mode 100644 index f241aae7..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonAssertionError.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class PythonAssertionError extends PythonException { - public static final PythonLikeType ASSERTION_ERROR_TYPE = new PythonLikeType("AssertionError", - PythonAssertionError.class, - List.of(EXCEPTION_TYPE)), - $TYPE = ASSERTION_ERROR_TYPE; - - static { - ASSERTION_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new PythonAssertionError(ASSERTION_ERROR_TYPE, - positionalArguments))); - } - - public PythonAssertionError() { - super(ASSERTION_ERROR_TYPE); - } - - public PythonAssertionError(PythonLikeType type) { - super(type); - } - - public PythonAssertionError(PythonLikeType type, List args) { - super(type, args); - } -} 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 deleted file mode 100644 index 91d655e2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonBaseException.java +++ /dev/null @@ -1,101 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.wrappers.JavaObjectWrapper; - -/** - * Python base class for all exceptions. Equivalent to Java's {@link Throwable}. - */ -public class PythonBaseException extends RuntimeException implements PythonLikeObject { - final public static PythonLikeType BASE_EXCEPTION_TYPE = new PythonLikeType("BaseException", PythonBaseException.class), - $TYPE = BASE_EXCEPTION_TYPE; - - static { - BASE_EXCEPTION_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new PythonBaseException(BASE_EXCEPTION_TYPE, - positionalArguments))); - } - - Map dict; - - final PythonLikeType type; - final List args; - - private static String getMessageFromArgs(List args) { - if (args.size() < 1) { - return null; - } - - if (args.get(0) instanceof PythonString) { - return ((PythonString) args.get(0)).getValue(); - } - - return null; - } - - public PythonBaseException(PythonLikeType type) { - this(type, Collections.emptyList()); - } - - public PythonBaseException(PythonLikeType type, List args) { - super(getMessageFromArgs(args)); - this.type = type; - this.args = args; - this.dict = new HashMap<>(); - $setAttribute("args", PythonLikeTuple.fromList(args)); - $setAttribute("__cause__", PythonNone.INSTANCE); - } - - public PythonBaseException(PythonLikeType type, String message) { - super(message); - this.type = type; - this.args = List.of(PythonString.valueOf(message)); - this.dict = new HashMap<>(); - $setAttribute("args", PythonLikeTuple.fromList(args)); - $setAttribute("__cause__", PythonNone.INSTANCE); - } - - @Override - public synchronized Throwable initCause(Throwable cause) { - super.initCause(cause); - if (cause instanceof PythonLikeObject pythonError) { - $setAttribute("__cause__", pythonError); - } else { - $setAttribute("__cause__", new JavaObjectWrapper(cause)); - } - return this; - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - return dict.get(attributeName); - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - dict.put(attributeName, value); - } - - @Override - public void $deleteAttribute(String attributeName) { - dict.remove(attributeName); - } - - public PythonLikeTuple $getArgs() { - return (PythonLikeTuple) $getAttributeOrError("args"); - } - - @Override - public PythonLikeType $getType() { - return type; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonException.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonException.java deleted file mode 100644 index bc78081f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonException.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Python class for general exceptions. Equivalent to Java's {@link RuntimeException} - */ -public class PythonException extends PythonBaseException { - final public static PythonLikeType EXCEPTION_TYPE = - new PythonLikeType("Exception", PythonException.class, List.of(BASE_EXCEPTION_TYPE)), - $TYPE = EXCEPTION_TYPE; - - static { - EXCEPTION_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new PythonException(EXCEPTION_TYPE, - positionalArguments))); - } - - public PythonException(PythonLikeType type) { - super(type); - } - - public PythonException(PythonLikeType type, List args) { - super(type, args); - } - - public PythonException(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonTraceback.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonTraceback.java deleted file mode 100644 index 44a2d1d1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonTraceback.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Traceback of a Python Error. - * TODO: Implement this - */ -public class PythonTraceback extends AbstractPythonLikeObject { - public static final PythonLikeType TRACEBACK_TYPE = new PythonLikeType("traceback", PythonTraceback.class), - $TYPE = TRACEBACK_TYPE; - - public PythonTraceback() { - super(TRACEBACK_TYPE); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RecursionError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RecursionError.java deleted file mode 100644 index 16398b16..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RecursionError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class RecursionError extends RuntimeError { - final public static PythonLikeType RECURSION_ERROR_TYPE = - new PythonLikeType("RecursionError", RecursionError.class, List.of(RUNTIME_ERROR_TYPE)), - $TYPE = RECURSION_ERROR_TYPE; - - static { - RECURSION_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new RecursionError(RECURSION_ERROR_TYPE, - positionalArguments))); - } - - public RecursionError(PythonLikeType type) { - super(type); - } - - public RecursionError(PythonLikeType type, String message) { - super(type, message); - } - - public RecursionError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ReferenceError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ReferenceError.java deleted file mode 100644 index fb792457..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ReferenceError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ReferenceError extends PythonBaseException { - final public static PythonLikeType REFERENCE_ERROR_TYPE = - new PythonLikeType("ReferenceError", ReferenceError.class, List.of(BASE_EXCEPTION_TYPE)), - $TYPE = REFERENCE_ERROR_TYPE; - - static { - REFERENCE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ReferenceError(REFERENCE_ERROR_TYPE, - positionalArguments))); - } - - public ReferenceError(PythonLikeType type) { - super(type); - } - - public ReferenceError(PythonLikeType type, String message) { - super(type, message); - } - - public ReferenceError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RuntimeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RuntimeError.java deleted file mode 100644 index cd7dd68f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/RuntimeError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class RuntimeError extends PythonBaseException { - final public static PythonLikeType RUNTIME_ERROR_TYPE = - new PythonLikeType("RuntimeError", RuntimeError.class, List.of(BASE_EXCEPTION_TYPE)), - $TYPE = RUNTIME_ERROR_TYPE; - - static { - RUNTIME_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new RuntimeError(RUNTIME_ERROR_TYPE, - positionalArguments))); - } - - public RuntimeError(String message) { - super(RUNTIME_ERROR_TYPE, message); - } - - public RuntimeError(PythonLikeType type) { - super(type); - } - - public RuntimeError(PythonLikeType type, String message) { - super(type, message); - } - - public RuntimeError(PythonLikeType type, List args) { - super(type, args); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopAsyncIteration.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopAsyncIteration.java deleted file mode 100644 index 9049f725..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopAsyncIteration.java +++ /dev/null @@ -1,55 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; - -/** - * Error thrown when a Python async iterator has no more values to return. - */ -public class StopAsyncIteration extends PythonException { - public static final PythonLikeType STOP_ASYNC_ITERATION_TYPE = new PythonLikeType("StopAsyncIteration", - StopAsyncIteration.class, List.of(EXCEPTION_TYPE)), - $TYPE = STOP_ASYNC_ITERATION_TYPE; - - static { - STOP_ASYNC_ITERATION_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new StopAsyncIteration(STOP_ASYNC_ITERATION_TYPE, positionalArguments))); - } - - private final PythonLikeObject value; - - public StopAsyncIteration() { - this(PythonNone.INSTANCE); - } - - public StopAsyncIteration(PythonLikeObject value) { - this(STOP_ASYNC_ITERATION_TYPE, List.of(value)); - } - - public StopAsyncIteration(PythonLikeType type, List args) { - super(type, args); - if (args.size() > 0) { - value = args.get(0); - } else { - value = PythonNone.INSTANCE; - } - } - - public PythonLikeObject getValue() { - return value; - } - - /** - * This exception acts as a signal, and should be low cost - * - * @return this - */ - @Override - public synchronized Throwable fillInStackTrace() { - // Do nothing - return this; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopIteration.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopIteration.java deleted file mode 100644 index a1d56f0f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/StopIteration.java +++ /dev/null @@ -1,56 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; - -/** - * Error thrown when a Python iterator has no more values to return. - */ -public class StopIteration extends PythonException { - public static final PythonLikeType STOP_ITERATION_TYPE = new PythonLikeType("StopIteration", - StopIteration.class, List.of(EXCEPTION_TYPE)), - $TYPE = STOP_ITERATION_TYPE; - - static { - STOP_ITERATION_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new StopIteration(STOP_ITERATION_TYPE, - positionalArguments))); - } - - private final PythonLikeObject value; - - public StopIteration() { - this(PythonNone.INSTANCE); - } - - public StopIteration(PythonLikeObject value) { - this(STOP_ITERATION_TYPE, List.of(value)); - } - - public StopIteration(PythonLikeType type, List args) { - super(type, args); - if (args.size() > 0) { - value = args.get(0); - } else { - value = PythonNone.INSTANCE; - } - } - - public PythonLikeObject getValue() { - return value; - } - - /** - * This exception acts as a signal, and should be low cost - * - * @return this - */ - @Override - public synchronized Throwable fillInStackTrace() { - // Do nothing - return this; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/TypeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/TypeError.java deleted file mode 100644 index f25662e2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/TypeError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class TypeError extends PythonException { - public final static PythonLikeType TYPE_ERROR_TYPE = - new PythonLikeType("TypeError", TypeError.class, List.of(EXCEPTION_TYPE)), - $TYPE = TYPE_ERROR_TYPE; - - static { - TYPE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new TypeError(TYPE_ERROR_TYPE, positionalArguments))); - } - - public TypeError() { - super(TYPE_ERROR_TYPE); - } - - public TypeError(String message) { - super(TYPE_ERROR_TYPE, message); - } - - public TypeError(PythonLikeType type, List args) { - super(type, args); - } - - public TypeError(PythonLikeType type) { - super(type); - } - - public TypeError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/UnboundLocalError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/UnboundLocalError.java deleted file mode 100644 index 0bcb4356..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/UnboundLocalError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UnboundLocalError extends NameError { - public final static PythonLikeType UNBOUND_LOCAL_ERROR_TYPE = - new PythonLikeType("UnboundLocalError", UnboundLocalError.class, List.of(NAME_ERROR_TYPE)), - $TYPE = UNBOUND_LOCAL_ERROR_TYPE; - - static { - UNBOUND_LOCAL_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new UnboundLocalError(UNBOUND_LOCAL_ERROR_TYPE, positionalArguments))); - } - - public UnboundLocalError() { - super(UNBOUND_LOCAL_ERROR_TYPE); - } - - public UnboundLocalError(String message) { - super(UNBOUND_LOCAL_ERROR_TYPE, message); - } - - public UnboundLocalError(PythonLikeType type, List args) { - super(type, args); - } - - public UnboundLocalError(PythonLikeType type) { - super(type); - } - - public UnboundLocalError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ValueError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ValueError.java deleted file mode 100644 index db9659d6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/ValueError.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class ValueError extends PythonException { - public final static PythonLikeType VALUE_ERROR_TYPE = - new PythonLikeType("ValueError", ValueError.class, List.of(EXCEPTION_TYPE)), - $TYPE = VALUE_ERROR_TYPE; - - static { - VALUE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ValueError(VALUE_ERROR_TYPE, - positionalArguments))); - } - - public ValueError() { - super(VALUE_ERROR_TYPE); - } - - public ValueError(String message) { - super(VALUE_ERROR_TYPE, message); - } - - public ValueError(PythonLikeType type, List args) { - super(type, args); - } - - public ValueError(PythonLikeType type) { - super(type); - } - - public ValueError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ArithmeticError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ArithmeticError.java deleted file mode 100644 index 453c3709..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ArithmeticError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.arithmetic; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonException; - -/** - * The base class for those built-in exceptions that are raised for various arithmetic errors - */ -public class ArithmeticError extends PythonException { - final public static PythonLikeType ARITHMETIC_ERROR_TYPE = - new PythonLikeType("ArithmeticError", ArithmeticError.class, List.of(EXCEPTION_TYPE)), - $TYPE = ARITHMETIC_ERROR_TYPE; - - static { - ARITHMETIC_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ArithmeticError(ARITHMETIC_ERROR_TYPE, - positionalArguments))); - } - - public ArithmeticError(PythonLikeType type) { - super(type); - } - - public ArithmeticError(PythonLikeType type, List args) { - super(type, args); - } - - public ArithmeticError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/FloatingPointError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/FloatingPointError.java deleted file mode 100644 index bcb89c17..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/FloatingPointError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.arithmetic; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * The base class for those built-in exceptions that are raised for various arithmetic errors - */ -public class FloatingPointError extends ArithmeticError { - final public static PythonLikeType FLOATING_POINT_ERROR_TYPE = - new PythonLikeType("FloatingPointError", FloatingPointError.class, List.of(ARITHMETIC_ERROR_TYPE)), - $TYPE = FLOATING_POINT_ERROR_TYPE; - - static { - FLOATING_POINT_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new FloatingPointError(FLOATING_POINT_ERROR_TYPE, positionalArguments))); - } - - public FloatingPointError(PythonLikeType type) { - super(type); - } - - public FloatingPointError(PythonLikeType type, List args) { - super(type, args); - } - - public FloatingPointError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/OverflowError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/OverflowError.java deleted file mode 100644 index bd6ea3fd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/OverflowError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.arithmetic; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * The base class for those built-in exceptions that are raised for various arithmetic errors - */ -public class OverflowError extends ArithmeticError { - final public static PythonLikeType OVERFLOW_ERROR_TYPE = - new PythonLikeType("OverflowError", OverflowError.class, List.of(ARITHMETIC_ERROR_TYPE)), - $TYPE = OVERFLOW_ERROR_TYPE; - - static { - OVERFLOW_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new OverflowError(OVERFLOW_ERROR_TYPE, - positionalArguments))); - } - - public OverflowError(PythonLikeType type) { - super(type); - } - - public OverflowError(PythonLikeType type, List args) { - super(type, args); - } - - public OverflowError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ZeroDivisionError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ZeroDivisionError.java deleted file mode 100644 index 8e9c1e9c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/arithmetic/ZeroDivisionError.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.arithmetic; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * The base class for those built-in exceptions that are raised for various arithmetic errors - */ -public class ZeroDivisionError extends ArithmeticError { - final public static PythonLikeType ZERO_DIVISION_ERROR_TYPE = - new PythonLikeType("ZeroDivisionError", ZeroDivisionError.class, List.of(ARITHMETIC_ERROR_TYPE)), - $TYPE = ZERO_DIVISION_ERROR_TYPE; - - static { - ZERO_DIVISION_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new ZeroDivisionError(ZERO_DIVISION_ERROR_TYPE, positionalArguments))); - } - - public ZeroDivisionError(String message) { - super(ZERO_DIVISION_ERROR_TYPE, message); - } - - public ZeroDivisionError(PythonLikeType type) { - super(type); - } - - public ZeroDivisionError(PythonLikeType type, List args) { - super(type, args); - } - - public ZeroDivisionError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/BlockingIOError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/BlockingIOError.java deleted file mode 100644 index 3848c7a6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/BlockingIOError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class BlockingIOError extends OSError { - final public static PythonLikeType BLOCKING_IO_ERROR_TYPE = - new PythonLikeType("BlockingIOError", BlockingIOError.class, List.of(OS_ERROR_TYPE)), - $TYPE = BLOCKING_IO_ERROR_TYPE; - - static { - BLOCKING_IO_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new BlockingIOError(BLOCKING_IO_ERROR_TYPE, - positionalArguments))); - } - - public BlockingIOError(PythonLikeType type) { - super(type); - } - - public BlockingIOError(PythonLikeType type, List args) { - super(type, args); - } - - public BlockingIOError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ChildProcessError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ChildProcessError.java deleted file mode 100644 index 8fae3102..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ChildProcessError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ChildProcessError extends OSError { - final public static PythonLikeType CHILD_PROCESS_ERROR_TYPE = - new PythonLikeType("ChildProcessError", ChildProcessError.class, List.of(OS_ERROR_TYPE)), - $TYPE = CHILD_PROCESS_ERROR_TYPE; - - static { - CHILD_PROCESS_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new ChildProcessError(CHILD_PROCESS_ERROR_TYPE, positionalArguments))); - } - - public ChildProcessError(PythonLikeType type) { - super(type); - } - - public ChildProcessError(PythonLikeType type, List args) { - super(type, args); - } - - public ChildProcessError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/EOFError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/EOFError.java deleted file mode 100644 index 20588a59..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/EOFError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class EOFError extends PythonBaseException { - final public static PythonLikeType EOF_ERROR_TYPE = - new PythonLikeType("EOFError", EOFError.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = EOF_ERROR_TYPE; - - static { - EOF_ERROR_TYPE - .setConstructor(((positionalArguments, namedArguments, callerInstance) -> new EOFError(EOF_ERROR_TYPE, - positionalArguments))); - } - - public EOFError(PythonLikeType type) { - super(type); - } - - public EOFError(PythonLikeType type, List args) { - super(type, args); - } - - public EOFError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileExistsError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileExistsError.java deleted file mode 100644 index df91dc57..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileExistsError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class FileExistsError extends OSError { - final public static PythonLikeType FILE_EXISTS_ERROR_TYPE = - new PythonLikeType("FileExistsError", FileExistsError.class, List.of(OS_ERROR_TYPE)), - $TYPE = FILE_EXISTS_ERROR_TYPE; - - static { - FILE_EXISTS_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new FileExistsError(FILE_EXISTS_ERROR_TYPE, - positionalArguments))); - } - - public FileExistsError(PythonLikeType type) { - super(type); - } - - public FileExistsError(PythonLikeType type, List args) { - super(type, args); - } - - public FileExistsError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileNotFoundError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileNotFoundError.java deleted file mode 100644 index 45ad30db..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/FileNotFoundError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class FileNotFoundError extends OSError { - final public static PythonLikeType FILE_NOT_FOUND_ERROR_TYPE = - new PythonLikeType("FileNotFoundError", FileNotFoundError.class, List.of(OS_ERROR_TYPE)), - $TYPE = FILE_NOT_FOUND_ERROR_TYPE; - - static { - FILE_NOT_FOUND_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new FileNotFoundError(FILE_NOT_FOUND_ERROR_TYPE, positionalArguments))); - } - - public FileNotFoundError(PythonLikeType type) { - super(type); - } - - public FileNotFoundError(PythonLikeType type, List args) { - super(type, args); - } - - public FileNotFoundError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/InterruptedError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/InterruptedError.java deleted file mode 100644 index 7719c404..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/InterruptedError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class InterruptedError extends OSError { - final public static PythonLikeType INTERRUPTED_ERROR_TYPE = - new PythonLikeType("InterruptedError", InterruptedError.class, List.of(OS_ERROR_TYPE)), - $TYPE = INTERRUPTED_ERROR_TYPE; - - static { - INTERRUPTED_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new InterruptedError(INTERRUPTED_ERROR_TYPE, - positionalArguments))); - } - - public InterruptedError(PythonLikeType type) { - super(type); - } - - public InterruptedError(PythonLikeType type, List args) { - super(type, args); - } - - public InterruptedError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/IsADirectoryError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/IsADirectoryError.java deleted file mode 100644 index fb4459e0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/IsADirectoryError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class IsADirectoryError extends OSError { - final public static PythonLikeType IS_A_DIRECTORY_ERROR_TYPE = - new PythonLikeType("IsADirectoryError", IsADirectoryError.class, List.of(OS_ERROR_TYPE)), - $TYPE = IS_A_DIRECTORY_ERROR_TYPE; - - static { - IS_A_DIRECTORY_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new IsADirectoryError(IS_A_DIRECTORY_ERROR_TYPE, positionalArguments))); - } - - public IsADirectoryError(PythonLikeType type) { - super(type); - } - - public IsADirectoryError(PythonLikeType type, List args) { - super(type, args); - } - - public IsADirectoryError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/KeyboardInterrupt.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/KeyboardInterrupt.java deleted file mode 100644 index ceaa1ea4..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/KeyboardInterrupt.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class KeyboardInterrupt extends PythonBaseException { - final public static PythonLikeType KEYBOARD_INTERRUPT_TYPE = - new PythonLikeType("KeyboardInterrupt", KeyboardInterrupt.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = KEYBOARD_INTERRUPT_TYPE; - - static { - KEYBOARD_INTERRUPT_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new KeyboardInterrupt(KEYBOARD_INTERRUPT_TYPE, - positionalArguments))); - } - - public KeyboardInterrupt(PythonLikeType type) { - super(type); - } - - public KeyboardInterrupt(PythonLikeType type, List args) { - super(type, args); - } - - public KeyboardInterrupt(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/MemoryError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/MemoryError.java deleted file mode 100644 index 63487528..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/MemoryError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class MemoryError extends PythonBaseException { - final public static PythonLikeType MEMORY_ERROR_TYPE = - new PythonLikeType("MemoryError", MemoryError.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = MEMORY_ERROR_TYPE; - - static { - MEMORY_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new MemoryError(MEMORY_ERROR_TYPE, - positionalArguments))); - } - - public MemoryError(PythonLikeType type) { - super(type); - } - - public MemoryError(PythonLikeType type, List args) { - super(type, args); - } - - public MemoryError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/NotADirectoryError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/NotADirectoryError.java deleted file mode 100644 index 292e05ba..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/NotADirectoryError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class NotADirectoryError extends OSError { - final public static PythonLikeType NOT_A_DIRECTORY_ERROR_TYPE = - new PythonLikeType("NotADirectoryError", NotADirectoryError.class, List.of(OS_ERROR_TYPE)), - $TYPE = NOT_A_DIRECTORY_ERROR_TYPE; - - static { - NOT_A_DIRECTORY_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new NotADirectoryError(NOT_A_DIRECTORY_ERROR_TYPE, positionalArguments))); - } - - public NotADirectoryError(PythonLikeType type) { - super(type); - } - - public NotADirectoryError(PythonLikeType type, List args) { - super(type, args); - } - - public NotADirectoryError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/OSError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/OSError.java deleted file mode 100644 index 074b18e8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/OSError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class OSError extends PythonBaseException { - final public static PythonLikeType OS_ERROR_TYPE = - new PythonLikeType("OSError", OSError.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = OS_ERROR_TYPE; - - static { - OS_ERROR_TYPE - .setConstructor(((positionalArguments, namedArguments, callerInstance) -> new OSError(OS_ERROR_TYPE, - positionalArguments))); - } - - public OSError(PythonLikeType type) { - super(type); - } - - public OSError(PythonLikeType type, List args) { - super(type, args); - } - - public OSError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/PermissionError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/PermissionError.java deleted file mode 100644 index ad32fe0c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/PermissionError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class PermissionError extends OSError { - final public static PythonLikeType PERMISSION_ERROR_TYPE = - new PythonLikeType("PermissionError", PermissionError.class, List.of(OS_ERROR_TYPE)), - $TYPE = PERMISSION_ERROR_TYPE; - - static { - PERMISSION_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new PermissionError(PERMISSION_ERROR_TYPE, - positionalArguments))); - } - - public PermissionError(PythonLikeType type) { - super(type); - } - - public PermissionError(PythonLikeType type, List args) { - super(type, args); - } - - public PermissionError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ProcessLookupError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ProcessLookupError.java deleted file mode 100644 index 6beb484c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/ProcessLookupError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ProcessLookupError extends OSError { - final public static PythonLikeType PROCESS_LOOKUP_ERROR_TYPE = - new PythonLikeType("ProcessLookupError", ProcessLookupError.class, List.of(OS_ERROR_TYPE)), - $TYPE = PROCESS_LOOKUP_ERROR_TYPE; - - static { - PROCESS_LOOKUP_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new ProcessLookupError(PROCESS_LOOKUP_ERROR_TYPE, positionalArguments))); - } - - public ProcessLookupError(PythonLikeType type) { - super(type); - } - - public ProcessLookupError(PythonLikeType type, List args) { - super(type, args); - } - - public ProcessLookupError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemError.java deleted file mode 100644 index 8f8b3f21..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class SystemError extends PythonBaseException { - final public static PythonLikeType SYSTEM_ERROR_TYPE = - new PythonLikeType("SystemError", SystemError.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = SYSTEM_ERROR_TYPE; - - static { - SYSTEM_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new SystemError(SYSTEM_ERROR_TYPE, - positionalArguments))); - } - - public SystemError(PythonLikeType type) { - super(type); - } - - public SystemError(PythonLikeType type, List args) { - super(type, args); - } - - public SystemError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemExit.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemExit.java deleted file mode 100644 index 3f89e8c1..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/SystemExit.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class SystemExit extends PythonBaseException { - final public static PythonLikeType SYSTEM_EXIT_TYPE = - new PythonLikeType("SystemExit", SystemExit.class, List.of(PythonBaseException.BASE_EXCEPTION_TYPE)), - $TYPE = SYSTEM_EXIT_TYPE; - - static { - SYSTEM_EXIT_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new SystemExit(SYSTEM_EXIT_TYPE, - positionalArguments))); - } - - public SystemExit(PythonLikeType type) { - super(type); - } - - public SystemExit(PythonLikeType type, List args) { - super(type, args); - } - - public SystemExit(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/TimeoutError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/TimeoutError.java deleted file mode 100644 index 42414ad7..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/TimeoutError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class TimeoutError extends OSError { - final public static PythonLikeType TIMEOUT_ERROR_TYPE = - new PythonLikeType("TimeoutError", TimeoutError.class, List.of(OS_ERROR_TYPE)), - $TYPE = TIMEOUT_ERROR_TYPE; - - static { - TIMEOUT_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new TimeoutError(TIMEOUT_ERROR_TYPE, - positionalArguments))); - } - - public TimeoutError(PythonLikeType type) { - super(type); - } - - public TimeoutError(PythonLikeType type, List args) { - super(type, args); - } - - public TimeoutError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/BrokenPipeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/BrokenPipeError.java deleted file mode 100644 index b2c78afd..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/BrokenPipeError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io.connection; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class BrokenPipeError extends ConnectionError { - final public static PythonLikeType BROKEN_PIPE_ERROR_TYPE = - new PythonLikeType("BrokenPipeError", BrokenPipeError.class, List.of(CONNECTION_ERROR_TYPE)), - $TYPE = BROKEN_PIPE_ERROR_TYPE; - - static { - BROKEN_PIPE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new BrokenPipeError(BROKEN_PIPE_ERROR_TYPE, - positionalArguments))); - } - - public BrokenPipeError(PythonLikeType type) { - super(type); - } - - public BrokenPipeError(PythonLikeType type, List args) { - super(type, args); - } - - public BrokenPipeError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionAbortedError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionAbortedError.java deleted file mode 100644 index 35b96b31..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionAbortedError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io.connection; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ConnectionAbortedError extends ConnectionError { - final public static PythonLikeType CONNECTION_ABORTED_ERROR_TYPE = - new PythonLikeType("ConnectionAbortedError", ConnectionAbortedError.class, List.of(CONNECTION_ERROR_TYPE)), - $TYPE = CONNECTION_ABORTED_ERROR_TYPE; - - static { - CONNECTION_ABORTED_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, - callerInstance) -> new ConnectionAbortedError(CONNECTION_ABORTED_ERROR_TYPE, positionalArguments))); - } - - public ConnectionAbortedError(PythonLikeType type) { - super(type); - } - - public ConnectionAbortedError(PythonLikeType type, List args) { - super(type, args); - } - - public ConnectionAbortedError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionError.java deleted file mode 100644 index 59ac0130..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io.connection; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.io.OSError; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ConnectionError extends OSError { - final public static PythonLikeType CONNECTION_ERROR_TYPE = - new PythonLikeType("ConnectionError", ConnectionError.class, List.of(OS_ERROR_TYPE)), - $TYPE = CONNECTION_ERROR_TYPE; - - static { - CONNECTION_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ConnectionError(CONNECTION_ERROR_TYPE, - positionalArguments))); - } - - public ConnectionError(PythonLikeType type) { - super(type); - } - - public ConnectionError(PythonLikeType type, List args) { - super(type, args); - } - - public ConnectionError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionRefusedError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionRefusedError.java deleted file mode 100644 index 8c3c8b0b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionRefusedError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io.connection; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ConnectionRefusedError extends ConnectionError { - final public static PythonLikeType CONNECTION_REFUSED_ERROR_TYPE = - new PythonLikeType("ConnectionRefusedError", ConnectionRefusedError.class, List.of(CONNECTION_ERROR_TYPE)), - $TYPE = CONNECTION_REFUSED_ERROR_TYPE; - - static { - CONNECTION_REFUSED_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, - callerInstance) -> new ConnectionRefusedError(CONNECTION_REFUSED_ERROR_TYPE, positionalArguments))); - } - - public ConnectionRefusedError(PythonLikeType type) { - super(type); - } - - public ConnectionRefusedError(PythonLikeType type, List args) { - super(type, args); - } - - public ConnectionRefusedError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionResetError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionResetError.java deleted file mode 100644 index ef07bc37..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/io/connection/ConnectionResetError.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.io.connection; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class ConnectionResetError extends ConnectionError { - final public static PythonLikeType CONNECTION_RESET_ERROR_TYPE = - new PythonLikeType("ConnectionResetError", ConnectionResetError.class, List.of(CONNECTION_ERROR_TYPE)), - $TYPE = CONNECTION_RESET_ERROR_TYPE; - - static { - CONNECTION_RESET_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new ConnectionResetError(CONNECTION_RESET_ERROR_TYPE, positionalArguments))); - } - - public ConnectionResetError(PythonLikeType type) { - super(type); - } - - public ConnectionResetError(PythonLikeType type, List args) { - super(type, args); - } - - public ConnectionResetError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/IndexError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/IndexError.java deleted file mode 100644 index a66e86ab..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/IndexError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.lookup; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * The base class for the exceptions that are raised when a key or index used on a mapping or sequence is invalid. - */ -public class IndexError extends LookupError { - final public static PythonLikeType INDEX_ERROR_TYPE = - new PythonLikeType("IndexError", IndexError.class, List.of(LOOKUP_ERROR_TYPE)), - $TYPE = INDEX_ERROR_TYPE; - - static { - INDEX_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new IndexError(INDEX_ERROR_TYPE, - positionalArguments))); - } - - public IndexError(PythonLikeType type) { - super(type); - } - - public IndexError(String message) { - super(INDEX_ERROR_TYPE, message); - } - - public IndexError(PythonLikeType type, List args) { - super(type, args); - } - - public IndexError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/KeyError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/KeyError.java deleted file mode 100644 index 4caeea71..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/KeyError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.lookup; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * The base class for the exceptions that are raised when a key or index used on a mapping or sequence is invalid. - */ -public class KeyError extends LookupError { - final public static PythonLikeType KEY_ERROR_TYPE = - new PythonLikeType("KeyError", KeyError.class, List.of(LOOKUP_ERROR_TYPE)), - $TYPE = KEY_ERROR_TYPE; - - static { - KEY_ERROR_TYPE - .setConstructor(((positionalArguments, namedArguments, callerInstance) -> new KeyError(KEY_ERROR_TYPE, - positionalArguments))); - } - - public KeyError(PythonLikeType type) { - super(type); - } - - public KeyError(String message) { - super(KEY_ERROR_TYPE, message); - } - - public KeyError(PythonLikeType type, List args) { - super(type, args); - } - - public KeyError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/LookupError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/LookupError.java deleted file mode 100644 index f7486259..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/lookup/LookupError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.lookup; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonException; - -/** - * The base class for the exceptions that are raised when a key or index used on a mapping or sequence is invalid. - */ -public class LookupError extends PythonException { - final public static PythonLikeType LOOKUP_ERROR_TYPE = - new PythonLikeType("LookupError", LookupError.class, List.of(EXCEPTION_TYPE)), - $TYPE = LOOKUP_ERROR_TYPE; - - static { - LOOKUP_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new LookupError(LOOKUP_ERROR_TYPE, - positionalArguments))); - } - - public LookupError(PythonLikeType type) { - super(type); - } - - public LookupError(PythonLikeType type, List args) { - super(type, args); - } - - public LookupError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/IndentationError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/IndentationError.java deleted file mode 100644 index ed9d556f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/IndentationError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.syntax; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class IndentationError extends SyntaxError { - final public static PythonLikeType INDENTATION_ERROR_TYPE = - new PythonLikeType("IndentationError", IndentationError.class, List.of(SYNTAX_ERROR_TYPE)), - $TYPE = INDENTATION_ERROR_TYPE; - - static { - INDENTATION_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new IndentationError(INDENTATION_ERROR_TYPE, - positionalArguments))); - } - - public IndentationError(PythonLikeType type) { - super(type); - } - - public IndentationError(PythonLikeType type, List args) { - super(type, args); - } - - public IndentationError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/SyntaxError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/SyntaxError.java deleted file mode 100644 index 1ba5539b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/SyntaxError.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.syntax; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonException; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class SyntaxError extends PythonException { - final public static PythonLikeType SYNTAX_ERROR_TYPE = - new PythonLikeType("SyntaxError", SyntaxError.class, List.of(EXCEPTION_TYPE)), - $TYPE = SYNTAX_ERROR_TYPE; - - static { - SYNTAX_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new SyntaxError(SYNTAX_ERROR_TYPE, - positionalArguments))); - } - - public SyntaxError(PythonLikeType type) { - super(type); - } - - public SyntaxError(PythonLikeType type, List args) { - super(type, args); - } - - public SyntaxError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/TabError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/TabError.java deleted file mode 100644 index 856dd542..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/syntax/TabError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.syntax; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Raised when a buffer related operation cannot be performed. - */ -public class TabError extends IndentationError { - final public static PythonLikeType TAB_ERROR_TYPE = - new PythonLikeType("TabError", TabError.class, List.of(INDENTATION_ERROR_TYPE)), - $TYPE = TAB_ERROR_TYPE; - - static { - TAB_ERROR_TYPE - .setConstructor(((positionalArguments, namedArguments, callerInstance) -> new TabError(TAB_ERROR_TYPE, - positionalArguments))); - } - - public TabError(PythonLikeType type) { - super(type); - } - - public TabError(PythonLikeType type, List args) { - super(type, args); - } - - public TabError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeDecodeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeDecodeError.java deleted file mode 100644 index a7ae0b37..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeDecodeError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.unicode; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UnicodeDecodeError extends UnicodeError { - public final static PythonLikeType UNICODE_DECODE_ERROR_TYPE = - new PythonLikeType("UnicodeDecodeError", UnicodeDecodeError.class, List.of(UNICODE_ERROR_TYPE)), - $TYPE = UNICODE_DECODE_ERROR_TYPE; - - static { - UNICODE_DECODE_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new UnicodeDecodeError(UNICODE_DECODE_ERROR_TYPE, positionalArguments))); - } - - public UnicodeDecodeError() { - super(UNICODE_DECODE_ERROR_TYPE); - } - - public UnicodeDecodeError(String message) { - super(UNICODE_DECODE_ERROR_TYPE, message); - } - - public UnicodeDecodeError(PythonLikeType type, List args) { - super(type, args); - } - - public UnicodeDecodeError(PythonLikeType type) { - super(type); - } - - public UnicodeDecodeError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeEncodeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeEncodeError.java deleted file mode 100644 index c913573e..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeEncodeError.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.unicode; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UnicodeEncodeError extends UnicodeError { - public final static PythonLikeType UNICODE_ENCODE_ERROR_TYPE = - new PythonLikeType("UnicodeEncodeError", UnicodeEncodeError.class, List.of(UNICODE_ERROR_TYPE)), - $TYPE = UNICODE_ENCODE_ERROR_TYPE; - - static { - UNICODE_ENCODE_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new UnicodeEncodeError(UNICODE_ENCODE_ERROR_TYPE, positionalArguments))); - } - - public UnicodeEncodeError() { - super(UNICODE_ENCODE_ERROR_TYPE); - } - - public UnicodeEncodeError(String message) { - super(UNICODE_ENCODE_ERROR_TYPE, message); - } - - public UnicodeEncodeError(PythonLikeType type, List args) { - super(type, args); - } - - public UnicodeEncodeError(PythonLikeType type) { - super(type); - } - - public UnicodeEncodeError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeError.java deleted file mode 100644 index 40acff41..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeError.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.unicode; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class UnicodeError extends ValueError { - public final static PythonLikeType UNICODE_ERROR_TYPE = - new PythonLikeType("UnicodeError", UnicodeError.class, List.of(VALUE_ERROR_TYPE)), - $TYPE = UNICODE_ERROR_TYPE; - - static { - UNICODE_ERROR_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new UnicodeError(UNICODE_ERROR_TYPE, - positionalArguments))); - } - - public UnicodeError() { - super(UNICODE_ERROR_TYPE); - } - - public UnicodeError(String message) { - super(UNICODE_ERROR_TYPE, message); - } - - public UnicodeError(PythonLikeType type, List args) { - super(type, args); - } - - public UnicodeError(PythonLikeType type) { - super(type); - } - - public UnicodeError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeTranslateError.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeTranslateError.java deleted file mode 100644 index 6e275882..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/unicode/UnicodeTranslateError.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.unicode; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UnicodeTranslateError extends UnicodeError { - public final static PythonLikeType UNICODE_TRANSLATE_ERROR_TYPE = - new PythonLikeType("UnicodeTranslateError", UnicodeTranslateError.class, List.of(UNICODE_ERROR_TYPE)), - $TYPE = UNICODE_TRANSLATE_ERROR_TYPE; - - static { - UNICODE_TRANSLATE_ERROR_TYPE.setConstructor(((positionalArguments, - namedArguments, - callerInstance) -> new UnicodeTranslateError(UNICODE_TRANSLATE_ERROR_TYPE, positionalArguments))); - } - - public UnicodeTranslateError() { - super(UNICODE_TRANSLATE_ERROR_TYPE); - } - - public UnicodeTranslateError(String message) { - super(UNICODE_TRANSLATE_ERROR_TYPE, message); - } - - public UnicodeTranslateError(PythonLikeType type, List args) { - super(type, args); - } - - public UnicodeTranslateError(PythonLikeType type) { - super(type); - } - - public UnicodeTranslateError(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/BytesWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/BytesWarning.java deleted file mode 100644 index 6a6445c7..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/BytesWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class BytesWarning extends Warning { - public final static PythonLikeType BYTES_WARNING_TYPE = - new PythonLikeType("BytesWarning", BytesWarning.class, List.of(WARNING_TYPE)), - $TYPE = BYTES_WARNING_TYPE; - - static { - BYTES_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new BytesWarning(BYTES_WARNING_TYPE, - positionalArguments))); - } - - public BytesWarning() { - super(BYTES_WARNING_TYPE); - } - - public BytesWarning(String message) { - super(BYTES_WARNING_TYPE, message); - } - - public BytesWarning(PythonLikeType type, List args) { - super(type, args); - } - - public BytesWarning(PythonLikeType type) { - super(type); - } - - public BytesWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/DeprecationWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/DeprecationWarning.java deleted file mode 100644 index f22baeb0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/DeprecationWarning.java +++ /dev/null @@ -1,37 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class DeprecationWarning extends Warning { - public final static PythonLikeType DEPRECATION_WARNING_TYPE = - new PythonLikeType("DeprecationWarning", DeprecationWarning.class, List.of(WARNING_TYPE)), - $TYPE = DEPRECATION_WARNING_TYPE; - - static { - DEPRECATION_WARNING_TYPE.setConstructor(((positionalArguments, - namedArguments, callerInstance) -> new DeprecationWarning(DEPRECATION_WARNING_TYPE, positionalArguments))); - } - - public DeprecationWarning() { - super(DEPRECATION_WARNING_TYPE); - } - - public DeprecationWarning(String message) { - super(DEPRECATION_WARNING_TYPE, message); - } - - public DeprecationWarning(PythonLikeType type, List args) { - super(type, args); - } - - public DeprecationWarning(PythonLikeType type) { - super(type); - } - - public DeprecationWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/EncodingWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/EncodingWarning.java deleted file mode 100644 index 976bce0b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/EncodingWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class EncodingWarning extends Warning { - public final static PythonLikeType ENCODING_WARNING_TYPE = - new PythonLikeType("EncodingWarning", EncodingWarning.class, List.of(WARNING_TYPE)), - $TYPE = ENCODING_WARNING_TYPE; - - static { - ENCODING_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new EncodingWarning(ENCODING_WARNING_TYPE, - positionalArguments))); - } - - public EncodingWarning() { - super(ENCODING_WARNING_TYPE); - } - - public EncodingWarning(String message) { - super(ENCODING_WARNING_TYPE, message); - } - - public EncodingWarning(PythonLikeType type, List args) { - super(type, args); - } - - public EncodingWarning(PythonLikeType type) { - super(type); - } - - public EncodingWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/FutureWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/FutureWarning.java deleted file mode 100644 index 79858512..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/FutureWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class FutureWarning extends Warning { - public final static PythonLikeType FUTURE_WARNING_TYPE = - new PythonLikeType("FutureWarning", FutureWarning.class, List.of(WARNING_TYPE)), - $TYPE = FUTURE_WARNING_TYPE; - - static { - FUTURE_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new FutureWarning(FUTURE_WARNING_TYPE, - positionalArguments))); - } - - public FutureWarning() { - super(FUTURE_WARNING_TYPE); - } - - public FutureWarning(String message) { - super(FUTURE_WARNING_TYPE, message); - } - - public FutureWarning(PythonLikeType type, List args) { - super(type, args); - } - - public FutureWarning(PythonLikeType type) { - super(type); - } - - public FutureWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ImportWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ImportWarning.java deleted file mode 100644 index 631df805..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ImportWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class ImportWarning extends Warning { - public final static PythonLikeType IMPORT_WARNING_TYPE = - new PythonLikeType("ImportWarning", ImportWarning.class, List.of(WARNING_TYPE)), - $TYPE = IMPORT_WARNING_TYPE; - - static { - IMPORT_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ImportWarning(IMPORT_WARNING_TYPE, - positionalArguments))); - } - - public ImportWarning() { - super(IMPORT_WARNING_TYPE); - } - - public ImportWarning(String message) { - super(IMPORT_WARNING_TYPE, message); - } - - public ImportWarning(PythonLikeType type, List args) { - super(type, args); - } - - public ImportWarning(PythonLikeType type) { - super(type); - } - - public ImportWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/PendingDeprecationWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/PendingDeprecationWarning.java deleted file mode 100644 index e5eef6f0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/PendingDeprecationWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class PendingDeprecationWarning extends Warning { - public final static PythonLikeType PENDING_DEPRECATION_WARNING_TYPE = - new PythonLikeType("PendingDeprecationWarning", PendingDeprecationWarning.class, List.of(WARNING_TYPE)), - $TYPE = PENDING_DEPRECATION_WARNING_TYPE; - - static { - PENDING_DEPRECATION_WARNING_TYPE.setConstructor(((positionalArguments, - namedArguments, - callerInstance) -> new PendingDeprecationWarning(PENDING_DEPRECATION_WARNING_TYPE, positionalArguments))); - } - - public PendingDeprecationWarning() { - super(PENDING_DEPRECATION_WARNING_TYPE); - } - - public PendingDeprecationWarning(String message) { - super(PENDING_DEPRECATION_WARNING_TYPE, message); - } - - public PendingDeprecationWarning(PythonLikeType type, List args) { - super(type, args); - } - - public PendingDeprecationWarning(PythonLikeType type) { - super(type); - } - - public PendingDeprecationWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ResourceWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ResourceWarning.java deleted file mode 100644 index 4b93f1e6..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/ResourceWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class ResourceWarning extends Warning { - public final static PythonLikeType RESOURCE_WARNING_TYPE = - new PythonLikeType("ResourceWarning", ResourceWarning.class, List.of(WARNING_TYPE)), - $TYPE = RESOURCE_WARNING_TYPE; - - static { - RESOURCE_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new ResourceWarning(RESOURCE_WARNING_TYPE, - positionalArguments))); - } - - public ResourceWarning() { - super(RESOURCE_WARNING_TYPE); - } - - public ResourceWarning(String message) { - super(RESOURCE_WARNING_TYPE, message); - } - - public ResourceWarning(PythonLikeType type, List args) { - super(type, args); - } - - public ResourceWarning(PythonLikeType type) { - super(type); - } - - public ResourceWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/RuntimeWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/RuntimeWarning.java deleted file mode 100644 index adf5f941..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/RuntimeWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class RuntimeWarning extends Warning { - public final static PythonLikeType RUNTIME_WARNING_TYPE = - new PythonLikeType("RuntimeWarning", RuntimeWarning.class, List.of(WARNING_TYPE)), - $TYPE = RUNTIME_WARNING_TYPE; - - static { - RUNTIME_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new RuntimeWarning(RUNTIME_WARNING_TYPE, - positionalArguments))); - } - - public RuntimeWarning() { - super(RUNTIME_WARNING_TYPE); - } - - public RuntimeWarning(String message) { - super(RUNTIME_WARNING_TYPE, message); - } - - public RuntimeWarning(PythonLikeType type, List args) { - super(type, args); - } - - public RuntimeWarning(PythonLikeType type) { - super(type); - } - - public RuntimeWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/SyntaxWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/SyntaxWarning.java deleted file mode 100644 index 88a61889..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/SyntaxWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class SyntaxWarning extends Warning { - public final static PythonLikeType SYNTAX_WARNING_TYPE = - new PythonLikeType("SyntaxWarning", SyntaxWarning.class, List.of(WARNING_TYPE)), - $TYPE = SYNTAX_WARNING_TYPE; - - static { - SYNTAX_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new SyntaxWarning(SYNTAX_WARNING_TYPE, - positionalArguments))); - } - - public SyntaxWarning() { - super(SYNTAX_WARNING_TYPE); - } - - public SyntaxWarning(String message) { - super(SYNTAX_WARNING_TYPE, message); - } - - public SyntaxWarning(PythonLikeType type, List args) { - super(type, args); - } - - public SyntaxWarning(PythonLikeType type) { - super(type); - } - - public SyntaxWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UnicodeWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UnicodeWarning.java deleted file mode 100644 index edac0233..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UnicodeWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UnicodeWarning extends Warning { - public final static PythonLikeType UNICODE_WARNING_TYPE = - new PythonLikeType("UnicodeWarning", UnicodeWarning.class, List.of(WARNING_TYPE)), - $TYPE = UNICODE_WARNING_TYPE; - - static { - UNICODE_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new UnicodeWarning(UNICODE_WARNING_TYPE, - positionalArguments))); - } - - public UnicodeWarning() { - super(UNICODE_WARNING_TYPE); - } - - public UnicodeWarning(String message) { - super(UNICODE_WARNING_TYPE, message); - } - - public UnicodeWarning(PythonLikeType type, List args) { - super(type, args); - } - - public UnicodeWarning(PythonLikeType type) { - super(type); - } - - public UnicodeWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UserWarning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UserWarning.java deleted file mode 100644 index 1f84bf23..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/UserWarning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public class UserWarning extends Warning { - public final static PythonLikeType USER_WARNING_TYPE = - new PythonLikeType("UserWarning", UserWarning.class, List.of(WARNING_TYPE)), - $TYPE = USER_WARNING_TYPE; - - static { - USER_WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new UserWarning(USER_WARNING_TYPE, - positionalArguments))); - } - - public UserWarning() { - super(USER_WARNING_TYPE); - } - - public UserWarning(String message) { - super(USER_WARNING_TYPE, message); - } - - public UserWarning(PythonLikeType type, List args) { - super(type, args); - } - - public UserWarning(PythonLikeType type) { - super(type); - } - - public UserWarning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/Warning.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/Warning.java deleted file mode 100644 index 23265d2d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/warning/Warning.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.jpyinterpreter.types.errors.warning; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.PythonException; - -public class Warning extends PythonException { - public final static PythonLikeType WARNING_TYPE = - new PythonLikeType("Warning", Warning.class, List.of(PythonException.EXCEPTION_TYPE)), - $TYPE = WARNING_TYPE; - - static { - WARNING_TYPE.setConstructor( - ((positionalArguments, namedArguments, callerInstance) -> new Warning(WARNING_TYPE, positionalArguments))); - } - - public Warning() { - super(WARNING_TYPE); - } - - public Warning(String message) { - super(WARNING_TYPE, message); - } - - public Warning(PythonLikeType type, List args) { - super(type, args); - } - - public Warning(PythonLikeType type) { - super(type); - } - - public Warning(PythonLikeType type, String message) { - super(type, message); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonBoolean.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonBoolean.java deleted file mode 100644 index ca173a2a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonBoolean.java +++ /dev/null @@ -1,160 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.math.BigInteger; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class PythonBoolean extends PythonInteger { - public final static PythonBoolean TRUE = new PythonBoolean(true); - public final static PythonBoolean FALSE = new PythonBoolean(false); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonBoolean::registerMethods); - } - - public static PythonLikeType registerMethods() { - try { - BuiltinTypes.BOOLEAN_TYPE.addUnaryMethod(PythonUnaryOperator.AS_BOOLEAN, - new PythonFunctionSignature(new MethodDescriptor( - PythonBoolean.class.getMethod("asBoolean")), - BuiltinTypes.BOOLEAN_TYPE)); - BuiltinTypes.BOOLEAN_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, - new PythonFunctionSignature(new MethodDescriptor( - PythonBoolean.class.getMethod("asString")), - BuiltinTypes.STRING_TYPE)); - BuiltinTypes.BOOLEAN_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, - new PythonFunctionSignature(new MethodDescriptor( - PythonBoolean.class.getMethod("asString")), - BuiltinTypes.STRING_TYPE)); - BuiltinTypes.BOOLEAN_TYPE.setConstructor(((positionalArguments, namedArguments, callerInstance) -> { - if (namedArguments.size() > 1) { - throw new ValueError("bool does not take named arguments"); - } - if (positionalArguments.isEmpty()) { - return FALSE; - } else if (positionalArguments.size() == 1) { - return PythonBoolean.valueOf(PythonBoolean.isTruthful(positionalArguments.get(0))); - } else { - throw new ValueError("bool expects 0 or 1 arguments, got " + positionalArguments.size()); - } - })); - - GlobalBuiltins.addBuiltinConstant("True", TRUE); - GlobalBuiltins.addBuiltinConstant("False", FALSE); - return BuiltinTypes.BOOLEAN_TYPE; - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private final boolean booleanValue; - - private PythonBoolean(boolean booleanValue) { - super(BuiltinTypes.BOOLEAN_TYPE, booleanValue ? BigInteger.ONE : BigInteger.ZERO); - this.booleanValue = booleanValue; - } - - public boolean getBooleanValue() { - return booleanValue; - } - - public PythonBoolean not() { - if (this == TRUE) { - return FALSE; - } else { - return TRUE; - } - } - - public static boolean isTruthful(PythonLikeObject tested) { - if (tested instanceof PythonBoolean) { - return tested == TRUE; - } else if (tested instanceof PythonInteger) { - return ((PythonInteger) tested).asBoolean() == TRUE; - } else if (tested instanceof PythonFloat) { - return ((PythonFloat) tested).asBoolean() == TRUE; - } else if (tested instanceof PythonNone) { - return false; - } else if (tested instanceof Collection) { - return ((Collection) tested).size() == 0; - } else if (tested instanceof Map) { - return ((Map) tested).size() == 0; - } else { - PythonLikeType testedType = tested.$getType(); - PythonLikeFunction boolMethod = (PythonLikeFunction) testedType.$getAttributeOrNull("__bool__"); - if (boolMethod != null) { - return isTruthful(boolMethod.$call(List.of(tested), Map.of(), null)); - } - - PythonLikeFunction lenMethod = (PythonLikeFunction) testedType.$getAttributeOrNull("__len__"); - if (lenMethod != null) { - return isTruthful(lenMethod.$call(List.of(tested), Map.of(), null)); - } - - return true; - } - } - - public PythonBoolean asBoolean() { - return this; - } - - public static PythonBoolean valueOf(boolean result) { - return (result) ? TRUE : FALSE; - } - - @Override - public PythonLikeType $getType() { - return BuiltinTypes.BOOLEAN_TYPE; - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - if (this == TRUE) { - return "True"; - } else { - return "False"; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PythonBoolean that = (PythonBoolean) o; - return booleanValue == that.booleanValue; - } - - @Override - public int hashCode() { - return Objects.hash(booleanValue); - } - - public PythonString asString() { - return PythonString.valueOf(toString()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonComplex.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonComplex.java deleted file mode 100644 index 49610f39..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonComplex.java +++ /dev/null @@ -1,48 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.util.List; - -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonComplex extends AbstractPythonLikeObject implements PythonNumber, PlanningImmutable { - final PythonNumber real; - final PythonNumber imaginary; - - public final static PythonLikeType COMPLEX_TYPE = new PythonLikeType("complex", PythonComplex.class, List.of(NUMBER_TYPE)); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonComplex::registerMethods); - } - - public PythonComplex(PythonNumber real, PythonNumber imaginary) { - super(COMPLEX_TYPE); - this.real = real; - this.imaginary = imaginary; - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // TODO - return COMPLEX_TYPE; - } - - public PythonComplex valueOf(PythonNumber real, PythonNumber imaginary) { - return new PythonComplex(real, imaginary); - } - - @Override - public Number getValue() { - return (real.getValue().doubleValue() * real.getValue().doubleValue()) + - (imaginary.getValue().doubleValue() * imaginary.getValue().doubleValue()); - } - - public PythonNumber getReal() { - return real; - } - - public PythonNumber getImaginary() { - return imaginary; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonDecimal.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonDecimal.java deleted file mode 100644 index 53182617..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonDecimal.java +++ /dev/null @@ -1,805 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.function.BiPredicate; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.PythonClassTranslator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonDecimal extends AbstractPythonLikeObject implements PythonNumber, PlanningImmutable { - public final BigDecimal value; - private static final ThreadLocal threadMathContext = - ThreadLocal.withInitial(() -> new MathContext(28, RoundingMode.HALF_EVEN)); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonDecimal::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.DECIMAL_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.size() == 0) { - return new PythonDecimal(BigDecimal.ZERO); - } else if (positionalArguments.size() == 1) { - return PythonDecimal.from(positionalArguments.get(0)); - } else if (positionalArguments.size() == 2) { - // TODO: Support context - throw new ValueError("context constructor not supported"); - } else { - throw new TypeError("function takes at most 2 arguments, got " + positionalArguments.size()); - } - }); - - for (var method : PythonDecimal.class.getDeclaredMethods()) { - if (method.getName().startsWith(PythonClassTranslator.JAVA_METHOD_PREFIX)) { - BuiltinTypes.DECIMAL_TYPE.addMethod( - method.getName().substring(PythonClassTranslator.JAVA_METHOD_PREFIX.length()), - method); - } - } - - return BuiltinTypes.DECIMAL_TYPE; - } - - // *************************** - // Constructors - // *************************** - public PythonDecimal(BigDecimal value) { - super(BuiltinTypes.DECIMAL_TYPE); - this.value = value; - } - - public static PythonDecimal from(PythonLikeObject value) { - if (value instanceof PythonInteger integer) { - return PythonDecimal.valueOf(integer); - } else if (value instanceof PythonFloat pythonFloat) { - return PythonDecimal.valueOf(pythonFloat); - } else if (value instanceof PythonString str) { - return PythonDecimal.valueOf(str); - } else { - throw new TypeError( - "conversion from %s to Decimal is not supported".formatted(value.$getType().getTypeName())); - } - } - - public static PythonDecimal $method$from_float(PythonFloat value) { - return new PythonDecimal(new BigDecimal(value.value, threadMathContext.get())); - } - - public static PythonDecimal valueOf(PythonInteger value) { - return new PythonDecimal(new BigDecimal(value.value, threadMathContext.get())); - } - - public static PythonDecimal valueOf(PythonFloat value) { - return new PythonDecimal(new BigDecimal(value.value, threadMathContext.get())); - } - - public static PythonDecimal valueOf(PythonString value) { - return valueOf(value.value); - } - - public static PythonDecimal valueOf(String value) { - return new PythonDecimal(new BigDecimal(value, threadMathContext.get())); - } - - // *************************** - // Interface methods - // *************************** - - @Override - public Number getValue() { - return value; - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public PythonString $method$__repr__() { - return PythonString.valueOf("Decimal('%s')".formatted(value.toPlainString())); - } - - @Override - public String toString() { - return value.toPlainString(); - } - - public boolean equals(Object o) { - if (o instanceof PythonNumber number) { - return compareTo(number) == 0; - } else { - return false; - } - } - - @Override - public int hashCode() { - return $method$__hash__().value.intValue(); - } - - @Override - public PythonInteger $method$__hash__() { - var scale = value.scale(); - if (scale <= 0) { - return PythonNumber.computeHash(new PythonInteger(value.toBigInteger()), - PythonInteger.ONE); - } - var scaledValue = value.movePointRight(scale); - return PythonNumber.computeHash(new PythonInteger(scaledValue.toBigInteger()), - new PythonInteger(BigInteger.TEN.pow(scale))); - } - - // *************************** - // Unary operations - // *************************** - public PythonBoolean $method$__bool__() { - return PythonBoolean.valueOf(value.compareTo(BigDecimal.ZERO) != 0); - } - - public PythonInteger $method$__int__() { - return PythonInteger.valueOf(value.toBigInteger()); - } - - public PythonFloat $method$__float__() { - return PythonFloat.valueOf(value.doubleValue()); - } - - public PythonDecimal $method$__pos__() { - return this; - } - - public PythonDecimal $method$__neg__() { - return new PythonDecimal(value.negate()); - } - - public PythonDecimal $method$__abs__() { - return new PythonDecimal(value.abs()); - } - - // *************************** - // Binary operations - // *************************** - public PythonBoolean $method$__lt__(PythonDecimal other) { - return PythonBoolean.valueOf(value.compareTo(other.value) < 0); - } - - public PythonBoolean $method$__lt__(PythonInteger other) { - return $method$__lt__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__lt__(PythonFloat other) { - return $method$__lt__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__le__(PythonDecimal other) { - return PythonBoolean.valueOf(value.compareTo(other.value) <= 0); - } - - public PythonBoolean $method$__le__(PythonInteger other) { - return $method$__le__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__le__(PythonFloat other) { - return $method$__le__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__gt__(PythonDecimal other) { - return PythonBoolean.valueOf(value.compareTo(other.value) > 0); - } - - public PythonBoolean $method$__gt__(PythonInteger other) { - return $method$__gt__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__gt__(PythonFloat other) { - return $method$__gt__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__ge__(PythonDecimal other) { - return PythonBoolean.valueOf(value.compareTo(other.value) >= 0); - } - - public PythonBoolean $method$__ge__(PythonInteger other) { - return $method$__ge__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__ge__(PythonFloat other) { - return $method$__ge__(PythonDecimal.valueOf(other)); - } - - public PythonBoolean $method$__eq__(PythonDecimal other) { - return PythonBoolean.valueOf(value.compareTo(other.value) == 0); - } - - public PythonBoolean $method$__eq__(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(new BigDecimal(other.value)) == 0); - } - - public PythonBoolean $method$__eq__(PythonFloat other) { - return PythonBoolean.valueOf(value.compareTo(new BigDecimal(other.value)) == 0); - } - - public PythonBoolean $method$__neq__(PythonDecimal other) { - return $method$__eq__(other).not(); - } - - public PythonBoolean $method$__neq__(PythonInteger other) { - return $method$__eq__(other).not(); - } - - public PythonBoolean $method$__neq__(PythonFloat other) { - return $method$__eq__(other).not(); - } - - public PythonDecimal $method$__add__(PythonDecimal other) { - return new PythonDecimal(value.add(other.value, threadMathContext.get())); - } - - public PythonDecimal $method$__add__(PythonInteger other) { - return $method$__add__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__radd__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__add__(this); - } - - public PythonDecimal $method$__sub__(PythonDecimal other) { - return new PythonDecimal(value.subtract(other.value, threadMathContext.get())); - } - - public PythonDecimal $method$__sub__(PythonInteger other) { - return $method$__sub__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rsub__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__sub__(this); - } - - public PythonDecimal $method$__mul__(PythonDecimal other) { - return new PythonDecimal(value.multiply(other.value, threadMathContext.get())); - } - - public PythonDecimal $method$__mul__(PythonInteger other) { - return $method$__mul__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rmul__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__mul__(this); - } - - public PythonDecimal $method$__truediv__(PythonDecimal other) { - return new PythonDecimal(value.divide(other.value, threadMathContext.get())); - } - - public PythonDecimal $method$__truediv__(PythonInteger other) { - return $method$__truediv__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rtruediv__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__truediv__(this); - } - - public PythonDecimal $method$__floordiv__(PythonDecimal other) { - var newSignNum = switch (value.signum() * other.value.signum()) { - case -1 -> BigDecimal.ONE.negate(); - case 0 -> BigDecimal.ZERO; - case 1 -> BigDecimal.ONE; - default -> throw new IllegalStateException("Unexpected signum (%d)." - .formatted(value.signum() * other.value.signum())); - }; - // Need to round toward 0, but Java floors the result, so take the absolute and - // multiply by the sign-num - return new PythonDecimal(value.abs().divideToIntegralValue(other.value.abs()) - .multiply(newSignNum, threadMathContext.get())); - } - - public PythonDecimal $method$__floordiv__(PythonInteger other) { - return $method$__floordiv__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rfloordiv__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__floordiv__(this); - } - - public PythonDecimal $method$__mod__(PythonDecimal other) { - return new PythonDecimal( - value.subtract($method$__floordiv__(other).value.multiply(other.value, threadMathContext.get()))); - } - - public PythonDecimal $method$__mod__(PythonInteger other) { - return $method$__mod__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rmod__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__mod__(this); - } - - public PythonDecimal $method$__pow__(PythonDecimal other) { - if (other.value.stripTrailingZeros().scale() <= 0) { - // other is an int - return new PythonDecimal(value.pow(other.value.intValue(), threadMathContext.get())); - } - return new PythonDecimal(new BigDecimal(Math.pow(value.doubleValue(), other.value.doubleValue()), - threadMathContext.get())); - } - - public PythonDecimal $method$__pow__(PythonInteger other) { - return $method$__pow__(PythonDecimal.valueOf(other)); - } - - public PythonDecimal $method$__rpow__(PythonInteger other) { - return PythonDecimal.valueOf(other).$method$__mod__(this); - } - - // *************************** - // Other methods - // *************************** - public PythonInteger $method$adjusted() { - // scale is the negative exponent that the big int is multiplied by - // len(unscaled) - 1 = floor(log_10(unscaled)) - // floor(log_10(unscaled)) - scale = exponent in engineering notation - return PythonInteger.valueOf(value.unscaledValue().toString().length() - 1 - value.scale()); - } - - public PythonLikeTuple $method$as_integer_ratio() { - var parts = value.divideAndRemainder(BigDecimal.ONE); - var integralPart = parts[0]; - var fractionPart = parts[1]; - if (fractionPart.compareTo(BigDecimal.ZERO) == 0) { - // No decimal part, as integer ratio = (self, 1) - return PythonLikeTuple.fromItems(PythonInteger.valueOf(integralPart.toBigInteger()), - PythonInteger.ONE); - } - var scale = fractionPart.scale(); - var scaledDenominator = BigDecimal.ONE.movePointRight(scale).toBigInteger(); - var scaledIntegralPart = integralPart.movePointRight(scale).toBigInteger(); - var scaledFractionPart = fractionPart.movePointRight(scale).toBigInteger(); - var scaledNumerator = scaledIntegralPart.add(scaledFractionPart); - var commonFactors = scaledNumerator.gcd(scaledDenominator); - var reducedNumerator = scaledNumerator.divide(commonFactors); - var reducedDenominator = scaledDenominator.divide(commonFactors); - return PythonLikeTuple.fromItems(PythonInteger.valueOf(reducedNumerator), - PythonInteger.valueOf(reducedDenominator)); - } - - public PythonLikeTuple $method$as_tuple() { - // TODO: Use named tuple - return PythonLikeTuple.fromItems(PythonInteger.valueOf(value.signum() >= 0 ? 0 : 1), - value.unscaledValue().abs().toString() - .chars() - .mapToObj(digit -> PythonInteger.valueOf(digit - '0')) - .collect(Collectors.toCollection(PythonLikeTuple::new)), - PythonInteger.valueOf(-value.scale())); - } - - public PythonDecimal $method$canonical() { - return this; - } - - public PythonDecimal $method$compare(PythonDecimal other) { - return new PythonDecimal(BigDecimal.valueOf(value.compareTo(other.value))); - } - - public PythonDecimal $method$compare_signal(PythonDecimal other) { - return $method$compare(other); - } - - // See https://speleotrove.com/decimal/damisc.html#refcotot - public PythonDecimal $method$compare_total(PythonDecimal other) { - var result = $method$compare(other); - if (result.value.compareTo(BigDecimal.ZERO) != 0) { - return result; - } - var sigNum = value.scale() - other.value.scale(); - if (sigNum < 0) { - return new PythonDecimal(BigDecimal.ONE); - } - if (sigNum > 0) { - return new PythonDecimal(BigDecimal.valueOf(-1L)); - } - return result; // Can only reach here if result == BigDecimal.ZERO - } - - public PythonDecimal $method$compare_total_mag(PythonDecimal other) { - return new PythonDecimal(value.abs()).$method$compare_total(new PythonDecimal(other.value.abs())); - } - - public PythonDecimal $method$conjugate() { - return this; - } - - public PythonDecimal $method$copy_abs() { - return new PythonDecimal(value.abs()); - } - - public PythonDecimal $method$copy_negate() { - return new PythonDecimal(value.negate()); - } - - public PythonDecimal $method$copy_sign(PythonDecimal other) { - var signChange = value.signum() * other.value.signum(); - var multiplier = switch (signChange) { - case -1 -> BigDecimal.valueOf(-1); - case 0, 1 -> BigDecimal.ONE; // Note: there also a -0 BigDecimal in Python. - default -> throw new IllegalStateException("Unexpected signum (%d).".formatted(signChange)); - }; - return new PythonDecimal(value.multiply(multiplier)); - } - - private static BigDecimal getEToPrecision(int precision) { - return getESubPowerToPrecision(BigDecimal.ONE, precision); - } - - private static BigDecimal getESubPowerToPrecision(BigDecimal value, int precision) { - // Uses taylor series e^x = sum(x^n/n! for n in 0...infinity) - var numerator = BigDecimal.ONE; - var denominator = BigDecimal.ONE; - var total = BigDecimal.ZERO; - var extendedContext = new MathContext(precision + 8, RoundingMode.HALF_EVEN); - for (var index = 1; index < 100; index++) { - total = total.add(numerator.divide(denominator, extendedContext), extendedContext); - numerator = numerator.multiply(value); - denominator = denominator.multiply(BigDecimal.valueOf(index)); - } - return total; - } - - private static BigDecimal getEPower(BigDecimal value, int precision) { - var extendedPrecision = precision + 8; - - // Do e^x = e^(int(x))*e^(frac(x)) - var e = getEToPrecision(extendedPrecision); - var integralPart = value.toBigInteger().intValue(); - var fractionPart = value.remainder(BigDecimal.ONE); - return e.pow(integralPart).multiply(getESubPowerToPrecision(fractionPart, extendedPrecision), - threadMathContext.get()); - } - - public PythonDecimal $method$exp() { - var precision = threadMathContext.get().getPrecision(); - return new PythonDecimal(getEPower(value, precision)); - } - - public PythonDecimal $method$fma(PythonDecimal multiplier, PythonDecimal summand) { - return new PythonDecimal(this.value.multiply(multiplier.value).add(summand.value, threadMathContext.get())); - } - - public PythonDecimal $method$fma(PythonInteger multiplier, PythonDecimal summand) { - return $method$fma(PythonDecimal.valueOf(multiplier), summand); - } - - public PythonDecimal $method$fma(PythonDecimal multiplier, PythonInteger summand) { - return $method$fma(multiplier, PythonDecimal.valueOf(summand)); - } - - public PythonDecimal $method$fma(PythonInteger multiplier, PythonInteger summand) { - return $method$fma(PythonDecimal.valueOf(multiplier), PythonDecimal.valueOf(summand)); - } - - public PythonBoolean $method$is_canonical() { - return PythonBoolean.TRUE; - } - - public PythonBoolean $method$is_finite() { - // We don't support infinite or NaN Decimals - return PythonBoolean.TRUE; - } - - public PythonBoolean $method$is_infinite() { - // We don't support infinite or NaN Decimals - return PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_nan() { - // We don't support infinite or NaN Decimals - return PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_normal() { - // We don't support subnormal Decimals - return PythonBoolean.TRUE; - } - - public PythonBoolean $method$is_qnan() { - // We don't support infinite or NaN Decimals - return PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_signed() { - // Same as `isNegative()` - return value.compareTo(BigDecimal.ZERO) < 0 ? PythonBoolean.TRUE : PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_snan() { - // We don't support infinite or NaN Decimals - return PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_subnormal() { - // We don't support subnormal Decimals - return PythonBoolean.FALSE; - } - - public PythonBoolean $method$is_zero() { - return value.compareTo(BigDecimal.ZERO) == 0 ? PythonBoolean.TRUE : PythonBoolean.FALSE; - } - - public PythonDecimal $method$ln() { - return new PythonDecimal(new BigDecimal( - Math.log(value.doubleValue()), - threadMathContext.get())); - } - - public PythonDecimal $method$log10() { - return new PythonDecimal(new BigDecimal( - Math.log10(value.doubleValue()), - threadMathContext.get())); - } - - public PythonDecimal $method$logb() { - // Finds the exponent b in a * 10^b, where a in [1, 10) - return new PythonDecimal(BigDecimal.valueOf(value.precision() - value.scale() - 1)); - } - - private static PythonDecimal logicalOp(BiPredicate op, - BigDecimal a, BigDecimal b) { - if (a.scale() < 0 || b.scale() < 0) { - throw new ValueError("Invalid Operation: both operands must be positive integers consisting of 1's and 0's"); - } - var aText = a.toPlainString(); - var bText = b.toPlainString(); - if (aText.length() > bText.length()) { - bText = "0".repeat(aText.length() - bText.length()) + bText; - } else if (aText.length() < bText.length()) { - aText = "0".repeat(bText.length() - aText.length()) + aText; - } - - var digitCount = aText.length(); - var result = new StringBuilder(); - for (int i = 0; i < digitCount; i++) { - var aBit = switch (aText.charAt(i)) { - case '0' -> false; - case '1' -> true; - default -> throw new ValueError(("Invalid Operation: first operand (%s) is not a positive integer " + - "consisting of 1's and 0's").formatted(a)); - }; - var bBit = switch (bText.charAt(i)) { - case '0' -> false; - case '1' -> true; - default -> throw new ValueError(("Invalid Operation: second operand (%s) is not a positive integer " + - "consisting of 1's and 0's").formatted(b)); - }; - result.append(op.test(aBit, bBit) ? '1' : '0'); - } - return new PythonDecimal(new BigDecimal(result.toString())); - } - - public PythonDecimal $method$logical_and(PythonDecimal other) { - return logicalOp(Boolean::logicalAnd, this.value, other.value); - } - - public PythonDecimal $method$logical_or(PythonDecimal other) { - return logicalOp(Boolean::logicalOr, this.value, other.value); - } - - public PythonDecimal $method$logical_xor(PythonDecimal other) { - return logicalOp(Boolean::logicalXor, this.value, other.value); - } - - public PythonDecimal $method$logical_invert() { - return logicalOp(Boolean::logicalXor, this.value, new BigDecimal("1".repeat(threadMathContext.get().getPrecision()))); - } - - public PythonDecimal $method$max(PythonDecimal other) { - return new PythonDecimal(value.max(other.value)); - } - - public PythonDecimal $method$max_mag(PythonDecimal other) { - var result = $method$compare_total_mag(other).value.intValue(); - if (result >= 0) { - return this; - } else { - return other; - } - } - - public PythonDecimal $method$min(PythonDecimal other) { - return new PythonDecimal(value.min(other.value)); - } - - public PythonDecimal $method$min_mag(PythonDecimal other) { - var result = $method$compare_total_mag(other).value.intValue(); - if (result <= 0) { - return this; - } else { - return other; - } - } - - private BigDecimal getLastPlaceUnit(MathContext mathContext) { - int remainingPrecision = mathContext.getPrecision() - value.stripTrailingZeros().precision(); - return BigDecimal.ONE.movePointLeft(value.scale() + remainingPrecision + 1); - } - - public PythonDecimal $method$next_minus() { - var context = new MathContext(threadMathContext.get().getPrecision(), RoundingMode.FLOOR); - var lastPlaceUnit = getLastPlaceUnit(context); - return new PythonDecimal(value.subtract(lastPlaceUnit, context)); - } - - public PythonDecimal $method$next_plus() { - var context = new MathContext(threadMathContext.get().getPrecision(), RoundingMode.CEILING); - var lastPlaceUnit = getLastPlaceUnit(context); - return new PythonDecimal(value.add(lastPlaceUnit, context)); - } - - public PythonDecimal $method$next_toward(PythonDecimal other) { - var result = $method$compare(other).value.intValue(); - switch (result) { - case -1 -> { - return $method$next_plus(); - } - case 1 -> { - return $method$next_minus(); - } - case 0 -> { - return this; - } - default -> throw new IllegalStateException(); - } - } - - public PythonDecimal $method$normalize() { - return new PythonDecimal(value.stripTrailingZeros()); - } - - public PythonString $method$number_class() { - var result = value.compareTo(BigDecimal.ZERO); - if (result < 0) { - return PythonString.valueOf("-Normal"); - } else if (result > 0) { - return PythonString.valueOf("+Normal"); - } else { - return PythonString.valueOf("+Zero"); - } - } - - public PythonDecimal $method$quantize(PythonDecimal other) { - return new PythonDecimal(value.setScale(other.value.scale(), threadMathContext.get().getRoundingMode())); - } - - public PythonDecimal $method$radix() { - return new PythonDecimal(BigDecimal.TEN); - } - - public PythonDecimal $method$remainder_near(PythonDecimal other) { - var floorQuotient = $method$__floordiv__(other).value; - var firstRemainder = new PythonDecimal(value.subtract(floorQuotient.multiply(other.value, threadMathContext.get()))); - var secondRemainder = other.$method$__sub__(firstRemainder).$method$__neg__(); - var comparison = firstRemainder.$method$compare_total_mag(secondRemainder).value.intValue(); - return switch (comparison) { - case -1 -> firstRemainder; - case 1 -> secondRemainder; - case 0 -> { - if (floorQuotient.longValue() % 2 == 0) { - yield firstRemainder; - } else { - yield secondRemainder; - } - } - default -> throw new IllegalStateException(); - }; - } - - public PythonDecimal $method$rotate(PythonInteger other) { - var amount = -other.value.intValue(); - if (amount == 0) { - return this; - } - var precision = threadMathContext.get().getPrecision(); - if (Math.abs(amount) > precision) { - throw new ValueError("other must be between -%d and %d".formatted(amount, amount)); - } - var digitString = value.unscaledValue().toString(); - digitString = "0".repeat(precision - digitString.length()) + digitString; - if (amount < 0) { - // Turn a rotate right to a rotate left - amount = precision + amount; - } - var rotatedResult = digitString.substring(precision - amount, precision) + digitString.substring(0, precision - amount); - var unscaledResult = new BigInteger(rotatedResult); - return new PythonDecimal(new BigDecimal(unscaledResult, value.scale())); - } - - public PythonBoolean $method$same_quantum(PythonDecimal other) { - return PythonBoolean.valueOf( - value.ulp().compareTo(other.value.ulp()) == 0); - } - - public PythonDecimal $method$scaleb(PythonInteger other) { - return new PythonDecimal(value.movePointRight(other.value.intValue())); - } - - public PythonDecimal $method$shift(PythonInteger other) { - var amount = other.value.intValue(); - if (amount == 0) { - return this; - } - var precision = threadMathContext.get().getPrecision(); - if (Math.abs(amount) > precision) { - throw new ValueError("other must be between -%d and %d".formatted(amount, amount)); - } - return new PythonDecimal(value.movePointLeft(amount)); - } - - public PythonDecimal $method$sqrt() { - return new PythonDecimal(value.sqrt(threadMathContext.get())); - } - - public PythonString $method$to_eng_string() { - return new PythonString(value.toEngineeringString()); - } - - public PythonInteger $method$to_integral() { - return $method$to_integral_value(); - } - - public PythonInteger $method$to_integral_exact() { - // TODO: set signals in the context object - return $method$to_integral_value(); - } - - public PythonInteger $method$to_integral_value() { - return new PythonInteger(value.divideToIntegralValue(BigDecimal.ONE, threadMathContext.get()).toBigInteger()); - } - - public PythonInteger $method$__round__() { - // Round without an argument ignores thread math context - var first = value.toBigInteger(); - var second = first.add(BigInteger.ONE); - var firstDiff = value.subtract(new BigDecimal(first)); - var secondDiff = new BigDecimal(second).subtract(value); - var comparison = firstDiff.compareTo(secondDiff); - return switch (comparison) { - case -1 -> new PythonInteger(first); - case 1 -> new PythonInteger(second); - case 0 -> { - if (first.intValue() % 2 == 0) { - yield new PythonInteger(first); - } else { - yield new PythonInteger(second); - } - } - default -> throw new IllegalStateException(); - }; - } - - public PythonLikeObject $method$__round__(PythonLikeObject maybePrecision) { - if (maybePrecision instanceof PythonNone) { - return $method$__round__(); - } - if (!(maybePrecision instanceof PythonInteger precision)) { - throw new ValueError("ndigits must be an integer"); - } - // Round with an argument uses thread math context - var integralPart = value.toBigInteger(); - return new PythonDecimal(value.round(new MathContext( - integralPart.toString().length() + precision.value.intValue(), - threadMathContext.get().getRoundingMode()))); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonFloat.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonFloat.java deleted file mode 100644 index 91ac4356..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonFloat.java +++ /dev/null @@ -1,825 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.Coercible; -import ai.timefold.jpyinterpreter.types.NotImplemented; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.ZeroDivisionError; -import ai.timefold.jpyinterpreter.util.DefaultFormatSpec; -import ai.timefold.jpyinterpreter.util.StringFormatter; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonFloat extends AbstractPythonLikeObject implements PythonNumber, PlanningImmutable, - Coercible { - public final double value; - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonFloat::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - PythonLikeComparable.setup(BuiltinTypes.FLOAT_TYPE); - - // Constructor - BuiltinTypes.FLOAT_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.isEmpty()) { - return new PythonFloat(0.0); - } else if (positionalArguments.size() == 1) { - return PythonFloat.from(positionalArguments.get(0)); - } else { - throw new ValueError("float takes 0 or 1 arguments, got " + positionalArguments.size()); - } - }); - // Unary - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_BOOLEAN, PythonFloat.class.getMethod("asBoolean")); - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_INT, PythonFloat.class.getMethod("asInteger")); - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.POSITIVE, PythonFloat.class.getMethod("asFloat")); - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.NEGATIVE, PythonFloat.class.getMethod("negative")); - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.ABS, PythonFloat.class.getMethod("abs")); - BuiltinTypes.FLOAT_TYPE.addUnaryMethod(PythonUnaryOperator.HASH, PythonFloat.class.getMethod("$method$__hash__")); - - // Binary - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonFloat.class.getMethod("add", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonFloat.class.getMethod("add", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonFloat.class.getMethod("add", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonFloat.class.getMethod("subtract", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonFloat.class.getMethod("subtract", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonFloat.class.getMethod("subtract", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonFloat.class.getMethod("multiply", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonFloat.class.getMethod("multiply", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonFloat.class.getMethod("multiply", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonFloat.class.getMethod("trueDivide", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonFloat.class.getMethod("trueDivide", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonFloat.class.getMethod("trueDivide", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonFloat.class.getMethod("floorDivide", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonFloat.class.getMethod("floorDivide", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonFloat.class.getMethod("floorDivide", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.DIVMOD, - PythonFloat.class.getMethod("divmod", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.DIVMOD, - PythonFloat.class.getMethod("divmod", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.DIVMOD, - PythonFloat.class.getMethod("divmod", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonFloat.class.getMethod("modulo", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonFloat.class.getMethod("modulo", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonFloat.class.getMethod("modulo", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonFloat.class.getMethod("power", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonFloat.class.getMethod("power", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonFloat.class.getMethod("power", PythonFloat.class)); - - // Comparisons - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonFloat.class.getMethod("pythonEquals", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonFloat.class.getMethod("pythonEquals", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonFloat.class.getMethod("pythonEquals", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonFloat.class.getMethod("notEqual", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonFloat.class.getMethod("notEqual", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonFloat.class.getMethod("notEqual", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonFloat.class.getMethod("lessThan", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonFloat.class.getMethod("lessThan", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonFloat.class.getMethod("lessThan", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonFloat.class.getMethod("lessThanOrEqual", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonFloat.class.getMethod("lessThanOrEqual", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonFloat.class.getMethod("lessThanOrEqual", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonFloat.class.getMethod("greaterThan", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonFloat.class.getMethod("greaterThan", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonFloat.class.getMethod("greaterThan", PythonFloat.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonFloat.class.getMethod("greaterThanOrEqual", PythonLikeObject.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonFloat.class.getMethod("greaterThanOrEqual", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonFloat.class.getMethod("greaterThanOrEqual", PythonFloat.class)); - - // Other - BuiltinTypes.FLOAT_TYPE.addMethod("__round__", PythonFloat.class.getMethod("round")); - BuiltinTypes.FLOAT_TYPE.addMethod("__round__", PythonFloat.class.getMethod("round", PythonInteger.class)); - BuiltinTypes.FLOAT_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonFloat.class.getMethod("$method$__format__")); - BuiltinTypes.FLOAT_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonFloat.class.getMethod("$method$__format__", PythonLikeObject.class)); - - return BuiltinTypes.FLOAT_TYPE; - } - - public PythonFloat(double value) { - super(BuiltinTypes.FLOAT_TYPE); - this.value = value; - } - - public static PythonFloat from(PythonLikeObject value) { - if (value instanceof PythonInteger integer) { - return integer.asFloat(); - } else if (value instanceof PythonFloat) { - return (PythonFloat) value; - } else if (value instanceof PythonString str) { - try { - var literal = switch (str.value.toLowerCase()) { - case "nan", "+nan" -> "+NaN"; - case "-nan" -> "-NaN"; - case "inf", "+inf", "infinity" -> "+Infinity"; - case "-inf", "-infinity" -> "-Infinity"; - default -> str.value; - }; - return new PythonFloat(Double.parseDouble(literal)); - } catch (NumberFormatException e) { - throw new ValueError("invalid literal for float(): %s".formatted(value)); - } - } else { - PythonLikeType valueType = value.$getType(); - PythonLikeFunction asFloatFunction = (PythonLikeFunction) (valueType.$getAttributeOrError("__float__")); - return (PythonFloat) asFloatFunction.$call(List.of(value), Map.of(), null); - } - } - - @Override - public Number getValue() { - return value; - } - - public PythonLikeTuple asFraction() { - final BigInteger FIVE = BigInteger.valueOf(5L); - - BigDecimal bigDecimal = new BigDecimal(value); - BigInteger numerator = bigDecimal.movePointRight(bigDecimal.scale()).toBigIntegerExact(); - BigInteger denominator; - if (bigDecimal.scale() < 0) { - denominator = BigInteger.ONE; - numerator = numerator.multiply(BigInteger.TEN.pow(-bigDecimal.scale())); - } else { - denominator = BigInteger.TEN.pow(bigDecimal.scale()); - } - - // denominator is a multiple of 10, thus only have 5 and 2 as prime factors - - while (denominator.remainder(BigInteger.TWO).equals(BigInteger.ZERO) - && numerator.remainder(BigInteger.TWO).equals(BigInteger.ZERO)) { - denominator = denominator.shiftRight(1); - numerator = numerator.shiftRight(1); - } - - while (denominator.remainder(FIVE).equals(BigInteger.ZERO) && numerator.remainder(FIVE).equals(BigInteger.ZERO)) { - denominator = denominator.divide(FIVE); - numerator = numerator.divide(FIVE); - } - - return PythonLikeTuple.fromItems(PythonInteger.valueOf(numerator), PythonInteger.valueOf(denominator)); - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return Double.toString(value); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Number number) { - return number.doubleValue() == value; - } else if (o instanceof PythonNumber number) { - return compareTo(number) == 0; - } else { - return false; - } - } - - @Override - public int hashCode() { - return $method$__hash__().value.intValue(); - } - - @Override - public PythonInteger $method$__hash__() { - if (Double.isNaN(value)) { - return PythonInteger.valueOf(hashCode()); - } else if (Double.isInfinite(value)) { - if (value > 0) { - return INFINITY_HASH_VALUE; - } else { - return INFINITY_HASH_VALUE.negative(); - } - } - PythonLikeTuple fractionTuple = asFraction(); - return PythonNumber.computeHash((PythonInteger) fractionTuple.get(0), (PythonInteger) fractionTuple.get(1)); - } - - public static PythonFloat valueOf(float value) { - return new PythonFloat(value); - } - - public static PythonFloat valueOf(double value) { - return new PythonFloat(value); - } - - public PythonBoolean asBoolean() { - return value == 0.0 ? PythonBoolean.FALSE : PythonBoolean.TRUE; - } - - public PythonInteger asInteger() { - return new PythonInteger((long) Math.floor(value)); - } - - public PythonFloat asFloat() { - return this; - } - - public PythonFloat negative() { - return new PythonFloat(-value); - } - - public PythonFloat abs() { - return new PythonFloat(Math.abs(value)); - } - - public PythonLikeObject add(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return add((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return add((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat add(PythonInteger other) { - return new PythonFloat(value + other.value.doubleValue()); - } - - public PythonFloat add(PythonFloat other) { - return new PythonFloat(value + other.value); - } - - public PythonLikeObject subtract(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return subtract((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return subtract((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat subtract(PythonInteger other) { - return new PythonFloat(value - other.value.doubleValue()); - } - - public PythonFloat subtract(PythonFloat other) { - return new PythonFloat(value - other.value); - } - - public PythonLikeObject multiply(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return multiply((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return multiply((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat multiply(PythonInteger other) { - return new PythonFloat(value * other.value.doubleValue()); - } - - public PythonFloat multiply(PythonFloat other) { - return new PythonFloat(value * other.value); - } - - public PythonLikeObject trueDivide(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return trueDivide((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return trueDivide((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat trueDivide(PythonInteger other) { - if (other.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("float division"); - } - return new PythonFloat(value / other.value.doubleValue()); - } - - public PythonFloat trueDivide(PythonFloat other) { - if (other.value == 0) { - throw new ZeroDivisionError("float division"); - } - return new PythonFloat(value / other.value); - } - - public PythonLikeObject floorDivide(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return floorDivide((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return floorDivide((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat floorDivide(PythonInteger other) { - if (other.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("float division"); - } - return new PythonFloat(new BigDecimal(value) - .divideToIntegralValue(new BigDecimal(other.value)) - .doubleValue()); - } - - public PythonFloat floorDivide(PythonFloat other) { - if (other.value == 0) { - throw new ZeroDivisionError("float division"); - } - return PythonFloat.valueOf(Math.floor(value / other.value)); - } - - public PythonFloat ceilDivide(PythonInteger other) { - if (other.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("float division"); - } - return new PythonFloat(new BigDecimal(value) - .divide(new BigDecimal(other.value), RoundingMode.CEILING) - .doubleValue()); - } - - public PythonFloat ceilDivide(PythonFloat other) { - if (other.value == 0) { - throw new ZeroDivisionError("float division"); - } - return PythonFloat.valueOf(Math.ceil(value / other.value)); - } - - public PythonLikeObject modulo(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return modulo((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return modulo((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat modulo(PythonInteger other) { - int remainderSign = other.compareTo(PythonInteger.ZERO); - - if (remainderSign == 0) { - throw new ZeroDivisionError("float modulo"); - } else if (remainderSign > 0) { - double remainder = value % other.value.doubleValue(); - if (remainder < 0) { - remainder = remainder + other.value.doubleValue(); - } - return new PythonFloat(remainder); - } else { - double remainder = value % other.value.doubleValue(); - if (remainder > 0) { - remainder = remainder + other.value.doubleValue(); - } - return new PythonFloat(remainder); - } - } - - public PythonFloat modulo(PythonFloat other) { - if (other.value == 0) { - throw new ZeroDivisionError("float modulo"); - } else if (other.value > 0) { - double remainder = value % other.value; - if (remainder < 0) { - remainder = remainder + other.value; - } - return new PythonFloat(remainder); - } else { - double remainder = value % other.value; - if (remainder > 0) { - remainder = remainder + other.value; - } - return new PythonFloat(remainder); - } - } - - public PythonLikeObject divmod(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return divmod((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return divmod((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeTuple divmod(PythonInteger other) { - PythonFloat quotient; - - if (value < 0 == other.value.compareTo(BigInteger.ZERO) < 0) { - // Same sign, use floor division - quotient = floorDivide(other); - } else { - // Different sign, use ceil division - quotient = ceilDivide(other); - } - PythonInteger.valueOf(Math.round(value / other.value.doubleValue())); - double remainder = value % other.value.doubleValue(); - - // Python remainder has sign of divisor - if (other.value.compareTo(BigInteger.ZERO) < 0) { - if (remainder > 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value.doubleValue(); - } - } else { - if (remainder < 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value.doubleValue(); - } - } - return PythonLikeTuple.fromItems(quotient, new PythonFloat(remainder)); - } - - public PythonLikeTuple divmod(PythonFloat other) { - PythonFloat quotient; - - if (value < 0 == other.value < 0) { - // Same sign, use floor division - quotient = floorDivide(other); - } else { - // Different sign, use ceil division - quotient = ceilDivide(other); - } - double remainder = value % other.value; - - // Python remainder has sign of divisor - if (other.value < 0) { - if (remainder > 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value; - } - } else { - if (remainder < 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value; - } - } - return PythonLikeTuple.fromItems(quotient, new PythonFloat(remainder)); - } - - public PythonInteger round() { - if (value % 1.0 == 0.5) { - long floor = (long) Math.floor(value); - if (floor % 2 == 0) { - return PythonInteger.valueOf(floor); - } else { - return PythonInteger.valueOf(floor + 1); - } - } - return PythonInteger.valueOf(Math.round(value)); - } - - public PythonNumber round(PythonInteger digitsAfterDecimal) { - if (digitsAfterDecimal.equals(PythonInteger.ZERO)) { - return round(); - } - - BigDecimal asDecimal = new BigDecimal(value); - return new PythonFloat( - asDecimal.setScale(digitsAfterDecimal.value.intValueExact(), RoundingMode.HALF_EVEN).doubleValue()); - } - - public PythonLikeObject power(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return power((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return power((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat power(PythonInteger other) { - return new PythonFloat(Math.pow(value, other.value.doubleValue())); - } - - public PythonFloat power(PythonFloat other) { - return new PythonFloat(Math.pow(value, other.value)); - } - - public PythonLikeObject pythonEquals(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return pythonEquals((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return pythonEquals((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject notEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return notEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return notEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject lessThan(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return lessThan((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return lessThan((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject greaterThan(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return greaterThan((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return greaterThan((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject lessThanOrEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return lessThanOrEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return lessThanOrEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject greaterThanOrEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return greaterThanOrEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return greaterThanOrEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonBoolean pythonEquals(PythonInteger other) { - return PythonBoolean.valueOf(value == other.value.doubleValue()); - } - - public PythonBoolean notEqual(PythonInteger other) { - return PythonBoolean.valueOf(value != other.value.doubleValue()); - } - - public PythonBoolean lessThan(PythonInteger other) { - return PythonBoolean.valueOf(value < other.value.doubleValue()); - } - - public PythonBoolean lessThanOrEqual(PythonInteger other) { - return PythonBoolean.valueOf(value <= other.value.doubleValue()); - } - - public PythonBoolean greaterThan(PythonInteger other) { - return PythonBoolean.valueOf(value > other.value.doubleValue()); - } - - public PythonBoolean greaterThanOrEqual(PythonInteger other) { - return PythonBoolean.valueOf(value >= other.value.doubleValue()); - } - - public PythonBoolean pythonEquals(PythonFloat other) { - return PythonBoolean.valueOf(value == other.value); - } - - public PythonBoolean notEqual(PythonFloat other) { - return PythonBoolean.valueOf(value != other.value); - } - - public PythonBoolean lessThan(PythonFloat other) { - return PythonBoolean.valueOf(value < other.value); - } - - public PythonBoolean lessThanOrEqual(PythonFloat other) { - return PythonBoolean.valueOf(value <= other.value); - } - - public PythonBoolean greaterThan(PythonFloat other) { - return PythonBoolean.valueOf(value > other.value); - } - - public PythonBoolean greaterThanOrEqual(PythonFloat other) { - return PythonBoolean.valueOf(value >= other.value); - } - - public PythonString format() { - return PythonString.valueOf(Double.toString(value)); - } - - private DecimalFormat getNumberFormat(DefaultFormatSpec formatSpec) { - DecimalFormat numberFormat = new DecimalFormat(); - DecimalFormatSymbols symbols = new DecimalFormatSymbols(); - boolean isUppercase = false; - - switch (formatSpec.conversionType.orElse(DefaultFormatSpec.ConversionType.LOWERCASE_GENERAL)) { - case UPPERCASE_GENERAL: - case UPPERCASE_SCIENTIFIC_NOTATION: - case UPPERCASE_FIXED_POINT: - isUppercase = true; - break; - } - - if (isUppercase) { - symbols.setExponentSeparator("E"); - symbols.setInfinity("INF"); - symbols.setNaN("NAN"); - } else { - symbols.setExponentSeparator("e"); - symbols.setInfinity("inf"); - symbols.setNaN("nan"); - } - - if (formatSpec.groupingOption.isPresent()) { - switch (formatSpec.groupingOption.get()) { - case COMMA: - symbols.setGroupingSeparator(','); - break; - case UNDERSCORE: - symbols.setGroupingSeparator('_'); - break; - } - } - - if (formatSpec.conversionType.orElse(null) == DefaultFormatSpec.ConversionType.LOCALE_SENSITIVE) { - symbols.setGroupingSeparator(DecimalFormatSymbols.getInstance().getGroupingSeparator()); - } - numberFormat.setDecimalFormatSymbols(symbols); - - switch (formatSpec.conversionType.orElse(DefaultFormatSpec.ConversionType.LOWERCASE_GENERAL)) { - case LOWERCASE_SCIENTIFIC_NOTATION: - case UPPERCASE_SCIENTIFIC_NOTATION: - numberFormat.applyPattern("0." + "#".repeat(formatSpec.getPrecisionOrDefault()) + "E00"); - break; - - case LOWERCASE_FIXED_POINT: - case UPPERCASE_FIXED_POINT: - if (formatSpec.groupingOption.isPresent()) { - numberFormat.applyPattern("#,##0." + "0".repeat(formatSpec.getPrecisionOrDefault())); - } else { - numberFormat.applyPattern("0." + "0".repeat(formatSpec.getPrecisionOrDefault())); - } - break; - - case LOCALE_SENSITIVE: - case LOWERCASE_GENERAL: - case UPPERCASE_GENERAL: - BigDecimal asBigDecimal = new BigDecimal(value); - // total digits - digits to the right of the decimal point - int exponent; - if (asBigDecimal.precision() == asBigDecimal.scale() + 1) { - exponent = -asBigDecimal.scale(); - } else { - exponent = asBigDecimal.precision() - asBigDecimal.scale() - 1; - } - - if (-4 < exponent || exponent >= formatSpec.getPrecisionOrDefault()) { - if (formatSpec.conversionType.isEmpty()) { - numberFormat.applyPattern("0.0" + "#".repeat(formatSpec.getPrecisionOrDefault() - 1) + "E00"); - } else { - numberFormat.applyPattern("0." + "#".repeat(formatSpec.getPrecisionOrDefault()) + "E00"); - } - } else { - if (formatSpec.groupingOption.isPresent() || - formatSpec.conversionType.orElse(null) == DefaultFormatSpec.ConversionType.LOCALE_SENSITIVE) { - if (formatSpec.conversionType.isEmpty()) { - numberFormat.applyPattern("#,##0.0" + "#".repeat(formatSpec.getPrecisionOrDefault() - 1)); - } else { - numberFormat.applyPattern("#,##0." + "#".repeat(formatSpec.getPrecisionOrDefault())); - } - } else { - if (formatSpec.conversionType.isEmpty()) { - numberFormat.applyPattern("0.0" + "#".repeat(formatSpec.getPrecisionOrDefault() - 1)); - } else { - numberFormat.applyPattern("0." + "#".repeat(formatSpec.getPrecisionOrDefault())); - } - } - } - case PERCENTAGE: - if (formatSpec.groupingOption.isPresent()) { - numberFormat.applyPattern("#,##0." + "0".repeat(formatSpec.getPrecisionOrDefault()) + "%"); - } else { - numberFormat.applyPattern("0." + "0".repeat(formatSpec.getPrecisionOrDefault()) + "%"); - } - break; - default: - throw new ValueError("Invalid conversion for float type: " + formatSpec.conversionType); - } - - switch (formatSpec.signOption.orElse(DefaultFormatSpec.SignOption.ONLY_NEGATIVE_NUMBERS)) { - case ALWAYS_SIGN: - numberFormat.setPositivePrefix("+"); - numberFormat.setNegativePrefix("-"); - break; - case ONLY_NEGATIVE_NUMBERS: - numberFormat.setPositivePrefix(""); - numberFormat.setNegativePrefix("-"); - break; - case SPACE_FOR_POSITIVE_NUMBERS: - numberFormat.setPositivePrefix(" "); - numberFormat.setNegativePrefix("-"); - break; - } - - numberFormat.setRoundingMode(RoundingMode.HALF_EVEN); - numberFormat.setDecimalSeparatorAlwaysShown(formatSpec.useAlternateForm); - - return numberFormat; - } - - public PythonString $method$__format__(PythonLikeObject specObject) { - PythonString spec; - if (specObject == PythonNone.INSTANCE) { - spec = PythonString.EMPTY; - } else if (specObject instanceof PythonString) { - spec = (PythonString) specObject; - } else { - throw new TypeError("__format__ argument 0 has incorrect type (expecting str or None)"); - } - DefaultFormatSpec formatSpec = DefaultFormatSpec.fromSpec(spec); - - StringBuilder out = new StringBuilder(); - NumberFormat numberFormat = getNumberFormat(formatSpec); - - out.append(numberFormat.format(value)); - StringFormatter.align(out, formatSpec, DefaultFormatSpec.AlignmentOption.RIGHT_ALIGN); - return PythonString.valueOf(out.toString()); - } - - @Override - public T coerce(Class targetType) { - if (targetType.equals(PythonInteger.class)) { - return (T) PythonInteger.valueOf((long) value); - } - return null; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonInteger.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonInteger.java deleted file mode 100644 index 09f5f555..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonInteger.java +++ /dev/null @@ -1,882 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.text.NumberFormat; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.Coercible; -import ai.timefold.jpyinterpreter.types.NotImplemented; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.arithmetic.ZeroDivisionError; -import ai.timefold.jpyinterpreter.util.DefaultFormatSpec; -import ai.timefold.jpyinterpreter.util.StringFormatter; -import ai.timefold.solver.core.impl.domain.solution.cloner.PlanningImmutable; - -public class PythonInteger extends AbstractPythonLikeObject implements PythonNumber, - PlanningImmutable, Coercible { - private static final BigInteger MIN_BYTE = BigInteger.valueOf(0); - private static final BigInteger MAX_BYTE = BigInteger.valueOf(255); - - public final BigInteger value; - - public final static PythonInteger ZERO = new PythonInteger(BigInteger.ZERO); - public final static PythonInteger ONE = new PythonInteger(BigInteger.ONE); - public final static PythonInteger TWO = new PythonInteger(BigInteger.TWO); - - static { - PythonOverloadImplementor.deferDispatchesFor(PythonInteger::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - // Constructor - BuiltinTypes.INT_TYPE.setConstructor((positionalArguments, namedArguments, callerInstance) -> { - if (positionalArguments.isEmpty()) { - return PythonInteger.valueOf(0); - } else if (positionalArguments.size() == 1) { - return PythonInteger.from(positionalArguments.get(0)); - } else if (positionalArguments.size() == 2) { - return PythonInteger.fromUsingBase(positionalArguments.get(0), positionalArguments.get(1)); - } else { - throw new TypeError("int takes at most 2 arguments, got " + positionalArguments.size()); - } - }); - // Unary - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_BOOLEAN, PythonInteger.class.getMethod("asBoolean")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_INT, PythonInteger.class.getMethod("asInteger")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_FLOAT, PythonInteger.class.getMethod("asFloat")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_INDEX, PythonInteger.class.getMethod("asInteger")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.POSITIVE, PythonInteger.class.getMethod("asInteger")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.NEGATIVE, PythonInteger.class.getMethod("negative")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.INVERT, PythonInteger.class.getMethod("invert")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.ABS, PythonInteger.class.getMethod("abs")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.REPRESENTATION, PythonInteger.class.getMethod("asString")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.AS_STRING, PythonInteger.class.getMethod("asString")); - BuiltinTypes.INT_TYPE.addUnaryMethod(PythonUnaryOperator.HASH, PythonInteger.class.getMethod("$method$__hash__")); - - // Binary - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonInteger.class.getMethod("add", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonInteger.class.getMethod("add", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.ADD, - PythonInteger.class.getMethod("add", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonInteger.class.getMethod("subtract", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonInteger.class.getMethod("subtract", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.SUBTRACT, - PythonInteger.class.getMethod("subtract", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonInteger.class.getMethod("multiply", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonInteger.class.getMethod("multiply", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MULTIPLY, - PythonInteger.class.getMethod("multiply", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonInteger.class.getMethod("trueDivide", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonInteger.class.getMethod("trueDivide", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.TRUE_DIVIDE, - PythonInteger.class.getMethod("trueDivide", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonInteger.class.getMethod("floorDivide", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonInteger.class.getMethod("floorDivide", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.FLOOR_DIVIDE, - PythonInteger.class.getMethod("floorDivide", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.DIVMOD, - PythonInteger.class.getMethod("divmod", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.DIVMOD, - PythonInteger.class.getMethod("divmod", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonInteger.class.getMethod("modulo", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonInteger.class.getMethod("modulo", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.MODULO, - PythonInteger.class.getMethod("modulo", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonInteger.class.getMethod("power", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonInteger.class.getMethod("power", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.POWER, - PythonInteger.class.getMethod("power", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LSHIFT, - PythonInteger.class.getMethod("shiftLeft", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LSHIFT, - PythonInteger.class.getMethod("shiftLeft", PythonInteger.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.RSHIFT, - PythonInteger.class.getMethod("shiftRight", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.RSHIFT, - PythonInteger.class.getMethod("shiftRight", PythonInteger.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.AND, - PythonInteger.class.getMethod("bitwiseAnd", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.AND, - PythonInteger.class.getMethod("bitwiseAnd", PythonInteger.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.OR, - PythonInteger.class.getMethod("bitwiseOr", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.OR, - PythonInteger.class.getMethod("bitwiseOr", PythonInteger.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.XOR, - PythonInteger.class.getMethod("bitwiseXor", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.XOR, - PythonInteger.class.getMethod("bitwiseXor", PythonInteger.class)); - - // Ternary - BuiltinTypes.INT_TYPE.addBinaryMethod(PythonBinaryOperator.POWER, - PythonInteger.class.getMethod("power", PythonInteger.class, PythonInteger.class)); - - // Comparisons - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonInteger.class.getMethod("pythonEquals", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonInteger.class.getMethod("pythonEquals", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.EQUAL, - PythonInteger.class.getMethod("pythonEquals", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonInteger.class.getMethod("notEqual", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonInteger.class.getMethod("notEqual", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.NOT_EQUAL, - PythonInteger.class.getMethod("notEqual", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonInteger.class.getMethod("lessThan", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonInteger.class.getMethod("lessThan", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN, - PythonInteger.class.getMethod("lessThan", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonInteger.class.getMethod("lessThanOrEqual", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonInteger.class.getMethod("lessThanOrEqual", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.LESS_THAN_OR_EQUAL, - PythonInteger.class.getMethod("lessThanOrEqual", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonInteger.class.getMethod("greaterThan", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonInteger.class.getMethod("greaterThan", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN, - PythonInteger.class.getMethod("greaterThan", PythonFloat.class)); - - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonInteger.class.getMethod("greaterThanOrEqual", PythonLikeObject.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonInteger.class.getMethod("greaterThanOrEqual", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addLeftBinaryMethod(PythonBinaryOperator.GREATER_THAN_OR_EQUAL, - PythonInteger.class.getMethod("greaterThanOrEqual", PythonFloat.class)); - - // Other - BuiltinTypes.INT_TYPE.addMethod("__round__", PythonInteger.class.getMethod("round")); - BuiltinTypes.INT_TYPE.addMethod("__round__", PythonInteger.class.getMethod("round", PythonInteger.class)); - BuiltinTypes.INT_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonInteger.class.getMethod("$method$__format__")); - BuiltinTypes.INT_TYPE.addBinaryMethod(PythonBinaryOperator.FORMAT, - PythonInteger.class.getMethod("$method$__format__", PythonLikeObject.class)); - - return BuiltinTypes.INT_TYPE; - } - - public PythonInteger(PythonLikeType type) { - super(type); - this.value = BigInteger.ZERO; - } - - public PythonInteger(PythonLikeType type, BigInteger value) { - super(type); - this.value = value; - } - - public PythonInteger(long value) { - this(BigInteger.valueOf(value)); - } - - public PythonInteger(BigInteger value) { - super(BuiltinTypes.INT_TYPE); - this.value = value; - } - - private static PythonInteger from(PythonLikeObject value) { - if (value instanceof PythonInteger integer) { - return integer; - } else if (value instanceof PythonFloat pythonFloat) { - return pythonFloat.asInteger(); - } else if (value instanceof PythonString str) { - try { - return new PythonInteger(new BigInteger(str.value)); - } catch (NumberFormatException e) { - throw new ValueError("invalid literal for int() with base 10: %s".formatted(value)); - } - } else { - PythonLikeType valueType = value.$getType(); - PythonLikeFunction asIntFunction = (PythonLikeFunction) (valueType.$getAttributeOrError("__int__")); - return (PythonInteger) asIntFunction.$call(List.of(value), Map.of(), null); - } - } - - private static PythonInteger fromUsingBase(PythonLikeObject value, PythonLikeObject base) { - if (value instanceof PythonString str && base instanceof PythonInteger baseInt) { - try { - return new PythonInteger(new BigInteger(str.value, baseInt.value.intValue())); - } catch (NumberFormatException e) { - throw new ValueError( - "invalid literal for int() with base %d: %s".formatted(baseInt.value.intValue(), value)); - } - } else { - PythonLikeType valueType = value.$getType(); - PythonLikeFunction asIntFunction = (PythonLikeFunction) (valueType.$getAttributeOrError("__int__")); - return (PythonInteger) asIntFunction.$call(List.of(value, base), Map.of(), null); - } - } - - @Override - public Number getValue() { - return value; - } - - @Override - public PythonString $method$__str__() { - return PythonString.valueOf(toString()); - } - - @Override - public String toString() { - return value.toString(); - } - - public byte asByte() { - if (value.compareTo(MIN_BYTE) < 0 || value.compareTo(MAX_BYTE) > 0) { - throw new ValueError(value + " cannot represent a byte because it outside the range [0, 255]."); - } - return value.byteValue(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Number number) { - return value.equals(BigInteger.valueOf(number.longValue())); - } else if (o instanceof PythonNumber number) { - return compareTo(number) == 0; - } else { - return false; - } - } - - @Override - public int hashCode() { - return $method$__hash__().value.intValue(); - } - - public PythonInteger $method$__hash__() { - return PythonNumber.computeHash(this, ONE); - } - - public static PythonInteger valueOf(byte value) { - return new PythonInteger(value); - } - - public static PythonInteger valueOf(short value) { - return new PythonInteger(value); - } - - public static PythonInteger valueOf(int value) { - return new PythonInteger(value); - } - - public static PythonInteger valueOf(long value) { - return new PythonInteger(value); - } - - public static PythonInteger valueOf(BigInteger value) { - return new PythonInteger(value); - } - - public PythonBoolean asBoolean() { - return value.signum() == 0 ? PythonBoolean.FALSE : PythonBoolean.TRUE; - } - - public PythonInteger asInteger() { - return this; - } - - public PythonFloat asFloat() { - return new PythonFloat(value.doubleValue()); - } - - public PythonInteger negative() { - return new PythonInteger(value.negate()); - } - - public PythonInteger invert() { - return new PythonInteger(value.add(BigInteger.ONE).negate()); - } - - public PythonInteger abs() { - return new PythonInteger(value.abs()); - } - - public PythonLikeObject add(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return add((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return add((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger add(PythonInteger other) { - return new PythonInteger(value.add(other.value)); - } - - public PythonFloat add(PythonFloat other) { - return new PythonFloat(value.doubleValue() + other.value); - } - - public PythonLikeObject subtract(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return subtract((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return subtract((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger subtract(PythonInteger other) { - return new PythonInteger(value.subtract(other.value)); - } - - public PythonFloat subtract(PythonFloat other) { - return new PythonFloat(value.doubleValue() - other.value); - } - - public PythonLikeObject multiply(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return multiply((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return multiply((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger multiply(PythonInteger other) { - return new PythonInteger(value.multiply(other.value)); - } - - public PythonFloat multiply(PythonFloat other) { - return new PythonFloat(value.doubleValue() * other.value); - } - - public PythonLikeObject trueDivide(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return trueDivide((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return trueDivide((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonFloat trueDivide(PythonInteger other) { - if (other.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } - return new PythonFloat(value.doubleValue() / other.value.doubleValue()); - } - - public PythonFloat trueDivide(PythonFloat other) { - if (other.value == 0.0) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } - return new PythonFloat(value.doubleValue() / other.value); - } - - public PythonLikeObject floorDivide(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return floorDivide((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return floorDivide((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger floorDivide(PythonInteger other) { - if (other.value.equals(BigInteger.ZERO)) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } - return new PythonInteger(value.divide(other.value)); - } - - public PythonFloat floorDivide(PythonFloat other) { - if (other.value == 0.0) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } - return PythonFloat.valueOf(new BigDecimal(value) - .divideToIntegralValue(BigDecimal.valueOf(other.value)) - .doubleValue()); - } - - public PythonFloat ceilDivide(PythonFloat other) { - if (other.value == 0.0) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } - return PythonFloat.valueOf(new BigDecimal(value) - .divide(BigDecimal.valueOf(other.value), RoundingMode.CEILING) - .doubleValue()); - } - - public PythonLikeObject modulo(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return modulo((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return modulo((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger modulo(PythonInteger other) { - int remainderSign = other.compareTo(ZERO); - - if (remainderSign == 0) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } else if (remainderSign > 0) { - BigInteger remainder = value.remainder(other.value); - if (remainder.compareTo(BigInteger.ZERO) < 0) { - remainder = other.value.add(remainder); - } - return new PythonInteger(remainder); - } else { - BigInteger remainder = value.remainder(other.value); - if (remainder.compareTo(BigInteger.ZERO) > 0) { - remainder = other.value.add(remainder); - } - return new PythonInteger(remainder); - } - } - - public PythonFloat modulo(PythonFloat other) { - int remainderSign = other.compareTo(ZERO); - double doubleValue = value.doubleValue(); - - if (remainderSign == 0) { - throw new ZeroDivisionError("integer division or modulo by zero"); - } else if (remainderSign > 0) { - double remainder = doubleValue % other.value; - if (remainder < 0) { - remainder = remainder + other.value; - } - return new PythonFloat(remainder); - } else { - double remainder = doubleValue % other.value; - if (remainder > 0) { - remainder = remainder + other.value; - } - return new PythonFloat(remainder); - } - } - - public PythonLikeTuple divmod(PythonInteger other) { - BigInteger[] result = value.divideAndRemainder(other.value); - - // Python remainder has sign of divisor - if (other.value.compareTo(BigInteger.ZERO) < 0) { - if (result[1].compareTo(BigInteger.ZERO) > 0) { - result[0] = result[0].subtract(BigInteger.ONE); - result[1] = result[1].add(other.value); - } - } else { - if (result[1].compareTo(BigInteger.ZERO) < 0) { - result[0] = result[0].subtract(BigInteger.ONE); - result[1] = result[1].add(other.value); - } - } - return PythonLikeTuple.fromItems(PythonInteger.valueOf(result[0]), - PythonInteger.valueOf(result[1])); - } - - public PythonLikeTuple divmod(PythonFloat other) { - PythonFloat quotient; - - if (value.compareTo(BigInteger.ZERO) < 0 == other.value < 0) { - // Same sign, use floor division - quotient = floorDivide(other); - } else { - // Different sign, use ceil division - quotient = ceilDivide(other); - } - double remainder = value.doubleValue() % other.value; - - // Python remainder has sign of divisor - if (other.value < 0) { - if (remainder > 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value; - } - } else { - if (remainder < 0) { - quotient = quotient.subtract(PythonInteger.ONE); - remainder = remainder + other.value; - } - } - return PythonLikeTuple.fromItems(quotient, new PythonFloat(remainder)); - } - - public PythonInteger round() { - return this; - } - - public PythonInteger round(PythonInteger digitsAfterDecimal) { - if (digitsAfterDecimal.compareTo(PythonInteger.ZERO) >= 0) { - return this; - } - - BigInteger powerOfTen = BigInteger.TEN.pow(-digitsAfterDecimal.value.intValueExact()); - BigInteger halfPowerOfTen = powerOfTen.shiftRight(1); - BigInteger remainder = value.mod(powerOfTen); - - BigInteger previous = value.subtract(remainder); - BigInteger next = value.add(powerOfTen.subtract(remainder)); - - if (remainder.equals(halfPowerOfTen)) { - if (previous.divide(powerOfTen).mod(BigInteger.TWO).equals(BigInteger.ZERO)) { - // previous even - return PythonInteger.valueOf(previous); - } else { - // next even - return PythonInteger.valueOf(next); - } - } else if (remainder.compareTo(halfPowerOfTen) < 0) { - // previous closer - return PythonInteger.valueOf(previous); - } else { - // next closer - return PythonInteger.valueOf(next); - } - } - - public PythonLikeObject power(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return power((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return power((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonNumber power(PythonInteger other) { - if (other.value.signum() >= 0) { - return new PythonInteger(value.pow(other.value.intValueExact())); - } - return new PythonFloat(Math.pow(value.doubleValue(), other.value.doubleValue())); - } - - public PythonInteger power(PythonInteger exponent, PythonInteger modulus) { - return PythonInteger.valueOf(value.modPow(exponent.value, modulus.value)); - } - - public PythonFloat power(PythonFloat other) { - return new PythonFloat(Math.pow(value.doubleValue(), other.value)); - } - - public PythonLikeObject shiftLeft(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return shiftLeft((PythonInteger) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger shiftLeft(PythonInteger other) { - return new PythonInteger(value.shiftLeft(other.value.intValueExact())); - } - - public PythonLikeObject shiftRight(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return shiftRight((PythonInteger) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger shiftRight(PythonInteger other) { - return new PythonInteger(value.shiftRight(other.value.intValueExact())); - } - - public PythonLikeObject bitwiseAnd(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return bitwiseAnd((PythonInteger) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger bitwiseAnd(PythonInteger other) { - return new PythonInteger(value.and(other.value)); - } - - public PythonLikeObject bitwiseOr(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return bitwiseOr((PythonInteger) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger bitwiseOr(PythonInteger other) { - return new PythonInteger(value.or(other.value)); - } - - public PythonLikeObject bitwiseXor(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return bitwiseXor((PythonInteger) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonInteger bitwiseXor(PythonInteger other) { - return new PythonInteger(value.xor(other.value)); - } - - public PythonLikeObject pythonEquals(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return pythonEquals((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return pythonEquals((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject notEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return notEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return notEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject lessThan(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return lessThan((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return lessThan((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject lessThanOrEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return lessThanOrEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return lessThanOrEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject greaterThan(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return greaterThan((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return greaterThan((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonLikeObject greaterThanOrEqual(PythonLikeObject other) { - if (other instanceof PythonInteger) { - return greaterThanOrEqual((PythonInteger) other); - } else if (other instanceof PythonFloat) { - return greaterThanOrEqual((PythonFloat) other); - } else { - return NotImplemented.INSTANCE; - } - } - - public PythonBoolean pythonEquals(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) == 0); - } - - public PythonBoolean notEqual(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) != 0); - } - - public PythonBoolean lessThan(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) < 0); - } - - public PythonBoolean lessThanOrEqual(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) <= 0); - } - - public PythonBoolean greaterThan(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) > 0); - } - - public PythonBoolean greaterThanOrEqual(PythonInteger other) { - return PythonBoolean.valueOf(value.compareTo(other.value) >= 0); - } - - public PythonBoolean pythonEquals(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() == other.value); - } - - public PythonBoolean notEqual(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() != other.value); - } - - public PythonBoolean lessThan(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() < other.value); - } - - public PythonBoolean lessThanOrEqual(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() <= other.value); - } - - public PythonBoolean greaterThan(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() > other.value); - } - - public PythonBoolean greaterThanOrEqual(PythonFloat other) { - return PythonBoolean.valueOf(value.doubleValue() >= other.value); - } - - public PythonString asString() { - return PythonString.valueOf(value.toString()); - } - - public PythonString $method$__format__() { - return PythonString.valueOf(value.toString()); - } - - public PythonString $method$__format__(PythonLikeObject specObject) { - PythonString spec; - if (specObject == PythonNone.INSTANCE) { - spec = PythonString.EMPTY; - } else if (specObject instanceof PythonString) { - spec = (PythonString) specObject; - } else { - throw new TypeError("__format__ argument 0 has incorrect type (expecting str or None)"); - } - DefaultFormatSpec formatSpec = DefaultFormatSpec.fromSpec(spec); - - StringBuilder out = new StringBuilder(); - - String alternateFormPrefix = null; - int groupSize; - - if (formatSpec.precision.isPresent()) { - throw new ValueError("Precision not allowed in integer format specifier"); - } - switch (formatSpec.conversionType.orElse(DefaultFormatSpec.ConversionType.DECIMAL)) { - case BINARY: - alternateFormPrefix = "0b"; - groupSize = 4; - out.append(value.toString(2)); - break; - case OCTAL: - alternateFormPrefix = "0o"; - groupSize = 4; - out.append(value.toString(8)); - break; - case DECIMAL: - out.append(value.toString(10)); - groupSize = 3; - break; - case LOWERCASE_HEX: - alternateFormPrefix = "0x"; - groupSize = 4; - out.append(value.toString(16)); - break; - case UPPERCASE_HEX: - alternateFormPrefix = "0X"; - groupSize = 4; - out.append(value.toString(16).toUpperCase()); - break; - case CHARACTER: - groupSize = -1; - out.appendCodePoint(value.intValueExact()); - break; - case LOCALE_SENSITIVE: - groupSize = -1; - NumberFormat.getIntegerInstance().format(value); - break; - default: - throw new ValueError("Invalid format spec for int: " + spec); - } - - StringFormatter.addGroupings(out, formatSpec, groupSize); - switch (formatSpec.signOption.orElse(DefaultFormatSpec.SignOption.ONLY_NEGATIVE_NUMBERS)) { - case ALWAYS_SIGN: - if (out.charAt(0) != '-') { - out.insert(0, '+'); - } - break; - case ONLY_NEGATIVE_NUMBERS: - break; - case SPACE_FOR_POSITIVE_NUMBERS: - if (out.charAt(0) != '-') { - out.insert(0, ' '); - } - break; - default: - throw new IllegalStateException("Unhandled case: " + formatSpec.signOption); - } - - if (formatSpec.useAlternateForm && alternateFormPrefix != null) { - StringFormatter.alignWithPrefixRespectingSign(out, alternateFormPrefix, formatSpec, - DefaultFormatSpec.AlignmentOption.RIGHT_ALIGN); - } else { - StringFormatter.align(out, formatSpec, DefaultFormatSpec.AlignmentOption.RIGHT_ALIGN); - } - - return PythonString.valueOf(out.toString()); - } - - @Override - public T coerce(Class targetType) { - if (targetType.equals(PythonFloat.class)) { - return (T) PythonFloat.valueOf(value.doubleValue()); - } - return null; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonNumber.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonNumber.java deleted file mode 100644 index 3c5cb4f8..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/numeric/PythonNumber.java +++ /dev/null @@ -1,70 +0,0 @@ -package ai.timefold.jpyinterpreter.types.numeric; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeComparable; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -public interface PythonNumber extends PythonLikeComparable, - PythonLikeObject { - - PythonLikeType NUMBER_TYPE = new PythonLikeType("number", PythonNumber.class); - PythonInteger MODULUS = PythonInteger.valueOf((1L << 61) - 1); - PythonInteger INFINITY_HASH_VALUE = PythonInteger.valueOf(314159); - - Number getValue(); - - @Override - default int compareTo(PythonNumber pythonNumber) { - Number value = getValue(); - Number otherValue = pythonNumber.getValue(); - - if (value instanceof BigInteger self) { - if (otherValue instanceof BigInteger other) { - return self.compareTo(other); - } else if (otherValue instanceof BigDecimal other) { - return new BigDecimal(self).compareTo(other); - } - } - if (value instanceof BigDecimal self) { - if (otherValue instanceof BigDecimal other) { - return self.compareTo(other); - } else if (otherValue instanceof BigInteger other) { - return self.compareTo(new BigDecimal(other)); - } - } - // If comparing against a float, convert both arguments to float - return Double.compare(value.doubleValue(), otherValue.doubleValue()); - } - - static PythonInteger computeHash(PythonInteger numerator, PythonInteger denominator) { - PythonInteger P = MODULUS; - // Remove common factors of P. (Unnecessary if m and n already coprime.) - - while (numerator.modulo(P).equals(PythonInteger.ZERO) && denominator.modulo(P).equals(PythonInteger.ZERO)) { - numerator = numerator.floorDivide(P); - denominator = denominator.floorDivide(P); - } - - PythonInteger hash_value; - if (denominator.modulo(P).equals(PythonInteger.ZERO)) { - hash_value = INFINITY_HASH_VALUE; - } else { - // Fermat's Little Theorem: pow(n, P-1, P) is 1, so - // pow(n, P-2, P) gives the inverse of n modulo P. - hash_value = (numerator.abs().modulo(P)).multiply(denominator.power(P.subtract(PythonInteger.TWO), P)).modulo(P); - } - - if (numerator.lessThan(PythonInteger.ZERO).getBooleanValue()) { - hash_value = hash_value.negative(); - } - - if (hash_value.equals(PythonInteger.valueOf(-1))) { - hash_value = PythonInteger.valueOf(-2); - } - - return hash_value; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/CPythonType.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/CPythonType.java deleted file mode 100644 index 1b9a1d42..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/CPythonType.java +++ /dev/null @@ -1,142 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class CPythonType extends PythonLikeType { - - private static final Map cpythonTypeMap = new HashMap<>(); - - private final OpaquePythonReference pythonReference; - private final Map cachedAttributeMap; - - private static String getTypeName(OpaquePythonReference pythonReference) { - return ((PythonString) CPythonBackedPythonInterpreter - .lookupAttributeOnPythonReference(pythonReference, "__name__")) - .getValue(); - } - - public static CPythonType lookupTypeOfPythonObject(OpaquePythonReference reference) { - OpaquePythonReference type = CPythonBackedPythonInterpreter.getPythonReferenceType(reference); - return cpythonTypeMap.computeIfAbsent(CPythonBackedPythonInterpreter.getPythonReferenceId(type), - key -> new CPythonType(type)); - } - - public static CPythonType getType(OpaquePythonReference typeReference) { - return cpythonTypeMap.computeIfAbsent(CPythonBackedPythonInterpreter.getPythonReferenceId(typeReference), - key -> new CPythonType(typeReference)); - } - - private CPythonType(OpaquePythonReference pythonReference) { - super(getTypeName(pythonReference), PythonObjectWrapper.class); - this.pythonReference = pythonReference; - this.cachedAttributeMap = new HashMap<>(); - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - switch (attributeName) { - case "__eq__": - return cachedAttributeMap.computeIfAbsent(attributeName, - key -> { - PythonLikeObject equals = - CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference(pythonReference, - attributeName); - if (equals instanceof PythonLikeFunction) { - final PythonLikeFunction function = (PythonLikeFunction) equals; - return (PythonLikeFunction) (pos, named, callerInstance) -> { - PythonLikeObject result = function.$call(pos, named, null); - if (result instanceof PythonBoolean) { - return result; - } else { - return PythonBoolean.valueOf(pos.get(0) == pos.get(1)); - } - }; - } else { - return equals; - } - }); - case "__ne__": - return cachedAttributeMap.computeIfAbsent(attributeName, - key -> { - PythonLikeObject notEquals = - CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference(pythonReference, - attributeName); - if (notEquals instanceof PythonLikeFunction) { - final PythonLikeFunction function = (PythonLikeFunction) notEquals; - return (PythonLikeFunction) (pos, named, callerInstance) -> { - PythonLikeObject result = function.$call(pos, named, null); - if (result instanceof PythonBoolean) { - return result; - } else { - return PythonBoolean.valueOf(pos.get(0) != pos.get(1)); - } - }; - } else { - return notEquals; - } - }); - case "__hash__": - return cachedAttributeMap.computeIfAbsent(attributeName, - key -> { - PythonLikeObject hash = - CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference(pythonReference, - attributeName); - if (hash instanceof PythonLikeFunction) { - final PythonLikeFunction function = (PythonLikeFunction) hash; - return (PythonLikeFunction) (pos, named, callerInstance) -> { - PythonLikeObject result = function.$call(pos, named, null); - if (result instanceof PythonInteger) { - return result; - } else { - return PythonInteger.valueOf(System.identityHashCode(pos.get(0))); - } - }; - } else { - return hash; - } - }); - default: - return cachedAttributeMap.computeIfAbsent(attributeName, - key -> CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference(pythonReference, - attributeName)); - } - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - cachedAttributeMap.put(attributeName, value); - CPythonBackedPythonInterpreter.setAttributeOnPythonReference(pythonReference, null, attributeName, value); - } - - @Override - public void $deleteAttribute(String attributeName) { - cachedAttributeMap.remove(attributeName); - CPythonBackedPythonInterpreter.deleteAttributeOnPythonReference(pythonReference, attributeName); - } - - @Override - public PythonLikeType $getType() { - return BuiltinTypes.TYPE_TYPE; - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - return CPythonBackedPythonInterpreter.callPythonReference(pythonReference, positionalArguments, namedArguments); - } - - public OpaquePythonReference getPythonReference() { - return pythonReference; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaMethodReference.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaMethodReference.java deleted file mode 100644 index 2f30a895..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaMethodReference.java +++ /dev/null @@ -1,81 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; - -public class JavaMethodReference implements PythonLikeFunction { - private final Method method; - private final Map parameterNameToIndexMap; - - public JavaMethodReference(Method method, Map parameterNameToIndexMap) { - this.method = method; - this.parameterNameToIndexMap = parameterNameToIndexMap; - } - - public Method getMethod() { - return method; - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - Object self; - Object[] args; - if (Modifier.isStatic(method.getModifiers())) { - self = null; - args = unwrapPrimitiveArguments(positionalArguments, namedArguments); - } else { - self = positionalArguments.get(0); - if (self instanceof JavaObjectWrapper) { // unwrap wrapped Java Objects - self = ((JavaObjectWrapper) self).getWrappedObject(); - } - args = unwrapPrimitiveArguments(positionalArguments.subList(1, positionalArguments.size()), namedArguments); - } - try { - return JavaPythonTypeConversionImplementor.wrapJavaObject(method.invoke(self, args)); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Method (" + method + ") is not accessible.", e); - } catch (InvocationTargetException e) { - throw (RuntimeException) e.getCause(); - } - } - - private Object[] unwrapPrimitiveArguments(List positionalArguments, - Map namedArguments) { - namedArguments = (namedArguments != null) ? namedArguments : Map.of(); - Object[] out = new Object[method.getParameterCount()]; - Class[] parameterTypes = method.getParameterTypes(); - - for (int i = 0; i < positionalArguments.size(); i++) { - PythonLikeObject argument = positionalArguments.get(i); - out[i] = JavaPythonTypeConversionImplementor.convertPythonObjectToJavaType(parameterTypes[i], argument); - } - - for (PythonString key : namedArguments.keySet()) { - int index = parameterNameToIndexMap.get(key.value); - PythonLikeObject argument = namedArguments.get(key); - out[index] = JavaPythonTypeConversionImplementor.convertPythonObjectToJavaType(parameterTypes[index], argument); - } - - return out; - } - - @Override - public PythonLikeType $getType() { - if (Modifier.isStatic(method.getModifiers())) { - return BuiltinTypes.STATIC_FUNCTION_TYPE; - } else { - return BuiltinTypes.FUNCTION_TYPE; - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java deleted file mode 100644 index 3ed68b70..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java +++ /dev/null @@ -1,323 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; -import ai.timefold.jpyinterpreter.types.errors.RuntimeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class JavaObjectWrapper implements PythonLikeObject, - Iterable, - Comparable { - - final static Map, PythonLikeType> classToPythonTypeMap = new HashMap<>(); - final static Map, Map> classToAttributeNameToMemberListMap = new HashMap<>(); - - private final PythonLikeType type; - - private final Object wrappedObject; - private final Class objectClass; - private final Map attributeNameToMemberListMap; - private final Map convertedObjectMap; - - private static Map getAllFields(Class baseClass) { - return getAllDeclaredMembers(baseClass) - .stream() - .filter(member -> member instanceof Field && !Modifier.isStatic(member.getModifiers())) - .collect(Collectors.toMap(Member::getName, member -> (Field) member, - (oldMember, newMember) -> { - if (oldMember.getDeclaringClass().isAssignableFrom(newMember.getDeclaringClass())) { - return newMember; - } else { - return oldMember; - } - })); - } - - private static List getAllDeclaredMembers(Class baseClass) { - Class clazz = baseClass; - - List members = new ArrayList<>(); - members.addAll(Arrays.asList(clazz.getFields())); - members.addAll(Arrays.asList(clazz.getMethods())); - return members; - } - - private Method getGetterMethod(Field field) { - String getterName; - if (objectClass.isRecord()) { - getterName = field.getName(); - } else { - String propertyName = field.getName(); - String capitalizedName = - propertyName.isEmpty() ? "" : propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); - getterName = (field.getType().equals(boolean.class) ? "is" : "get") - + capitalizedName; - } - PythonLikeObject object = type.$getAttributeOrNull(getterName); - if (object instanceof JavaMethodReference methodReference) { - return methodReference.getMethod(); - } - if (object instanceof MultiDispatchJavaMethodReference multiDispatchJavaMethodReference) { - return multiDispatchJavaMethodReference.getNoArgsMethod(); - } - throw new AttributeError("Cannot get attribute (%s) on class (%s)." - .formatted(field.getName(), type)); - } - - private Method getSetterMethod(Field field) { - String propertyName = field.getName(); - String capitalizedName = - propertyName.isEmpty() ? "" : propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); - String setterName = "set" + capitalizedName; - - PythonLikeObject object = type.$getAttributeOrNull(setterName); - if (object instanceof JavaMethodReference methodReference) { - return methodReference.getMethod(); - } - throw new AttributeError("Cannot set attribute (%s) on class (%s)." - .formatted(field.getName(), type)); - } - - public JavaObjectWrapper(Object wrappedObject) { - this(wrappedObject, new IdentityHashMap<>()); - } - - public JavaObjectWrapper(Object wrappedObject, Map convertedObjectMap) { - convertedObjectMap.put(wrappedObject, this); - this.wrappedObject = wrappedObject; - this.objectClass = wrappedObject.getClass(); - this.convertedObjectMap = convertedObjectMap; - this.attributeNameToMemberListMap = - classToAttributeNameToMemberListMap.computeIfAbsent(objectClass, JavaObjectWrapper::getAllFields); - this.type = getPythonTypeForClass(objectClass, convertedObjectMap); - } - - public static PythonLikeType getPythonTypeForClass(Class objectClass) { - return getPythonTypeForClass(objectClass, new IdentityHashMap<>()); - } - - public static PythonLikeType getPythonTypeForClass(Class objectClass, Map convertedObjectMap) { - if (classToPythonTypeMap.containsKey(objectClass)) { - return classToPythonTypeMap.get(objectClass); - } - PythonLikeType out = generatePythonTypeForClass(objectClass, convertedObjectMap); - classToPythonTypeMap.put(objectClass, out); - return out; - } - - private static boolean isInaccessible(Member member) { - return isInaccessible(member.getDeclaringClass()); - } - - private static boolean isInaccessible(Class clazz) { - for (Class declaringClass = clazz; declaringClass != null; declaringClass = declaringClass.getDeclaringClass()) { - if (!Modifier.isPublic(declaringClass.getModifiers())) { - return true; - } - } - return false; - } - - private static Method findMethodInInterfaces(Class declaringClass, Method method) { - Queue> toVisit = new ArrayDeque<>(); - while (declaringClass != null) { - toVisit.addAll(List.of(declaringClass.getInterfaces())); - declaringClass = declaringClass.getSuperclass(); - } - Set> visited = new HashSet<>(); - while (!toVisit.isEmpty()) { - Class interfaceClass = toVisit.poll(); - if (visited.contains(interfaceClass)) { - continue; - } - visited.add(interfaceClass); - toVisit.addAll(Arrays.asList(interfaceClass.getInterfaces())); - if (isInaccessible(interfaceClass)) { - continue; - } - try { - return interfaceClass.getMethod(method.getName(), method.getParameterTypes()); - } catch (NoSuchMethodException e) { - // Intentionally empty, need to search other interfaces - } - } - return null; - } - - private static void addMemberToPythonType(PythonLikeType type, Member member, - Map convertedObjectMap) { - if (member instanceof Method method) { - if (isInaccessible(member)) { - method = findMethodInInterfaces(method.getDeclaringClass(), method); - if (method == null) { - return; - } - } - // For certain Collection/List/Map methods, also add their corresponding dunder methods, - // so a[x], x in a, len(a) will work. - switch (method.getName()) { - case "size" -> { - if (Collection.class.isAssignableFrom(method.getDeclaringClass())) { - type.__dir__.put(PythonUnaryOperator.LENGTH.getDunderMethod(), - new JavaMethodReference(method, Map.of())); - } - } - case "contains" -> { - if (Collection.class.isAssignableFrom(method.getDeclaringClass())) { - type.__dir__.put(PythonBinaryOperator.CONTAINS.getDunderMethod(), - new JavaMethodReference(method, Map.of())); - } - } - case "get" -> { - type.__dir__.put(PythonBinaryOperator.GET_ITEM.getDunderMethod(), - new JavaMethodReference(method, Map.of())); - } - } - ((MultiDispatchJavaMethodReference) type.__dir__.computeIfAbsent(method.getName(), - (name) -> new MultiDispatchJavaMethodReference())).addMethod(method); - } else { - if (isInaccessible(member)) { - return; - } - Field field = (Field) member; - if (Modifier.isPublic(field.getModifiers())) { - try { - type.__dir__.put(field.getName(), - JavaPythonTypeConversionImplementor.wrapJavaObject(field.get(null), - convertedObjectMap)); - } catch (IllegalAccessException e) { - throw (RuntimeError) new RuntimeError("Cannot get attribute (%s) on type (%s)." - .formatted(field.getName(), type.getTypeName())).initCause(e); - } - } - } - } - - private static PythonLikeType generatePythonTypeForClass(Class objectClass, - Map convertedObjectMap) { - var out = new PythonLikeType(objectClass.getName(), JavaObjectWrapper.class, objectClass); - getAllDeclaredMembers(objectClass) - .stream() - .filter(member -> Modifier.isStatic(member.getModifiers()) || member instanceof Method) - .forEach(member -> addMemberToPythonType(out, member, convertedObjectMap)); - return out; - } - - public Object getWrappedObject() { - return wrappedObject; - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - Field field = attributeNameToMemberListMap.get(attributeName); - if (field == null) { - return null; - } - Object result; - try { - if (Modifier.isPublic(field.getModifiers())) { - result = field.get(wrappedObject); - } else { - Method getterMethod = getGetterMethod(field); - result = getterMethod.invoke(wrappedObject); - } - return JavaPythonTypeConversionImplementor.wrapJavaObject(result, convertedObjectMap); - } catch (InvocationTargetException | IllegalAccessException e) { - throw (RuntimeError) new RuntimeError("Cannot get attribute (%s) on object (%s)." - .formatted(attributeName, this)).initCause(e); - } - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - Field field = attributeNameToMemberListMap.get(attributeName); - if (field == null) { - throw new AttributeError("(%s) object does not have attribute (%s)." - .formatted(type, attributeName)); - } - try { - Object javaObject = JavaPythonTypeConversionImplementor.convertPythonObjectToJavaType(field.getType(), value); - if (Modifier.isPublic(field.getModifiers())) { - field.set(wrappedObject, javaObject); - } else { - Method setterMethod = getSetterMethod(field); - setterMethod.invoke(wrappedObject, javaObject); - } - } catch (InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - @Override - public void $deleteAttribute(String attributeName) { - throw new IllegalArgumentException("Cannot delete attributes on type '" + objectClass + "'"); - } - - @Override - public PythonLikeType $getType() { - return type; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public int compareTo(JavaObjectWrapper javaObjectWrapper) { - if (!(wrappedObject instanceof Comparable comparable)) { - throw new IllegalStateException("Class (%s) does not implement (%s).".formatted(objectClass, Comparable.class)); - } - return comparable.compareTo(javaObjectWrapper.wrappedObject); - } - - @Override - public Iterator iterator() { - if (!(wrappedObject instanceof Iterable iterable)) { - throw new IllegalStateException("Class (%s) does not implement (%s).".formatted(objectClass, Iterable.class)); - } - return new WrappingJavaObjectIterator(iterable.iterator()); - } - - @Override - public String toString() { - return wrappedObject.toString(); - } - - @Override - public boolean equals(Object other) { - if (other instanceof JavaObjectWrapper) { - return wrappedObject.equals(((JavaObjectWrapper) other).wrappedObject); - } - return wrappedObject.equals(other); - } - - @Override - public int hashCode() { - return wrappedObject.hashCode(); - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/MultiDispatchJavaMethodReference.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/MultiDispatchJavaMethodReference.java deleted file mode 100644 index 62eaec2c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/MultiDispatchJavaMethodReference.java +++ /dev/null @@ -1,101 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.TypeError; - -public class MultiDispatchJavaMethodReference implements PythonLikeFunction { - private final List methodList; - - public MultiDispatchJavaMethodReference() { - this.methodList = new ArrayList<>(); - } - - public void addMethod(Method method) { - methodList.add(method); - } - - public Method getNoArgsMethod() { - for (Method method : methodList) { - if (method.getParameterCount() == 0) { - return method; - } - } - throw new TypeError(); - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - Object self; - Object[] args; - for (Method method : methodList) { - if (Modifier.isStatic(method.getModifiers())) { - if (method.getParameterCount() != positionalArguments.size()) { - continue; - } - self = null; - try { - args = unwrapPrimitiveArguments(method, positionalArguments); - } catch (TypeError e) { - continue; - } - - } else { - if (method.getParameterCount() + 1 != positionalArguments.size()) { - continue; - } - self = positionalArguments.get(0); - if (self instanceof JavaObjectWrapper) { // unwrap wrapped Java Objects - self = ((JavaObjectWrapper) self).getWrappedObject(); - } - try { - args = unwrapPrimitiveArguments(method, positionalArguments.subList(1, positionalArguments.size())); - } catch (TypeError e) { - continue; - } - } - try { - return JavaPythonTypeConversionImplementor.wrapJavaObject(method.invoke(self, args)); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Method (" + method + ") is not accessible.", e); - } catch (InvocationTargetException e) { - throw (RuntimeException) e.getCause(); - } - } - throw new TypeError("No method with matching signature found for %s in method list %s.".formatted(positionalArguments, - methodList)); - } - - private Object[] unwrapPrimitiveArguments(Method method, List positionalArguments) { - Object[] out = new Object[method.getParameterCount()]; - Class[] parameterTypes = method.getParameterTypes(); - - for (int i = 0; i < positionalArguments.size(); i++) { - PythonLikeObject argument = positionalArguments.get(i); - out[i] = JavaPythonTypeConversionImplementor.convertPythonObjectToJavaType(parameterTypes[i], argument); - } - - return out; - } - - @Override - public PythonLikeType $getType() { - if (Modifier.isStatic(methodList.get(0).getModifiers())) { - return BuiltinTypes.STATIC_FUNCTION_TYPE; - } else { - return BuiltinTypes.FUNCTION_TYPE; - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaqueJavaReference.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaqueJavaReference.java deleted file mode 100644 index 0829ed5f..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaqueJavaReference.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -/** - * An interface used to indicate a Java Object - * should not be interacted with directly and should - * be proxied (even if it implements {@link PythonLikeObject}). - */ -public interface OpaqueJavaReference { - /** - * Creates a proxy of the OpaqueJavaReference, which - * can be interacted with directly. - */ - PythonLikeObject proxy(); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaquePythonReference.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaquePythonReference.java deleted file mode 100644 index fd1f4d92..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/OpaquePythonReference.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -/** - * An OpaquePythonReference represents an - * arbitrary Python Object. No methods - * should ever be called on it, including - * hashCode and equals. To operate on - * the Object, pass it to a Python function. - */ -public interface OpaquePythonReference { - // intentionally empty -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonLikeFunctionWrapper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonLikeFunctionWrapper.java deleted file mode 100644 index 8b01ea93..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonLikeFunctionWrapper.java +++ /dev/null @@ -1,95 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; - -/** - * A class used as a delegator to another PythonLikeFunction. - * Used to handle recursion correctly when translating functions. - */ -public final class PythonLikeFunctionWrapper implements PythonLikeFunction { - PythonLikeFunction wrapped; - - public PythonLikeFunctionWrapper() { - this.wrapped = null; - } - - public PythonLikeFunction getWrapped() { - return wrapped; - } - - public void setWrapped(PythonLikeFunction wrapped) { - this.wrapped = wrapped; - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - return wrapped.$call(positionalArguments, namedArguments, callerInstance); - } - - public PythonLikeObject $getAttributeOrNull(String attributeName) { - return wrapped.$getAttributeOrNull(attributeName); - } - - public PythonLikeObject $getAttributeOrError(String attributeName) { - return wrapped.$getAttributeOrError(attributeName); - } - - public void $setAttribute(String attributeName, PythonLikeObject value) { - wrapped.$setAttribute(attributeName, value); - } - - public void $deleteAttribute(String attributeName) { - wrapped.$deleteAttribute(attributeName); - } - - public PythonLikeType $getType() { - return wrapped.$getType(); - } - - public PythonLikeObject $method$__getattribute__(PythonString pythonName) { - return wrapped.$method$__getattribute__(pythonName); - } - - public PythonLikeObject $method$__setattr__(PythonString pythonName, PythonLikeObject value) { - return wrapped.$method$__setattr__(pythonName, value); - } - - public PythonLikeObject $method$__delattr__(PythonString pythonName) { - return wrapped.$method$__delattr__(pythonName); - } - - public PythonLikeObject $method$__eq__(PythonLikeObject other) { - return wrapped.$method$__eq__(other); - } - - public PythonLikeObject $method$__ne__(PythonLikeObject other) { - return wrapped.$method$__ne__(other); - } - - public PythonString $method$__str__() { - return wrapped.$method$__str__(); - } - - public PythonLikeObject $method$__repr__() { - return wrapped.$method$__repr__(); - } - - public PythonLikeObject $method$__format__() { - return wrapped.$method$__format__(); - } - - public PythonLikeObject $method$__format__(PythonLikeObject formatString) { - return wrapped.$method$__format__(formatString); - } - - public PythonLikeObject $method$__hash__() { - return wrapped.$method$__hash__(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java deleted file mode 100644 index ca47d96d..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java +++ /dev/null @@ -1,170 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; -import ai.timefold.jpyinterpreter.types.errors.NotImplementedError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonObjectWrapper extends CPythonBackedPythonLikeObject - implements PythonLikeObject, - PythonLikeFunction, PythonIterator, Comparable { - - private final static PythonLikeType PYTHON_REFERENCE_TYPE = - new PythonLikeType("python-reference", PythonObjectWrapper.class), - $TYPE = PYTHON_REFERENCE_TYPE; - private final Map cachedAttributeMap; - - private PythonLikeObject cachedNext = null; - - public PythonObjectWrapper(OpaquePythonReference pythonReference) { - super(PythonInterpreter.DEFAULT, CPythonType.lookupTypeOfPythonObject(pythonReference), pythonReference); - cachedAttributeMap = new HashMap<>(); - } - - public OpaquePythonReference getWrappedObject() { - return $cpythonReference; - } - - @Override - public PythonLikeObject $getAttributeOrNull(String attributeName) { - return cachedAttributeMap.computeIfAbsent(attributeName, - key -> CPythonBackedPythonInterpreter.lookupAttributeOnPythonReference($cpythonReference, - attributeName, $instanceMap)); - } - - @Override - public void $setAttribute(String attributeName, PythonLikeObject value) { - cachedAttributeMap.put(attributeName, value); - CPythonBackedPythonInterpreter.setAttributeOnPythonReference($cpythonReference, null, attributeName, value); - } - - @Override - public void $deleteAttribute(String attributeName) { - cachedAttributeMap.remove(attributeName); - CPythonBackedPythonInterpreter.deleteAttributeOnPythonReference($cpythonReference, attributeName); - } - - @Override - public PythonLikeObject $call(List positionalArguments, - Map namedArguments, PythonLikeObject callerInstance) { - return CPythonBackedPythonInterpreter.callPythonReference($cpythonReference, positionalArguments, namedArguments); - } - - @Override - public int compareTo(PythonObjectWrapper other) { - if (equals(other)) { - return 0; - } - - PythonLikeFunction lessThan = (PythonLikeFunction) $getType().$getAttributeOrError("__lt__"); - PythonLikeObject result = lessThan.$call(List.of(this, other), Map.of(), null); - - if (result instanceof PythonBoolean) { - if (((PythonBoolean) result).getBooleanValue()) { - return -1; - } else { - return 1; - } - } else { - throw new NotImplementedError("Cannot compare " + this.$getType().getTypeName() + - " with " + other.$getType().getTypeName()); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof PythonObjectWrapper)) { - return false; - } - PythonObjectWrapper other = (PythonObjectWrapper) o; - Object maybeEquals = $getType().$getAttributeOrNull("__eq__"); - if (!(maybeEquals instanceof PythonLikeFunction)) { - return super.equals(o); - } - PythonLikeFunction equals = (PythonLikeFunction) maybeEquals; - PythonLikeObject result = equals.$call(List.of(this, other), Map.of(), null); - if (result instanceof PythonBoolean) { - return ((PythonBoolean) result).getBooleanValue(); - } - return false; - } - - @Override - public int hashCode() { - Object maybeHash = $getType().$getAttributeOrNull("__hash__"); - if (!(maybeHash instanceof PythonLikeFunction)) { - return super.hashCode(); - } - PythonLikeFunction hash = (PythonLikeFunction) maybeHash; - PythonLikeObject result = hash.$call(List.of(this), Map.of(), null); - if (result instanceof PythonInteger) { - return ((PythonInteger) result).value.hashCode(); - } else { - return System.identityHashCode(this); - } - } - - @Override - public PythonInteger $method$__hash__() { - return PythonInteger.valueOf(hashCode()); - } - - @Override - public String toString() { - Object maybeStr = $getType().$getAttributeOrNull("__str__"); - if (!(maybeStr instanceof PythonLikeFunction)) { - return super.toString(); - } - PythonLikeFunction str = (PythonLikeFunction) maybeStr; - PythonLikeObject result = str.$call(List.of(this), Map.of(), null); - return result.toString(); - } - - @Override - public PythonLikeObject nextPythonItem() { - if (cachedNext != null) { - var out = cachedNext; - cachedNext = null; - return out; - } - return UnaryDunderBuiltin.NEXT.invoke(this); - } - - @Override - public PythonIterator getIterator() { - return (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(this); - } - - @Override - public boolean hasNext() { - if (cachedNext != null) { - return true; - } - try { - cachedNext = nextPythonItem(); - return true; - } catch (Exception e) { - return false; - } - } - - @Override - public PythonLikeObject next() { - return nextPythonItem(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/WrappingJavaObjectIterator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/WrappingJavaObjectIterator.java deleted file mode 100644 index e5c83320..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/WrappingJavaObjectIterator.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import java.util.Iterator; - -record WrappingJavaObjectIterator(Iterator delegate) implements Iterator { - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public JavaObjectWrapper next() { - return new JavaObjectWrapper(delegate.next()); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ByteCharSequence.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ByteCharSequence.java deleted file mode 100644 index 4face22c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ByteCharSequence.java +++ /dev/null @@ -1,48 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -public final class ByteCharSequence implements CharSequence { - - private final byte[] data; - private final int inclusiveStartIndex; - private final int exclusiveEndIndex; - - public ByteCharSequence(byte[] data) { - this.data = data; - this.inclusiveStartIndex = 0; - this.exclusiveEndIndex = data.length; - } - - public ByteCharSequence(byte[] data, int inclusiveStartIndex, int exclusiveEndIndex) { - this.data = data; - this.inclusiveStartIndex = inclusiveStartIndex; - this.exclusiveEndIndex = exclusiveEndIndex; - } - - @Override - public int length() { - return exclusiveEndIndex - inclusiveStartIndex; - } - - @Override - public char charAt(int i) { - return (char) (data[inclusiveStartIndex + i] & 0xFF); - } - - @Override - public ByteCharSequence subSequence(int from, int to) { - return new ByteCharSequence(data, - inclusiveStartIndex + from, - inclusiveStartIndex + to); - } - - @Override - public String toString() { - char[] chars = new char[length()]; - - for (int i = 0; i < length(); i++) { - chars[i] = charAt(i); - } - - return String.valueOf(chars); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java deleted file mode 100644 index f07ff65c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java +++ /dev/null @@ -1,241 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Source: Spring - * Loaded ConcurrentWeakIdentityHashMap - */ -public class ConcurrentWeakIdentityHashMap extends AbstractMap implements ConcurrentMap { - private final ConcurrentMap, V> map; - private final ReferenceQueue queue = new ReferenceQueue<>(); - private transient Set> entry; - - public ConcurrentWeakIdentityHashMap(int initialCapacity) { - this.map = new ConcurrentHashMap<>(initialCapacity); - } - - public ConcurrentWeakIdentityHashMap() { - this.map = new ConcurrentHashMap<>(); - } - - @Override - public V get(Object key) { - purgeKeys(); - return map.get(new Key<>(key, null)); - } - - @Override - public V put(K key, V value) { - purgeKeys(); - return map.put(new Key<>(key, queue), value); - } - - @Override - public int size() { - purgeKeys(); - return map.size(); - } - - private void purgeKeys() { - Reference reference; - while ((reference = queue.poll()) != null) { - map.remove(reference); - } - } - - @Override - public Set> entrySet() { - Set> entrySet; - return ((entrySet = this.entry) == null) ? entry = new EntrySet() : entrySet; - } - - @Override - public V putIfAbsent(K key, V value) { - purgeKeys(); - return map.putIfAbsent(new Key<>(key, queue), value); - } - - @Override - public V remove(Object key) { - return map.remove(new Key<>(key, null)); - } - - @Override - public boolean remove(Object key, Object value) { - purgeKeys(); - return map.remove(new Key<>(key, null), value); - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - purgeKeys(); - return map.replace(new Key<>(key, null), oldValue, newValue); - } - - @Override - public V replace(K key, V value) { - purgeKeys(); - return map.replace(new Key<>(key, null), value); - } - - @Override - public boolean containsKey(Object key) { - purgeKeys(); - return map.containsKey(new Key<>(key, null)); - } - - @Override - public void clear() { - while (queue.poll() != null) - ; - map.clear(); - } - - @Override - public boolean containsValue(Object value) { - purgeKeys(); - return map.containsValue(value); - } - - private static class Key extends WeakReference { - - private final int hash; - - Key(T t, ReferenceQueue queue) { - super(t, queue); - if (t == null) { - throw new NullPointerException(); - } else { - hash = System.identityHashCode(t); - } - } - - @Override - public boolean equals(Object obj) { - return this == obj || obj instanceof Key && ((Key) obj).get() == get(); - } - - @Override - public int hashCode() { - return hash; - } - - } - - private class Iter implements Iterator> { - - private final Iterator, V>> it; - private Map.Entry nextValue; - - Iter(Iterator, V>> it) { - this.it = it; - } - - @Override - public boolean hasNext() { - if (nextValue != null) { - return true; - } - while (it.hasNext()) { - Map.Entry, V> entry = it.next(); - K key = entry.getKey().get(); - if (key != null) { - nextValue = new Entry(key, entry.getValue()); - return true; - } else { - it.remove(); - } - } - return false; - } - - @Override - public Map.Entry next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - Map.Entry entry = nextValue; - nextValue = null; - return entry; - } - - @Override - public void remove() { - it.remove(); - nextValue = null; - } - - } - - private class EntrySet extends AbstractSet> { - - @Override - public Iterator> iterator() { - return new Iter(map.entrySet().iterator()); - } - - @Override - public int size() { - return ConcurrentWeakIdentityHashMap.this.size(); - } - - @Override - public void clear() { - ConcurrentWeakIdentityHashMap.this.clear(); - } - - @Override - public boolean contains(Object o) { - if (!(o instanceof Map.Entry e)) { - return false; - } - return ConcurrentWeakIdentityHashMap.this.get(e.getKey()) == e.getValue(); - } - - @Override - public boolean remove(Object o) { - if (!(o instanceof Map.Entry e)) { - return false; - } - return ConcurrentWeakIdentityHashMap.this.remove(e.getKey(), e.getValue()); - } - } - - private class Entry extends AbstractMap.SimpleEntry { - Entry(K key, V value) { - super(key, value); - } - - @Override - public V setValue(V value) { - ConcurrentWeakIdentityHashMap.this.put(getKey(), value); - return super.setValue(value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Map.Entry e) { - return getKey() == e.getKey() && getValue() == e.getValue(); - } - return false; - } - - @Override - public int hashCode() { - return System.identityHashCode(getKey()) - ^ System.identityHashCode(getValue()); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/CopyOnWriteMap.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/CopyOnWriteMap.java deleted file mode 100644 index 3f4b19a2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/CopyOnWriteMap.java +++ /dev/null @@ -1,91 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -public class CopyOnWriteMap implements Map { - private final Map immutableMap; - private Optional> modifiedMap; - - public CopyOnWriteMap(Map immutableMap) { - this.immutableMap = immutableMap; - this.modifiedMap = Optional.empty(); - } - - // Read Operations - @Override - public int size() { - return modifiedMap.map(Map::size).orElseGet(immutableMap::size); - } - - @Override - public boolean isEmpty() { - return modifiedMap.map(Map::isEmpty).orElseGet(immutableMap::isEmpty); - } - - @Override - public boolean containsKey(Object o) { - return modifiedMap.map(map -> map.containsKey(o)).orElseGet(() -> immutableMap.containsKey(o)); - } - - @Override - public boolean containsValue(Object o) { - return modifiedMap.map(map -> map.containsValue(o)).orElseGet(() -> immutableMap.containsValue(o)); - } - - @Override - public Value_ get(Object o) { - return modifiedMap.map(map -> map.get(o)).orElseGet(() -> immutableMap.get(o)); - } - - @Override - public Set keySet() { - return modifiedMap.map(Map::keySet).orElseGet(immutableMap::keySet); - } - - @Override - public Collection values() { - return modifiedMap.map(Map::values).orElseGet(immutableMap::values); - } - - @Override - public Set> entrySet() { - return modifiedMap.map(Map::entrySet).orElseGet(immutableMap::entrySet); - } - - // Write Operations - @Override - public Value_ put(Key_ key, Value_ value) { - if (modifiedMap.isEmpty()) { - modifiedMap = Optional.of(new HashMap<>(immutableMap)); - } - return modifiedMap.get().put(key, value); - } - - @Override - public Value_ remove(Object o) { - if (modifiedMap.isEmpty()) { - modifiedMap = Optional.of(new HashMap<>(immutableMap)); - } - return modifiedMap.get().remove(o); - } - - @Override - public void putAll(Map map) { - if (modifiedMap.isEmpty()) { - modifiedMap = Optional.of(new HashMap<>(immutableMap)); - } - modifiedMap.get().putAll(map); - } - - @Override - public void clear() { - if (modifiedMap.isEmpty()) { - modifiedMap = Optional.of(new HashMap<>(immutableMap)); - } - modifiedMap.get().clear(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/DefaultFormatSpec.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/DefaultFormatSpec.java deleted file mode 100644 index b216d34c..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/DefaultFormatSpec.java +++ /dev/null @@ -1,348 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.ValueError; - -public class DefaultFormatSpec { - final static String FILL = "(?.)?"; - final static String ALIGN = "(?:" + FILL + "(?[<>=^]))?"; - final static String SIGN = "(?[+\\- ])?"; - final static String ALTERNATE_FORM = "(?#)?"; - final static String SIGN_AWARE_ZERO_FILL = "(?0)?"; - final static String WIDTH = "(?\\d+)?"; - final static String GROUPING_OPTION = "(?[_,])?"; - final static String PRECISION = "(?:\\.(?\\d+))?"; - final static String TYPE = "(?[bcdeEfFgGnosxX%])?"; - final static String DEFAULT_FORMAT_SPEC = ALIGN + - SIGN + - ALTERNATE_FORM + - SIGN_AWARE_ZERO_FILL + - WIDTH + - GROUPING_OPTION + - PRECISION + - TYPE; - final static Pattern DEFAULT_FORMAT_SPEC_PATTERN = Pattern.compile(DEFAULT_FORMAT_SPEC); - - /** - * The character to use for padding - */ - public final String fillCharacter; - - /** - * If true, modify the displayed output for some {@link ConversionType} - */ - public final boolean useAlternateForm; - - /** - * How padding should be applied to fill width - */ - public final Optional alignment; - - public final Optional signOption; - - /** - * The minimum space the output should satisfy - */ - public final Optional width; - - /** - * What to use for the thousands' seperator - */ - public final Optional groupingOption; - - /** - * How many significant digits for floating point numbers. For strings, the - * maximum space the output should satisfy. Not allowed for int types. - */ - public final Optional precision; - - /** - * How the value should be displayed - */ - public final Optional conversionType; - - private DefaultFormatSpec(String fillCharacter, boolean useAlternateForm, - Optional alignment, Optional signOption, - Optional width, Optional groupingOption, Optional precision, - Optional conversionType) { - this.fillCharacter = fillCharacter; - this.useAlternateForm = useAlternateForm; - this.alignment = alignment; - this.signOption = signOption; - this.width = width; - this.groupingOption = groupingOption; - this.precision = precision; - this.conversionType = conversionType; - } - - public static DefaultFormatSpec fromSpec(PythonString formatSpec) { - Matcher matcher = DEFAULT_FORMAT_SPEC_PATTERN.matcher(formatSpec.value); - - if (!matcher.matches()) { - throw new ValueError("Invalid format spec: " + formatSpec.value); - } - - Optional signAwareZeroFill = Optional.ofNullable(matcher.group("signAwareZeroFill")); - - return new DefaultFormatSpec( - Optional.ofNullable(matcher.group("fill")).or(() -> signAwareZeroFill).orElse(" "), - Optional.ofNullable(matcher.group("alternateForm")).isPresent(), - Optional.ofNullable(matcher.group("align")).map(AlignmentOption::fromString) - .or(() -> signAwareZeroFill.map(ignored -> AlignmentOption.RESPECT_SIGN_RIGHT_ALIGN)), - Optional.ofNullable(matcher.group("sign")).map(SignOption::fromString), - Optional.ofNullable(matcher.group("width")).map(Integer::parseInt), - Optional.ofNullable(matcher.group("groupingOption")).map(GroupingOption::fromString), - Optional.ofNullable(matcher.group("precision")).map(Integer::parseInt), - Optional.ofNullable(matcher.group("type")).map(ConversionType::fromString)); - } - - /** - * For use by {@link PythonString}, where since Python 3.10, 0 before width do not affect default alignment - * of strings. - */ - public static DefaultFormatSpec fromStringSpec(PythonString formatSpec) { - Matcher matcher = DEFAULT_FORMAT_SPEC_PATTERN.matcher(formatSpec.value); - - if (!matcher.matches()) { - throw new ValueError("Invalid format spec: " + formatSpec.value); - } - - Optional signAwareZeroFill = Optional.ofNullable(matcher.group("signAwareZeroFill")); - - return new DefaultFormatSpec( - Optional.ofNullable(matcher.group("fill")).or(() -> signAwareZeroFill).orElse(" "), - Optional.ofNullable(matcher.group("alternateForm")).isPresent(), - Optional.ofNullable(matcher.group("align")).map(AlignmentOption::fromString), - Optional.ofNullable(matcher.group("sign")).map(SignOption::fromString), - Optional.ofNullable(matcher.group("width")).map(Integer::parseInt), - Optional.ofNullable(matcher.group("groupingOption")).map(GroupingOption::fromString), - Optional.ofNullable(matcher.group("precision")).map(Integer::parseInt), - Optional.ofNullable(matcher.group("type")).map(ConversionType::fromString)); - } - - public int getPrecisionOrDefault() { - return precision.orElse(6); - } - - public enum AlignmentOption { - /** - * Forces the field to be left-aligned within the available space (this is the default for most objects). - */ - LEFT_ALIGN("<"), - /** - * Forces the field to be right-aligned within the available space (this is the default for numbers). - */ - RIGHT_ALIGN(">"), - /** - * Forces the padding to be placed after the sign (if any) but before the digits. - * This is used for printing fields in the form ‘+000000120’. - * This alignment option is only valid for numeric types. - * It becomes the default for numbers when ‘0’ immediately precedes the field width. - */ - RESPECT_SIGN_RIGHT_ALIGN("="), - - /** - * Forces the field to be centered within the available space. - */ - CENTER_ALIGN("^"); - - final String matchCharacter; - - AlignmentOption(String matchCharacter) { - this.matchCharacter = matchCharacter; - } - - public static AlignmentOption fromString(String text) { - for (AlignmentOption alignmentOption : AlignmentOption.values()) { - if (alignmentOption.matchCharacter.equals(text)) { - return alignmentOption; - } - } - throw new IllegalArgumentException("\"" + text + "\" does not match any alignment option"); - } - } - - public enum SignOption { - ALWAYS_SIGN("+"), - ONLY_NEGATIVE_NUMBERS("-"), - SPACE_FOR_POSITIVE_NUMBERS(" "); - - final String matchCharacter; - - SignOption(String matchCharacter) { - this.matchCharacter = matchCharacter; - } - - public static SignOption fromString(String text) { - for (SignOption signOption : SignOption.values()) { - if (signOption.matchCharacter.equals(text)) { - return signOption; - } - } - throw new IllegalArgumentException("\"" + text + "\" does not match any sign option"); - } - } - - public enum GroupingOption { - /** - * Signals the use of a comma for the thousands' separator. - */ - COMMA(","), - - /** - * Signals the use of an underscore for the thousands' separator. - */ - UNDERSCORE("_"); - - final String matchCharacter; - - GroupingOption(String matchCharacter) { - this.matchCharacter = matchCharacter; - } - - public static GroupingOption fromString(String text) { - for (GroupingOption groupingOption : GroupingOption.values()) { - if (groupingOption.matchCharacter.equals(text)) { - return groupingOption; - } - } - throw new IllegalArgumentException("\"" + text + "\" does not match any grouping option"); - } - } - - public enum ConversionType { - // String - /** - * (string) String format. This is the default type for strings and may be omitted. - */ - STRING("s"), - - // Integer - /** - * (int) Binary format. Outputs the number in base 2. - */ - BINARY("b"), - - /** - * (int) Character. Converts the integer to the corresponding unicode character before printing. - */ - CHARACTER("c"), - - /** - * (int) Decimal Integer. Outputs the number in base 10. - */ - DECIMAL("d"), - - /** - * (int) Octal format. Outputs the number in base 8. - */ - OCTAL("o"), - - /** - * (int) Hex format. Outputs the number in base 16, using lower-case letters for the digits above 9. - */ - LOWERCASE_HEX("x"), - - /** - * (int) Hex format. Outputs the number in base 16, using upper-case letters for the digits above 9. - * In case '#' is specified, the prefix '0x' will be upper-cased to '0X' as well. - */ - UPPERCASE_HEX("X"), - - // Float - /** - * (float) Scientific notation. For a given precision p, - * formats the number in scientific notation with the letter ‘e’ separating the coefficient from the exponent. - * The coefficient has one digit before and p digits after the decimal point, for a total of p + 1 significant digits. - * With no precision given, uses a precision of 6 digits after the decimal point for float, - * and shows all coefficient digits for Decimal. If no digits follow the decimal point, - * the decimal point is also removed unless the # option is used. - */ - LOWERCASE_SCIENTIFIC_NOTATION("e"), - - /** - * (float) Same as {@link #LOWERCASE_SCIENTIFIC_NOTATION} except it uses an upper case ‘E’ as the separator character. - */ - UPPERCASE_SCIENTIFIC_NOTATION("E"), - - /** - * (float) Fixed-point notation. For a given precision p, formats the number as a decimal number - * with exactly p digits following the decimal point. With no precision given, - * uses a precision of 6 digits after the decimal point for float, and uses a precision large enough to show - * all coefficient digits for Decimal. If no digits follow the decimal point, the decimal point is also removed - * unless the # option is used. - */ - LOWERCASE_FIXED_POINT("f"), - - /** - * (float) Same as {@link #LOWERCASE_FIXED_POINT} but converts "nan" to "NAN" and "inf" to "INF" - */ - UPPERCASE_FIXED_POINT("F"), - - /** - * (float) General format. For a given precision p >= 1, this rounds the number to p significant digits and then - * formats the result in either fixed-point format or in scientific notation, depending on its magnitude. - * A precision of 0 is treated as equivalent to a precision of 1. - * - * The precise rules are as follows: - * suppose that the result formatted with presentation type {@link #LOWERCASE_SCIENTIFIC_NOTATION} - * and precision p-1 would have exponent exp. - * Then, if m <= exp < p, where m is -4 for floats and -6 for Decimals, - * the number is formatted with presentation type 'f'and precision p-1-exp. - * Otherwise, the number is formatted with presentation type 'e' and precision p-1. - * In both cases insignificant trailing zeros are removed from the significand, - * and the decimal point is also removed if there are no remaining digits following it, - * unless the '#' option is used. - * - * With no precision given, uses a precision of 6 significant digits for float. - * For Decimal, the coefficient of the result is formed from the coefficient digits of the value; - * scientific notation is used for values smaller than 1e-6 in absolute value and values where - * the place value of the least significant digit is larger than 1, and fixed-point notation is used otherwise. - * - * Positive and negative infinity, positive and negative zero, and nans, are formatted as - * inf, -inf, 0, -0 and nan respectively, regardless of the precision. - */ - LOWERCASE_GENERAL("g"), - - /** - * (float) Same as {@link #LOWERCASE_GENERAL}, except switches to {#UPPERCASE_SCIENTIFIC_NOTATION} if the number gets - * too large. - * The representations of infinity and NaN are uppercased, too. - */ - UPPERCASE_GENERAL("G"), - - /** - * (int, float) Locale sensitive number output. For integers, same as {@link #DECIMAL} but it will use the - * current locale's number seperator. For floats, same as {@link #LOWERCASE_GENERAL}, - * except that when fixed-point notation is used to format the result, - * it always includes at least one digit past the decimal point. - * The precision used is as large as needed to represent the given value faithfully. - */ - LOCALE_SENSITIVE("n"), - - /** - * (float) Percentage. Multiplies the number by 100 and displays in {@link #LOWERCASE_FIXED_POINT} format, - * followed by a percent sign. - */ - PERCENTAGE("%"); - - final String matchCharacter; - - ConversionType(String matchCharacter) { - this.matchCharacter = matchCharacter; - } - - public static ConversionType fromString(String text) { - for (ConversionType conversionType : ConversionType.values()) { - if (conversionType.matchCharacter.equals(text)) { - return conversionType; - } - } - throw new IllegalArgumentException("\"" + text + "\" does not match any conversion type"); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/HandlerSorterAdapter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/HandlerSorterAdapter.java deleted file mode 100644 index 70fbb2ef..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/HandlerSorterAdapter.java +++ /dev/null @@ -1,70 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Collections; -import java.util.Comparator; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TryCatchBlockNode; - -// Taken from https://gist.github.com/sampsyo/281277 -/** - * Sorts the exception handlers in a method innermost-to-outermost. This - * allows the programmer to add handlers without worrying about ordering them - * correctly with respect to existing, in-code handlers. - * - * Behavior is only defined for properly-nested handlers. If any "try" blocks - * overlap (something that isn't possible in Java code) then this may not - * do what you want. - */ -public class HandlerSorterAdapter extends MethodNode { - - private final MethodVisitor mv; - - public HandlerSorterAdapter( - final MethodVisitor mv, - final int api, - final int access, - final String name, - final String desc, - final String signature, - final String[] exceptions) { - super(api, access, name, desc, signature, exceptions); - this.mv = mv; - } - - public void visitEnd() { - TryCatchBlockLengthComparator comp = - new TryCatchBlockLengthComparator(this); - Collections.sort(tryCatchBlocks, comp); - - if (mv != null) { - accept(mv); - } - } - - /** - * Compares TryCatchBlockNodes by the length of their "try" block. - */ - static class TryCatchBlockLengthComparator implements Comparator { - - private final MethodNode meth; - - public TryCatchBlockLengthComparator(MethodNode meth) { - this.meth = meth; - } - - public int compare(Object o1, Object o2) { - int len1 = blockLength((TryCatchBlockNode) o1); - int len2 = blockLength((TryCatchBlockNode) o2); - return len1 - len2; - } - - private int blockLength(TryCatchBlockNode block) { - int startidx = meth.instructions.indexOf(block.start); - int endidx = meth.instructions.indexOf(block.end); - return endidx - startidx; - } - } - -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/IteratorUtils.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/IteratorUtils.java deleted file mode 100644 index 32389e82..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/IteratorUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Iterator; -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public class IteratorUtils { - - public static Iterator iteratorMap(final Iterator source, - final Function mapFunction) { - return new Iterator() { - @Override - public boolean hasNext() { - return source.hasNext(); - } - - @Override - public PythonLikeObject next() { - return mapFunction.apply(source.next()); - } - }; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaIdentifierUtils.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaIdentifierUtils.java deleted file mode 100644 index 0c22a405..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaIdentifierUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Set; - -public class JavaIdentifierUtils { - private static final Set JAVA_KEYWORD_SET = Set.of( - "abstract", - "continue", - "for", - "new", - "switch", - "assert", - "default", - "goto", - "package", - "synchronized", - "boolean", - "do", - "if", - "private", - "this", - "break", - "double", - "implements", - "protected", - "throw", - "byte", - "else", - "import", - "public", - "throws", - "case", - "enum", - "instanceof", - "return", - "transient", - "catch", - "extends", - "int", - "short", - "try", - "char", - "final", - "interface", - "static", - "void", - "class", - "finally", - "long", - "strictfp", - "volatile", - "const", - "float", - "native", - "super", - "while"); - - private JavaIdentifierUtils() { - } - - public static String sanitizeClassName(String pythonClassName) { - StringBuilder builder = new StringBuilder(); - pythonClassName.chars().forEachOrdered(character -> { - if (character != '.' && !Character.isJavaIdentifierPart(character)) { - String replacement = "$_" + character + "_$"; - builder.append(replacement); - } else { - builder.appendCodePoint(character); - } - }); - String out = builder.toString(); - if (JAVA_KEYWORD_SET.contains(out)) { - return "$" + out; - } else { - return out; - } - } - - public static String sanitizeFieldName(String pythonFieldName) { - StringBuilder builder = new StringBuilder(); - pythonFieldName.chars().forEachOrdered(character -> { - if (!Character.isJavaIdentifierPart(character)) { - String replacement = "$_" + character + "_$"; - builder.append(replacement); - } else { - builder.appendCodePoint(character); - } - }); - String out = builder.toString(); - if (JAVA_KEYWORD_SET.contains(out)) { - return "$" + out; - } else if (pythonFieldName.isEmpty()) { - return "$$"; - } else if (Character.isDigit(pythonFieldName.charAt(0))) { - return "$" + out; - } else { - return out; - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaPythonClassWriter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaPythonClassWriter.java deleted file mode 100644 index 5a6bc362..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaPythonClassWriter.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Objects; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; - -/** - * A ClassWriter with a custom getCommonSuperClass, preventing - * TypeNotPresent errors when computing frames. - */ -public class JavaPythonClassWriter extends ClassWriter { - - public JavaPythonClassWriter(int flags) { - super(flags); - } - - @Override - protected String getCommonSuperClass(String type1, String type2) { - if (Objects.equals(type1, type2)) { - return type1; - } - - try { - return super.getCommonSuperClass(type1, type2); - } catch (TypeNotPresentException e) { - return Type.getInternalName(Object.class); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaStringMapMirror.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaStringMapMirror.java deleted file mode 100644 index 7e1a04c5..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JavaStringMapMirror.java +++ /dev/null @@ -1,158 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.Collection; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.stream.Collectors; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonString; - -import org.apache.commons.collections4.OrderedMap; -import org.apache.commons.collections4.OrderedMapIterator; - -public class JavaStringMapMirror implements OrderedMap { - final Map delegate; - - public JavaStringMapMirror(Map delegate) { - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean containsKey(Object o) { - return o instanceof PythonString && delegate.containsKey(((PythonString) o).value); - } - - @Override - public boolean containsValue(Object o) { - return delegate.containsValue(o); - } - - @Override - public PythonLikeObject get(Object o) { - if (o instanceof PythonString) { - return delegate.get(((PythonString) o).value); - } - return null; - } - - @Override - public PythonLikeObject put(PythonLikeObject key, PythonLikeObject value) { - if (key instanceof PythonString) { - return delegate.put(((PythonString) key).value, value); - } else { - throw new IllegalArgumentException(); - } - } - - @Override - public PythonLikeObject remove(Object o) { - if (o instanceof PythonString) { - return delegate.remove(((PythonString) o).value); - } - return delegate.remove(o); - } - - @Override - public void putAll(Map map) { - map.forEach(this::put); - } - - @Override - public void clear() { - delegate.clear(); - } - - @Override - public Set keySet() { - return delegate.keySet().stream().map(PythonString::valueOf).collect(Collectors.toSet()); - } - - @Override - public Collection values() { - return delegate.values(); - } - - @Override - public Set> entrySet() { - return delegate.entrySet().stream().map(entry -> new Entry() { - @Override - public PythonLikeObject getKey() { - return PythonString.valueOf(entry.getKey()); - } - - @Override - public PythonLikeObject getValue() { - return entry.getValue(); - } - - @Override - public PythonLikeObject setValue(PythonLikeObject o) { - return entry.setValue(o); - } - }).collect(Collectors.toSet()); - } - - @Override - public OrderedMapIterator mapIterator() { - throw new UnsupportedOperationException("mapIterator is not supported"); - } - - @Override - public PythonLikeObject firstKey() { - if (delegate.isEmpty()) { - throw new NoSuchElementException("Map is empty"); - } - return PythonString.valueOf(delegate.keySet().iterator().next()); - } - - @Override - public PythonLikeObject lastKey() { - if (delegate.isEmpty()) { - throw new NoSuchElementException("Map is empty"); - } - - String lastKey = null; - for (String key : delegate.keySet()) { - lastKey = key; - } - return PythonString.valueOf(lastKey); - } - - @Override - public PythonLikeObject nextKey(PythonLikeObject object) { - boolean returnNextKey = false; - for (String key : delegate.keySet()) { - if (key.equals(object.toString())) { - returnNextKey = true; - } - if (returnNextKey) { - return PythonString.valueOf(key); - } - } - return null; - } - - @Override - public PythonLikeObject previousKey(PythonLikeObject object) { - String previousKey = null; - for (String key : delegate.keySet()) { - if (key.equals(object.toString())) { - return PythonString.valueOf(previousKey); - } - previousKey = key; - } - return null; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java deleted file mode 100644 index 554157bc..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; - -public final class JumpUtils { - - private JumpUtils() { - } - - public static int getInstructionIndexForByteOffset(int byteOffset, PythonVersion 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_12)) { - return instruction.arg(); - } else { - return parseArgRepr(instruction); - } - } - - public static int getRelativeTarget(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - 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_12)) { - return instruction.offset() - instruction.arg() + 1; - } else { - return parseArgRepr(instruction); - } - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/MethodVisitorAdapters.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/MethodVisitorAdapters.java deleted file mode 100644 index ebed849a..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/MethodVisitorAdapters.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.lang.reflect.Modifier; -import java.util.HashMap; - -import ai.timefold.jpyinterpreter.MethodDescriptor; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.util.CheckMethodAdapter; - -public class MethodVisitorAdapters { - public static MethodVisitor adapt(MethodVisitor methodVisitor, MethodDescriptor method) { - return adapt(methodVisitor, method.getMethodName(), method.getMethodDescriptor()); - } - - public static MethodVisitor adapt(MethodVisitor methodVisitor, String name, String descriptor) { - CheckMethodAdapter out = new CheckMethodAdapter(Modifier.PUBLIC, name, descriptor, - new HandlerSorterAdapter(methodVisitor, - Opcodes.ASM9, - Modifier.PUBLIC, - name, - descriptor, - null, null), - new HashMap<>()); - out.version = Opcodes.V11; - return out; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/PythonGlobalsBackedMap.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/PythonGlobalsBackedMap.java deleted file mode 100644 index 3c78dc90..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/PythonGlobalsBackedMap.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.HashMap; - -import ai.timefold.jpyinterpreter.PythonLikeObject; - -public class PythonGlobalsBackedMap extends HashMap { - private final long pythonGlobalsId; - - public PythonGlobalsBackedMap(long pythonGlobalsId) { - this.pythonGlobalsId = pythonGlobalsId; - } - - public long getPythonGlobalsId() { - return pythonGlobalsId; - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/StringFormatter.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/StringFormatter.java deleted file mode 100644 index d07da993..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/StringFormatter.java +++ /dev/null @@ -1,733 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.builtins.BinaryDunderBuiltin; -import ai.timefold.jpyinterpreter.builtins.GlobalBuiltins; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.PythonByteArray; -import ai.timefold.jpyinterpreter.types.PythonBytes; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.errors.lookup.KeyError; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class StringFormatter { - final static String IDENTIFIER = "(?:(?:\\p{javaUnicodeIdentifierStart}|_)\\p{javaUnicodeIdentifierPart}*)"; - final static String ARG_NAME = "(?" + IDENTIFIER + "|\\d+)?"; - final static String ATTRIBUTE_NAME = IDENTIFIER; - final static String ELEMENT_INDEX = "[^]]+"; - final static String ITEM_NAME = "(?:(?:\\." + ATTRIBUTE_NAME + ")|(?:\\[" + ELEMENT_INDEX + "\\]))"; - final static String FIELD_NAME = "(?" + ARG_NAME + "(" + ITEM_NAME + ")*)?"; - final static String CONVERSION = "(?:!(?[rsa]))?"; - final static String FORMAT_SPEC = "(?::(?[^{}]*))?"; - - final static Pattern REPLACEMENT_FIELD_PATTERN = Pattern.compile("\\{" + - FIELD_NAME + - CONVERSION + - FORMAT_SPEC + - "}|(?\\{\\{|}})"); - - final static Pattern INDEX_CHAIN_PART_PATTERN = Pattern.compile(ITEM_NAME); - - /** - * Pattern that matches conversion specifiers for the "%" operator. See - * - * Python printf-style String Formatting documentation for details. - */ - private final static Pattern PRINTF_FORMAT_REGEX = Pattern.compile("%(?:(?\\([^()]+\\))?" + - "(?[#0\\-+ ]*)?" + - "(?\\*|\\d+)?" + - "(?\\.(?:\\*|\\d+))?" + - "[hlL]?" + // ignored length modifier - "(?[diouxXeEfFgGcrsa%])|.*)"); - - private enum PrintfConversionType { - SIGNED_INTEGER_DECIMAL("d", "i", "u"), - SIGNED_INTEGER_OCTAL("o"), - SIGNED_HEXADECIMAL_LOWERCASE("x"), - SIGNED_HEXADECIMAL_UPPERCASE("X"), - FLOATING_POINT_EXPONENTIAL_LOWERCASE("e"), - FLOATING_POINT_EXPONENTIAL_UPPERCASE("E"), - FLOATING_POINT_DECIMAL("f", "F"), - FLOATING_POINT_DECIMAL_OR_EXPONENTIAL_LOWERCASE("g"), - FLOATING_POINT_DECIMAL_OR_EXPONENTIAL_UPPERCASE("G"), - SINGLE_CHARACTER("c"), - REPR_STRING("r"), - STR_STRING("s"), - ASCII_STRING("a"), - LITERAL_PERCENT("%"); - - final String[] matchedCharacters; - - PrintfConversionType(String... matchedCharacters) { - this.matchedCharacters = matchedCharacters; - } - - public static PrintfConversionType getConversionType(Matcher matcher) { - String conversion = matcher.group("type"); - - if (conversion == null) { - throw new ValueError("Invalid specifier at position " + matcher.start() + " in string "); - } - - for (PrintfConversionType conversionType : PrintfConversionType.values()) { - for (String matchedCharacter : conversionType.matchedCharacters) { - if (matchedCharacter.equals(conversion)) { - return conversionType; - } - } - } - throw new IllegalStateException("Conversion (" + conversion + ") does not match any defined conversions"); - } - } - - public enum PrintfStringType { - STRING, - BYTES - } - - public static String printfInterpolate(CharSequence value, List tuple, PrintfStringType stringType) { - Matcher matcher = PRINTF_FORMAT_REGEX.matcher(value); - - StringBuilder out = new StringBuilder(); - int start = 0; - int currentElement = 0; - - while (matcher.find()) { - out.append(value, start, matcher.start()); - start = matcher.end(); - - String key = matcher.group("key"); - if (key != null) { - throw new TypeError("format requires a mapping"); - } - - String flags = matcher.group("flags"); - String minWidth = matcher.group("minWidth"); - String precisionString = matcher.group("precision"); - - PrintfConversionType conversionType = PrintfConversionType.getConversionType(matcher); - - if (conversionType != PrintfConversionType.LITERAL_PERCENT) { - if (tuple.size() <= currentElement) { - throw new TypeError("not enough arguments for format string"); - } - - PythonLikeObject toConvert = tuple.get(currentElement); - - currentElement++; - - if ("*".equals(minWidth)) { - if (tuple.size() <= currentElement) { - throw new TypeError("not enough arguments for format string"); - } - minWidth = ((PythonString) UnaryDunderBuiltin.STR.invoke(tuple.get(currentElement))).value; - currentElement++; - } - - if ("*".equals(precisionString)) { - if (tuple.size() <= currentElement) { - throw new TypeError("not enough arguments for format string"); - } - precisionString = ((PythonString) UnaryDunderBuiltin.STR.invoke(tuple.get(currentElement))).value; - currentElement++; - } - - Optional maybePrecision, maybeWidth; - if (precisionString != null) { - maybePrecision = Optional.of(Integer.parseInt(precisionString.substring(1))); - } else { - maybePrecision = Optional.empty(); - } - - if (minWidth != null) { - maybeWidth = Optional.of(Integer.parseInt(minWidth)); - } else { - maybeWidth = Optional.empty(); - } - out.append(performInterpolateConversion(flags, maybeWidth, maybePrecision, conversionType, toConvert, - stringType)); - } else { - out.append("%"); - } - } - - out.append(value.subSequence(start, value.length())); - - return out.toString(); - } - - public static String printfInterpolate(CharSequence value, PythonLikeDict dict, PrintfStringType stringType) { - Matcher matcher = PRINTF_FORMAT_REGEX.matcher(value); - - StringBuilder out = new StringBuilder(); - int start = 0; - while (matcher.find()) { - out.append(value, start, matcher.start()); - start = matcher.end(); - - PrintfConversionType conversionType = PrintfConversionType.getConversionType(matcher); - - if (conversionType != PrintfConversionType.LITERAL_PERCENT) { - String key = matcher.group("key"); - if (key == null) { - throw new ValueError( - "When a dict is used for the interpolation operator, all conversions must have parenthesised keys"); - } - key = key.substring(1, key.length() - 1); - - String flags = matcher.group("flags"); - String minWidth = matcher.group("minWidth"); - String precisionString = matcher.group("precision"); - - if ("*".equals(minWidth)) { - throw new ValueError( - "* cannot be used for minimum field width when a dict is used for the interpolation operator"); - } - - if ("*".equals(precisionString)) { - throw new ValueError("* cannot be used for precision when a dict is used for the interpolation operator"); - } - - PythonLikeObject toConvert; - if (stringType == PrintfStringType.STRING) { - toConvert = dict.getItemOrError(PythonString.valueOf(key)); - } else { - toConvert = dict.getItemOrError(PythonString.valueOf(key).asAsciiBytes()); - } - - Optional maybePrecision, maybeWidth; - if (precisionString != null) { - maybePrecision = Optional.of(Integer.parseInt(precisionString.substring(1))); - } else { - maybePrecision = Optional.empty(); - } - - if (minWidth != null) { - maybeWidth = Optional.of(Integer.parseInt(minWidth)); - } else { - maybeWidth = Optional.empty(); - } - - out.append(performInterpolateConversion(flags, maybeWidth, maybePrecision, conversionType, toConvert, - stringType)); - } else { - out.append("%"); - } - } - - out.append(value.subSequence(start, value.length())); - return out.toString(); - } - - private static BigDecimal getBigDecimalWithPrecision(BigDecimal number, Optional precision) { - int currentScale = number.scale(); - int currentPrecision = number.precision(); - int precisionDelta = precision.orElse(6) - currentPrecision; - return number.setScale(currentScale + precisionDelta, RoundingMode.HALF_EVEN); - } - - private static String getUppercaseEngineeringString(BigDecimal number, Optional precision) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PrintStream printStream = new PrintStream(out); - printStream.printf("%1." + (precision.orElse(6) - 1) + "E", number); - return out.toString(); - } - - private static String performInterpolateConversion(String flags, Optional maybeWidth, - Optional maybePrecision, - PrintfConversionType conversionType, - PythonLikeObject toConvert, - PrintfStringType stringType) { - boolean useAlternateForm = flags.contains("#"); - boolean isZeroPadded = flags.contains("0"); - boolean isLeftAdjusted = flags.contains("-"); - if (isLeftAdjusted) { - isZeroPadded = false; - } - - boolean putSpaceBeforePositiveNumber = flags.contains(" "); - boolean putSignBeforeConversion = flags.contains("+"); - if (putSignBeforeConversion) { - putSpaceBeforePositiveNumber = false; - } - - String result; - switch (conversionType) { - case SIGNED_INTEGER_DECIMAL: { - if (toConvert instanceof PythonFloat) { - toConvert = ((PythonFloat) toConvert).asInteger(); - } - if (!(toConvert instanceof PythonInteger)) { - throw new TypeError("%d format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - result = ((PythonInteger) toConvert).value.toString(10); - break; - } - case SIGNED_INTEGER_OCTAL: { - if (toConvert instanceof PythonFloat) { - toConvert = ((PythonFloat) toConvert).asInteger(); - } - if (!(toConvert instanceof PythonInteger)) { - throw new TypeError("%o format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - result = ((PythonInteger) toConvert).value.toString(8); - if (useAlternateForm) { - result = (result.startsWith("-")) ? "-0o" + result.substring(1) : "0o" + result; - } - break; - } - case SIGNED_HEXADECIMAL_LOWERCASE: { - if (toConvert instanceof PythonFloat) { - toConvert = ((PythonFloat) toConvert).asInteger(); - } - if (!(toConvert instanceof PythonInteger)) { - throw new TypeError("%x format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - result = ((PythonInteger) toConvert).value.toString(16); - if (useAlternateForm) { - result = (result.startsWith("-")) ? "-0x" + result.substring(1) : "0x" + result; - } - break; - } - case SIGNED_HEXADECIMAL_UPPERCASE: { - if (toConvert instanceof PythonFloat) { - toConvert = ((PythonFloat) toConvert).asInteger(); - } - if (!(toConvert instanceof PythonInteger)) { - throw new TypeError("%X format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - result = ((PythonInteger) toConvert).value.toString(16).toUpperCase(); - if (useAlternateForm) { - result = (result.startsWith("-")) ? "-0X" + result.substring(1) : "0X" + result; - } - break; - } - case FLOATING_POINT_EXPONENTIAL_LOWERCASE: { - if (toConvert instanceof PythonInteger) { - toConvert = ((PythonInteger) toConvert).asFloat(); - } - if (!(toConvert instanceof PythonFloat)) { - throw new TypeError("%e format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - BigDecimal value = BigDecimal.valueOf(((PythonFloat) toConvert).value); - result = getUppercaseEngineeringString(value, maybePrecision.map(precision -> precision + 1) - .or(() -> Optional.of(7))).toLowerCase(); - if (useAlternateForm && !result.contains(".")) { - result = result + ".0"; - } - break; - } - case FLOATING_POINT_EXPONENTIAL_UPPERCASE: { - if (toConvert instanceof PythonInteger) { - toConvert = ((PythonInteger) toConvert).asFloat(); - } - if (!(toConvert instanceof PythonFloat)) { - throw new TypeError("%E format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - BigDecimal value = BigDecimal.valueOf(((PythonFloat) toConvert).value); - result = getUppercaseEngineeringString(value, maybePrecision.map(precision -> precision + 1) - .or(() -> Optional.of(7))); - if (useAlternateForm && !result.contains(".")) { - result = result + ".0"; - } - break; - } - case FLOATING_POINT_DECIMAL: { - if (toConvert instanceof PythonInteger) { - toConvert = ((PythonInteger) toConvert).asFloat(); - } - if (!(toConvert instanceof PythonFloat)) { - throw new TypeError("%f format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - BigDecimal value = BigDecimal.valueOf(((PythonFloat) toConvert).value); - BigDecimal valueWithPrecision = value.setScale(maybePrecision.orElse(6), RoundingMode.HALF_EVEN); - result = valueWithPrecision.toPlainString(); - if (useAlternateForm && !result.contains(".")) { - result = result + ".0"; - } - break; - } - case FLOATING_POINT_DECIMAL_OR_EXPONENTIAL_LOWERCASE: { - if (toConvert instanceof PythonInteger) { - toConvert = ((PythonInteger) toConvert).asFloat(); - } - if (!(toConvert instanceof PythonFloat)) { - throw new TypeError("%g format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - BigDecimal value = BigDecimal.valueOf(((PythonFloat) toConvert).value); - BigDecimal valueWithPrecision; - - if (value.scale() > 4 || value.precision() >= maybePrecision.orElse(6)) { - valueWithPrecision = getBigDecimalWithPrecision(value, maybePrecision); - result = getUppercaseEngineeringString(valueWithPrecision, maybePrecision).toLowerCase(); - } else { - valueWithPrecision = value.setScale(maybePrecision.orElse(6), RoundingMode.HALF_EVEN); - result = valueWithPrecision.toPlainString(); - } - - if (result.length() >= 3 && result.charAt(result.length() - 3) == 'e') { - result = result.substring(0, result.length() - 1) + "0" + result.charAt(result.length() - 1); - } - break; - } - case FLOATING_POINT_DECIMAL_OR_EXPONENTIAL_UPPERCASE: { - if (toConvert instanceof PythonInteger) { - toConvert = ((PythonInteger) toConvert).asFloat(); - } - if (!(toConvert instanceof PythonFloat)) { - throw new TypeError("%G format: a real number is required, not " + toConvert.$getType().getTypeName()); - } - BigDecimal value = BigDecimal.valueOf(((PythonFloat) toConvert).value); - BigDecimal valueWithPrecision; - - if (value.scale() > 4 || value.precision() >= maybePrecision.orElse(6)) { - valueWithPrecision = getBigDecimalWithPrecision(value, maybePrecision); - result = getUppercaseEngineeringString(valueWithPrecision, maybePrecision); - } else { - valueWithPrecision = value.setScale(maybePrecision.orElse(6), RoundingMode.HALF_EVEN); - result = valueWithPrecision.toPlainString(); - } - break; - } - case SINGLE_CHARACTER: { - if (stringType == PrintfStringType.STRING) { - if (toConvert instanceof PythonString) { - PythonString convertedCharacter = (PythonString) toConvert; - if (convertedCharacter.value.length() != 1) { - throw new ValueError("c specifier can only take an integer or single character string"); - } - result = convertedCharacter.value; - } else { - result = Character.toString(((PythonInteger) toConvert).value.intValueExact()); - } - } else { - if (toConvert instanceof PythonBytes) { - PythonBytes convertedCharacter = (PythonBytes) toConvert; - if (convertedCharacter.value.length != 1) { - throw new ValueError("c specifier can only take an integer or single character string"); - } - result = convertedCharacter.asCharSequence().toString(); - } else if (toConvert instanceof PythonByteArray) { - PythonByteArray convertedCharacter = (PythonByteArray) toConvert; - if (convertedCharacter.valueBuffer.limit() != 1) { - throw new ValueError("c specifier can only take an integer or single character string"); - } - result = convertedCharacter.asCharSequence().toString(); - } else { - result = Character.toString(((PythonInteger) toConvert).value.intValueExact()); - } - } - break; - } - case REPR_STRING: { - result = ((PythonString) UnaryDunderBuiltin.REPRESENTATION.invoke(toConvert)).value; - break; - } - case STR_STRING: { - if (stringType == PrintfStringType.STRING) { - result = ((PythonString) UnaryDunderBuiltin.STR.invoke(toConvert)).value; - } else { - if (toConvert instanceof PythonBytes) { - result = ((PythonBytes) toConvert).asCharSequence().toString(); - } else if (toConvert instanceof PythonByteArray) { - result = ((PythonByteArray) toConvert).asCharSequence().toString(); - } else { - result = ((PythonString) UnaryDunderBuiltin.STR.invoke(toConvert)).value; - } - } - break; - } - case ASCII_STRING: { - result = GlobalBuiltins.ascii(List.of(toConvert), Map.of(), null).value; - break; - } - case LITERAL_PERCENT: { - result = "%"; - break; - } - default: - throw new IllegalStateException("Unhandled case: " + conversionType); - } - - if (putSignBeforeConversion && !(result.startsWith("+") || result.startsWith("-"))) { - result = "+" + result; - } - - if (putSpaceBeforePositiveNumber && !(result.startsWith("-"))) { - result = " " + result; - } - - if (maybeWidth.isPresent() && maybeWidth.get() > result.length()) { - int padding = maybeWidth.get() - result.length(); - if (isZeroPadded) { - if (result.startsWith("+") || result.startsWith("-")) { - result = result.charAt(0) + "0".repeat(padding) + result.substring(1); - } else { - result = "0".repeat(padding) + result; - } - } else if (isLeftAdjusted) { - result = result + " ".repeat(padding); - } - } - - return result; - } - - public static String format(String text, List positionalArguments, - Map namedArguments) { - Matcher matcher = REPLACEMENT_FIELD_PATTERN.matcher(text); - StringBuilder out = new StringBuilder(); - int start = 0; - int implicitField = 0; - - while (matcher.find()) { - out.append(text, start, matcher.start()); - start = matcher.end(); - - String literal = matcher.group("literal"); - if (literal != null) { - switch (literal) { - case "{{": - out.append("{"); - continue; - case "}}": - out.append("}"); - continue; - default: - throw new IllegalStateException("Unhandled literal: " + literal); - } - } - - String argName = matcher.group("argName"); - - PythonLikeObject toConvert; - - if (positionalArguments != null) { - if (argName == null) { - if (implicitField >= positionalArguments.size()) { - throw new ValueError( - "(" + implicitField + ") is larger than sequence length (" + positionalArguments.size() + ")"); - } - toConvert = positionalArguments.get(implicitField); - implicitField++; - } else { - try { - int argumentIndex = Integer.parseInt(argName); - if (argumentIndex >= positionalArguments.size()) { - throw new ValueError("(" + implicitField + ") is larger than sequence length (" - + positionalArguments.size() + ")"); - } - toConvert = positionalArguments.get(argumentIndex); - } catch (NumberFormatException e) { - if (namedArguments == null) { - throw new ValueError("(" + argName + ") cannot be used to index a sequence"); - } else { - toConvert = namedArguments.get(PythonString.valueOf(argName)); - } - } - } - } else { - toConvert = namedArguments.get(PythonString.valueOf(argName)); - } - - if (toConvert == null) { - throw new KeyError(argName); - } - - toConvert = getFinalObjectInChain(toConvert, matcher.group("fieldName")); - - String conversion = matcher.group("conversion"); - if (conversion != null) { - switch (conversion) { - case "s": - toConvert = UnaryDunderBuiltin.STR.invoke(toConvert); - break; - case "r": - toConvert = UnaryDunderBuiltin.REPRESENTATION.invoke(toConvert); - break; - case "a": - toConvert = GlobalBuiltins.ascii(List.of(toConvert), Map.of(), null); - break; - } - } - - String formatSpec = Objects.requireNonNullElse(matcher.group("formatSpec"), ""); - out.append(BinaryDunderBuiltin.FORMAT.invoke(toConvert, PythonString.valueOf(formatSpec))); - } - out.append(text.substring(start)); - return out.toString(); - } - - private static PythonLikeObject getFinalObjectInChain(PythonLikeObject chainStart, String chain) { - if (chain == null) { - return chainStart; - } - - PythonLikeObject current = chainStart; - Matcher matcher = INDEX_CHAIN_PART_PATTERN.matcher(chain); - - while (matcher.find()) { - String result = matcher.group(); - if (result.startsWith(".")) { - String attributeName = result.substring(1); - current = BinaryDunderBuiltin.GET_ATTRIBUTE.invoke(current, PythonString.valueOf(attributeName)); - } else { - String index = result.substring(1, result.length() - 1); - try { - int intIndex = Integer.parseInt(index); - current = BinaryDunderBuiltin.GET_ITEM.invoke(current, PythonInteger.valueOf(intIndex)); - } catch (NumberFormatException e) { - current = BinaryDunderBuiltin.GET_ITEM.invoke(current, PythonString.valueOf(index)); - } - } - } - return current; - } - - public static void addGroupings(StringBuilder out, DefaultFormatSpec formatSpec, int groupSize) { - if (formatSpec.groupingOption.isEmpty()) { - return; - } - - if (groupSize <= 0) { - throw new ValueError( - "Invalid format spec: grouping option now allowed for conversion type " + formatSpec.conversionType); - } - - int decimalSeperator = out.indexOf("."); - char seperator; - switch (formatSpec.groupingOption.get()) { - case COMMA: - seperator = ','; - break; - case UNDERSCORE: - seperator = '_'; - break; - default: - throw new IllegalStateException("Unhandled case: " + formatSpec.groupingOption.get()); - } - - int index; - if (decimalSeperator != -1) { - index = decimalSeperator - 1; - } else { - index = out.length() - 1; - } - - int groupIndex = 0; - while (index >= 0 && out.charAt(index) != '-') { - groupIndex++; - if (groupIndex == groupSize) { - out.insert(index, seperator); - groupIndex = 0; - } - index--; - } - } - - public static void align(StringBuilder out, DefaultFormatSpec formatSpec, - DefaultFormatSpec.AlignmentOption defaultAlignment) { - if (formatSpec.width.isPresent()) { - switch (formatSpec.alignment.orElse(defaultAlignment)) { - case LEFT_ALIGN: - leftAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - case RIGHT_ALIGN: - rightAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - case RESPECT_SIGN_RIGHT_ALIGN: - respectSignRightAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - case CENTER_ALIGN: - center(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - } - } - } - - public static void alignWithPrefixRespectingSign(StringBuilder out, String prefix, DefaultFormatSpec formatSpec, - DefaultFormatSpec.AlignmentOption defaultAlignment) { - int insertPosition = (out.charAt(0) == '+' || out.charAt(0) == '-' || out.charAt(0) == ' ') ? 1 : 0; - if (formatSpec.width.isPresent()) { - switch (formatSpec.alignment.orElse(defaultAlignment)) { - case LEFT_ALIGN: - out.insert(insertPosition, prefix); - leftAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - case RIGHT_ALIGN: - out.insert(insertPosition, prefix); - rightAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - case RESPECT_SIGN_RIGHT_ALIGN: - respectSignRightAlign(out, formatSpec.fillCharacter, formatSpec.width.get()); - out.insert(insertPosition, prefix); - break; - case CENTER_ALIGN: - out.insert(insertPosition, prefix); - center(out, formatSpec.fillCharacter, formatSpec.width.get()); - break; - } - } else { - out.insert(insertPosition, prefix); - } - } - - public static void leftAlign(StringBuilder builder, String fillCharAsString, int width) { - if (width <= builder.length()) { - return; - } - - int rightPadding = width - builder.length(); - builder.append(fillCharAsString.repeat(rightPadding)); - } - - public static void rightAlign(StringBuilder builder, String fillCharAsString, int width) { - if (width <= builder.length()) { - return; - } - - int leftPadding = width - builder.length(); - builder.insert(0, fillCharAsString.repeat(leftPadding)); - } - - public static void respectSignRightAlign(StringBuilder builder, String fillCharAsString, int width) { - if (width <= builder.length()) { - return; - } - - int leftPadding = width - builder.length(); - if (builder.length() >= 1 && (builder.charAt(0) == '+' || builder.charAt(0) == '-' || builder.charAt(0) == ' ')) { - builder.insert(1, fillCharAsString.repeat(leftPadding)); - } else { - builder.insert(0, fillCharAsString.repeat(leftPadding)); - } - } - - public static void center(StringBuilder builder, String fillCharAsString, int width) { - if (width <= builder.length()) { - return; - } - int extraWidth = width - builder.length(); - int rightPadding = extraWidth / 2; - // left padding get extra character if extraWidth is odd - int leftPadding = rightPadding + (extraWidth & 1); // x & 1 == x % 2 - - builder.insert(0, fillCharAsString.repeat(leftPadding)) - .append(fillCharAsString.repeat(rightPadding)); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/TracebackUtils.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/TracebackUtils.java deleted file mode 100644 index 0a58f6a2..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/TracebackUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; - -public class TracebackUtils { - private TracebackUtils() { - - } - - public static String getTraceback(Throwable t) { - ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); - PrintWriter printWriter = new PrintWriter(byteOutputStream); - t.printStackTrace(printWriter); - return byteOutputStream.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentKind.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentKind.java deleted file mode 100644 index abe24671..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentKind.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.timefold.jpyinterpreter.util.arguments; - -public enum ArgumentKind { - POSITIONAL_AND_KEYWORD(true, true), - POSITIONAL_ONLY(true, false), - KEYWORD_ONLY(false, true), - VARARGS(false, false); - - final boolean allowPositional; - final boolean allowKeyword; - - ArgumentKind(boolean allowPositional, boolean allowKeyword) { - this.allowPositional = allowPositional; - this.allowKeyword = allowKeyword; - } - - public boolean isAllowPositional() { - return allowPositional; - } - - public boolean isAllowKeyword() { - return allowKeyword; - } -} 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 deleted file mode 100644 index b9c604a9..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpec.java +++ /dev/null @@ -1,657 +0,0 @@ -package ai.timefold.jpyinterpreter.util.arguments; - -import java.lang.reflect.Method; -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; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.MethodDescriptor; -import ai.timefold.jpyinterpreter.PythonFunctionSignature; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public final class ArgumentSpec { - private static List> ARGUMENT_SPECS = new ArrayList<>(); - - private final String functionReturnTypeName; - private final String functionName; - private final List argumentNameList; - private final List argumentTypeNameList; - private final List argumentKindList; - private final List argumentDefaultList; - private final BitSet nullableArgumentSet; - private final Optional extraPositionalsArgumentIndex; - private final Optional extraKeywordsArgumentIndex; - - private final int numberOfPositionalArguments; - private final int requiredPositionalArguments; - - private Class functionReturnType = null; - private List argumentTypeList = null; - - private ArgumentSpec(String functionName, String functionReturnTypeName) { - this.functionReturnTypeName = functionReturnTypeName; - this.functionName = functionName + "()"; - requiredPositionalArguments = 0; - numberOfPositionalArguments = 0; - argumentNameList = Collections.emptyList(); - argumentTypeNameList = Collections.emptyList(); - argumentKindList = Collections.emptyList(); - argumentDefaultList = Collections.emptyList(); - extraPositionalsArgumentIndex = Optional.empty(); - extraKeywordsArgumentIndex = Optional.empty(); - nullableArgumentSet = new BitSet(); - } - - private ArgumentSpec(String argumentName, String argumentTypeName, ArgumentKind argumentKind, Object defaultValue, - Optional extraPositionalsArgumentIndex, Optional extraKeywordsArgumentIndex, - boolean allowNull, ArgumentSpec previousSpec) { - functionName = previousSpec.functionName; - functionReturnTypeName = previousSpec.functionReturnTypeName; - - if (previousSpec.numberOfPositionalArguments < previousSpec.getTotalArgumentCount()) { - numberOfPositionalArguments = previousSpec.numberOfPositionalArguments; - } else { - if (argumentKind.allowPositional) { - numberOfPositionalArguments = previousSpec.getTotalArgumentCount() + 1; - } else { - numberOfPositionalArguments = previousSpec.getTotalArgumentCount(); - } - } - - if (argumentKind == ArgumentKind.POSITIONAL_ONLY) { - if (previousSpec.requiredPositionalArguments != previousSpec.getTotalArgumentCount()) { - throw new IllegalArgumentException("All required positional arguments must come before all other arguments"); - } else { - requiredPositionalArguments = previousSpec.getTotalArgumentCount() + 1; - } - } else { - requiredPositionalArguments = previousSpec.requiredPositionalArguments; - } - - argumentNameList = new ArrayList<>(previousSpec.argumentNameList.size() + 1); - argumentTypeNameList = new ArrayList<>(previousSpec.argumentTypeNameList.size() + 1); - argumentKindList = new ArrayList<>(previousSpec.argumentKindList.size() + 1); - argumentDefaultList = new ArrayList<>(previousSpec.argumentDefaultList.size() + 1); - - argumentNameList.addAll(previousSpec.argumentNameList); - argumentNameList.add(argumentName); - - argumentTypeNameList.addAll(previousSpec.argumentTypeNameList); - argumentTypeNameList.add(argumentTypeName); - - argumentKindList.addAll(previousSpec.argumentKindList); - argumentKindList.add(argumentKind); - - argumentDefaultList.addAll(previousSpec.argumentDefaultList); - argumentDefaultList.add(defaultValue); - - if (extraPositionalsArgumentIndex.isPresent() && previousSpec.extraPositionalsArgumentIndex.isPresent()) { - throw new IllegalArgumentException("Multiple positional vararg arguments"); - } - if (previousSpec.extraPositionalsArgumentIndex.isPresent()) { - extraPositionalsArgumentIndex = previousSpec.extraPositionalsArgumentIndex; - } - - if (extraKeywordsArgumentIndex.isPresent() && previousSpec.extraKeywordsArgumentIndex.isPresent()) { - throw new IllegalArgumentException("Multiple keyword vararg arguments"); - } - if (previousSpec.extraKeywordsArgumentIndex.isPresent()) { - extraKeywordsArgumentIndex = previousSpec.extraKeywordsArgumentIndex; - } - - this.extraPositionalsArgumentIndex = extraPositionalsArgumentIndex; - this.extraKeywordsArgumentIndex = extraKeywordsArgumentIndex; - this.nullableArgumentSet = (BitSet) previousSpec.nullableArgumentSet.clone(); - if (allowNull) { - nullableArgumentSet.set(argumentNameList.size() - 1); - } - } - - public static ArgumentSpec forFunctionReturning(String functionName, - String outClass) { - return new ArgumentSpec<>(functionName, outClass); - } - - public int getTotalArgumentCount() { - return argumentNameList.size(); - } - - public int getAllowPositionalArgumentCount() { - return numberOfPositionalArguments; - } - - public boolean hasExtraPositionalArgumentsCapture() { - return extraPositionalsArgumentIndex.isPresent(); - } - - public boolean hasExtraKeywordArgumentsCapture() { - return extraKeywordsArgumentIndex.isPresent(); - } - - public String getArgumentTypeInternalName(int argumentIndex) { - return argumentTypeNameList.get(argumentIndex).replace('.', '/'); - } - - public ArgumentKind getArgumentKind(int argumentIndex) { - return argumentKindList.get(argumentIndex); - } - - /** - * Returns the index of an argument with the given name. Returns -1 if no argument has the given name. - * - * @param argumentName The name of the argument. - * @return the index of an argument with the given name, or -1 if no argument has that name - */ - public int getArgumentIndex(String argumentName) { - return argumentNameList.indexOf(argumentName); - } - - public boolean isArgumentNullable(int argumentIndex) { - return nullableArgumentSet.get(argumentIndex); - } - - public Optional getExtraPositionalsArgumentIndex() { - return extraPositionalsArgumentIndex; - } - - public Optional getExtraKeywordsArgumentIndex() { - return extraKeywordsArgumentIndex; - } - - public Collection getUnspecifiedArgumentSet(int positionalArguments, List keywordArgumentNameList) { - int specArgumentCount = getTotalArgumentCount(); - if (hasExtraPositionalArgumentsCapture()) { - specArgumentCount--; - } - if (hasExtraKeywordArgumentsCapture()) { - specArgumentCount--; - } - - return IntStream.range(positionalArguments, specArgumentCount) - .filter(index -> !keywordArgumentNameList.contains(argumentNameList.get(index))) - .boxed() - .collect(Collectors.toList()); - } - - public static ArgumentSpec getArgumentSpec(int argumentSpecIndex) { - return ARGUMENT_SPECS.get(argumentSpecIndex); - } - - public void loadArgumentSpec(MethodVisitor methodVisitor) { - int index = ARGUMENT_SPECS.size(); - ARGUMENT_SPECS.add(this); - methodVisitor.visitLdcInsn(index); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ArgumentSpec.class), - "getArgumentSpec", Type.getMethodDescriptor(Type.getType(ArgumentSpec.class), - Type.INT_TYPE), - false); - } - - private void computeArgumentTypeList() { - if (argumentTypeList == null) { - try { - functionReturnType = BuiltinTypes.asmClassLoader.loadClass(functionReturnTypeName.replace('/', '.')); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - argumentTypeList = argumentTypeNameList.stream() - .map(className -> { - try { - return (Class) BuiltinTypes.asmClassLoader.loadClass(className.replace('/', '.')); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - }) - .toList(); - } - } - - public List extractArgumentList(List positionalArguments, - Map keywordArguments) { - computeArgumentTypeList(); - - List out = new ArrayList<>(argumentNameList.size()); - - if (positionalArguments.size() > numberOfPositionalArguments && - extraPositionalsArgumentIndex.isEmpty()) { - throw new TypeError(functionName + " takes " + numberOfPositionalArguments + " positional arguments but " - + positionalArguments.size() + " were given"); - } - - if (positionalArguments.size() < requiredPositionalArguments) { - int missing = (requiredPositionalArguments - positionalArguments.size()); - String argumentString = (missing == 1) ? "argument" : "arguments"; - List missingArgumentNames = argumentNameList.subList(argumentNameList.size() - missing, - argumentNameList.size()); - throw new TypeError(functionName + " missing " + (requiredPositionalArguments - positionalArguments.size()) + - " required positional " + argumentString + ": '" + String.join("', ", missingArgumentNames) + "'"); - } - - int numberOfSetArguments = Math.min(numberOfPositionalArguments, positionalArguments.size()); - out.addAll(positionalArguments.subList(0, numberOfSetArguments)); - for (int i = numberOfSetArguments; i < argumentNameList.size(); i++) { - out.add(null); - } - - int remaining = argumentNameList.size() - numberOfSetArguments; - - PythonLikeDict extraKeywordArguments = null; - if (extraPositionalsArgumentIndex.isPresent()) { - remaining--; - out.set(extraPositionalsArgumentIndex.get(), - PythonLikeTuple - .fromList(positionalArguments.subList(numberOfSetArguments, positionalArguments.size()))); - } - - if (extraKeywordsArgumentIndex.isPresent()) { - remaining--; - extraKeywordArguments = new PythonLikeDict(); - out.set(extraKeywordsArgumentIndex.get(), - extraKeywordArguments); - } - - for (Map.Entry keywordArgument : keywordArguments.entrySet()) { - PythonString argumentName = keywordArgument.getKey(); - - int position = argumentNameList.indexOf(argumentName.value); - if (position == -1) { - if (extraKeywordsArgumentIndex.isPresent()) { - extraKeywordArguments.put(argumentName, keywordArgument.getValue()); - continue; - } else { - throw new TypeError(functionName + " got an unexpected keyword argument " + argumentName.repr().value); - } - } - - if (out.get(position) != null) { - throw new TypeError(functionName + " got multiple values for argument " + argumentName.repr().value); - } - - if (!argumentKindList.get(position).allowKeyword) { - throw new TypeError(functionName + " got some positional-only arguments passed as keyword arguments: " - + argumentName.repr().value); - } - - remaining--; - out.set(position, keywordArgument.getValue()); - } - - if (remaining > 0) { - List missing = new ArrayList<>(remaining); - for (int i = 0; i < out.size(); i++) { - if (out.get(i) == null) { - if (argumentDefaultList.get(i) != null || nullableArgumentSet.get(i)) { - out.set(i, (PythonLikeObject) argumentDefaultList.get(i)); - remaining--; - } else { - missing.add(i); - } - } - } - - if (remaining > 0) { - if (missing.stream().anyMatch(index -> argumentKindList.get(index).allowPositional)) { - List missingAllowsPositional = new ArrayList<>(remaining); - for (int index : missing) { - if (argumentKindList.get(index).allowPositional) { - missingAllowsPositional.add(argumentNameList.get(index)); - } - } - String argumentString = (missingAllowsPositional.size() == 1) ? "argument" : "arguments"; - throw new TypeError(functionName + " missing " + remaining + " required positional " + argumentString - + ": '" + String.join("', ", missingAllowsPositional) + "'"); - } else { - List missingKeywordOnly = new ArrayList<>(remaining); - for (int index : missing) { - missingKeywordOnly.add(argumentNameList.get(index)); - } - String argumentString = (missingKeywordOnly.size() == 1) ? "argument" : "arguments"; - throw new TypeError(functionName + " missing " + remaining + " required keyword-only " + argumentString - + ": '" + String.join("', ", missingKeywordOnly) + "'"); - } - } - } - - for (int i = 0; i < argumentNameList.size(); i++) { - if ((out.get(i) == null && !nullableArgumentSet.get(i)) - || (out.get(i) != null && !argumentTypeList.get(i).isInstance(out.get(i)))) { - throw new TypeError(functionName + "'s argument '" + argumentNameList.get(i) + "' has incorrect type: " + - "'" + argumentNameList.get(i) + "' must be a " + - JavaPythonTypeConversionImplementor.getPythonLikeType(argumentTypeList.get(i)) + - " (got " - + ((out.get(i) != null) ? JavaPythonTypeConversionImplementor.getPythonLikeType(out.get(i).getClass()) - : "NULL") - + " instead)"); - } - } - return out; - } - - public boolean verifyMatchesCallSignature(int positionalArgumentCount, List keywordArgumentNameList, - List callStackTypeList) { - computeArgumentTypeList(); - - Set missingValue = getRequiredArgumentIndexSet(); - for (int keywordIndex = 0; keywordIndex < keywordArgumentNameList.size(); keywordIndex++) { - String keyword = keywordArgumentNameList.get(keywordIndex); - PythonLikeType stackType = callStackTypeList.get(positionalArgumentCount + keywordIndex); - int index = argumentNameList.indexOf(keyword); - if (index == -1 && extraKeywordsArgumentIndex.isEmpty()) { - return false; - } - if (index != -1 && index < positionalArgumentCount) { - return false; - } else { - try { - if (!argumentTypeList.get(index).isAssignableFrom(stackType.getJavaClass())) { - return false; - } - } catch (ClassNotFoundException e) { - // Assume if the type is not found, it assignable - } - missingValue.remove(index); - } - } - - if (positionalArgumentCount < requiredPositionalArguments || positionalArgumentCount > getTotalArgumentCount()) { - return false; - } - - for (int i = 0; i < positionalArgumentCount; i++) { - missingValue.remove(i); - try { - if (!argumentTypeList.get(i).isAssignableFrom(callStackTypeList.get(i).getJavaClass())) { - return false; - } - } catch (ClassNotFoundException e) { - // Assume if the type is not found, it assignable - } - } - - if (!missingValue.isEmpty()) { - return false; - } - - if (extraPositionalsArgumentIndex.isEmpty() && extraKeywordsArgumentIndex.isEmpty()) { // no *vargs or **kwargs - return positionalArgumentCount <= numberOfPositionalArguments && - positionalArgumentCount + keywordArgumentNameList.size() <= argumentNameList.size(); - } else if (extraPositionalsArgumentIndex.isPresent() && extraKeywordsArgumentIndex.isEmpty()) { // *vargs only - return true; - } else if (extraPositionalsArgumentIndex.isEmpty()) { // **kwargs only - return positionalArgumentCount < numberOfPositionalArguments; - } else { // *vargs and **kwargs - return true; - } - } - - private Set getRequiredArgumentIndexSet() { - Set out = new HashSet<>(); - for (int i = 0; i < argumentNameList.size(); i++) { - if (argumentKindList.get(i) == ArgumentKind.VARARGS) { - continue; - } - if (argumentDefaultList.get(i) != null || nullableArgumentSet.get(i)) { - continue; - } - out.add(i); - } - return out; - } - - private ArgumentSpec addArgument(String argumentName, - String argumentTypeName, ArgumentKind argumentKind, ArgumentType_ defaultValue, - Optional extraPositionalsArgumentIndex, Optional extraKeywordsArgumentIndex, boolean allowNull) { - return new ArgumentSpec<>(argumentName, argumentTypeName, argumentKind, defaultValue, - extraPositionalsArgumentIndex, extraKeywordsArgumentIndex, allowNull, this); - } - - public ArgumentSpec addArgument(String argumentName, - String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_AND_KEYWORD, null, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec - addPositionalOnlyArgument(String argumentName, String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_ONLY, null, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec - addKeywordOnlyArgument(String argumentName, String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.KEYWORD_ONLY, null, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec addArgument(String argumentName, - String argumentTypeName, ArgumentType_ defaultValue) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_AND_KEYWORD, defaultValue, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec - addPositionalOnlyArgument(String argumentName, String argumentTypeName, ArgumentType_ defaultValue) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_ONLY, defaultValue, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec - addKeywordOnlyArgument(String argumentName, String argumentTypeName, ArgumentType_ defaultValue) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.KEYWORD_ONLY, defaultValue, - Optional.empty(), Optional.empty(), false); - } - - public ArgumentSpec addNullableArgument(String argumentName, - String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_AND_KEYWORD, null, - Optional.empty(), Optional.empty(), true); - } - - public ArgumentSpec addNullablePositionalOnlyArgument(String argumentName, - String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.POSITIONAL_ONLY, null, - Optional.empty(), Optional.empty(), true); - } - - public ArgumentSpec addNullableKeywordOnlyArgument(String argumentName, - String argumentTypeName) { - return addArgument(argumentName, argumentTypeName, ArgumentKind.KEYWORD_ONLY, null, - Optional.empty(), Optional.empty(), true); - } - - public ArgumentSpec addExtraPositionalVarArgument(String argumentName) { - return addArgument(argumentName, PythonLikeTuple.class.getName(), ArgumentKind.VARARGS, null, - Optional.of(getTotalArgumentCount()), Optional.empty(), false); - } - - public ArgumentSpec addExtraKeywordVarArgument(String argumentName) { - return addArgument(argumentName, PythonLikeDict.class.getName(), ArgumentKind.VARARGS, null, - Optional.empty(), Optional.of(getTotalArgumentCount()), false); - } - - public PythonFunctionSignature asPythonFunctionSignature(Method method) { - verifyMethodMatchesSpec(method); - return getPythonFunctionSignatureForMethodDescriptor(new MethodDescriptor(method), - method.getReturnType()); - } - - public PythonFunctionSignature asStaticPythonFunctionSignature(Method method) { - verifyMethodMatchesSpec(method); - return getPythonFunctionSignatureForMethodDescriptor(new MethodDescriptor(method, MethodDescriptor.MethodType.STATIC), - method.getReturnType()); - } - - public PythonFunctionSignature asClassPythonFunctionSignature(Method method) { - verifyMethodMatchesSpec(method); - return getPythonFunctionSignatureForMethodDescriptor(new MethodDescriptor(method, MethodDescriptor.MethodType.CLASS), - method.getReturnType()); - } - - public PythonFunctionSignature asPythonFunctionSignature(String internalClassName, String methodName, - String methodDescriptor) { - MethodDescriptor method = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.VIRTUAL, - methodName, methodDescriptor); - try { - return getPythonFunctionSignatureForMethodDescriptor(method, - BuiltinTypes.asmClassLoader.loadClass( - method.getReturnType().getClassName().replace('/', '.'))); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - public PythonFunctionSignature asStaticPythonFunctionSignature(String internalClassName, String methodName, - String methodDescriptor) { - MethodDescriptor method = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.STATIC, - methodName, methodDescriptor); - try { - return getPythonFunctionSignatureForMethodDescriptor(method, - BuiltinTypes.asmClassLoader.loadClass( - method.getReturnType().getClassName().replace('/', '.'))); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - public PythonFunctionSignature asClassPythonFunctionSignature(String internalClassName, String methodName, - String methodDescriptor) { - MethodDescriptor method = new MethodDescriptor(internalClassName, MethodDescriptor.MethodType.CLASS, - methodName, methodDescriptor); - try { - return getPythonFunctionSignatureForMethodDescriptor(method, - BuiltinTypes.asmClassLoader.loadClass( - method.getReturnType().getClassName().replace('/', '.'))); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - private void verifyMethodMatchesSpec(Method method) { - computeArgumentTypeList(); - - if (!functionReturnType.isAssignableFrom(method.getReturnType())) { - throw new IllegalArgumentException("Method (" + method + ") does not match the given spec (" + this + - "): its return type (" + method.getReturnType() + ") is not " + - "assignable to the spec return type (" + functionReturnTypeName + ")."); - } - - if (method.getParameterCount() != argumentNameList.size()) { - throw new IllegalArgumentException("Method (" + method + ") does not match the given spec (" + this + - "): they have different parameter counts."); - } - - for (int i = 0; i < method.getParameterCount(); i++) { - if (!method.getParameterTypes()[i].isAssignableFrom(argumentTypeList.get(i))) { - throw new IllegalArgumentException("Method (" + method + ") does not match the given spec (" + this + - "): its " + i + " parameter (" + method.getParameters()[i].toString() + ") cannot " + - " be assigned from the spec " + i + " parameter (" + argumentTypeList.get(i) + " " - + argumentNameList.get(i) + ")."); - } - } - } - - @SuppressWarnings("unchecked") - private PythonFunctionSignature getPythonFunctionSignatureForMethodDescriptor(MethodDescriptor methodDescriptor, - Class javaReturnType) { - computeArgumentTypeList(); - - int firstDefault = 0; - - while (firstDefault < argumentDefaultList.size() && argumentDefaultList.get(firstDefault) == null && - !nullableArgumentSet.get(firstDefault)) { - firstDefault++; - } - List defaultParameterValueList; - - if (firstDefault != argumentDefaultList.size()) { - defaultParameterValueList = (List) (List) argumentDefaultList.subList(firstDefault, - argumentDefaultList.size()); - } else { - defaultParameterValueList = Collections.emptyList(); - } - - List parameterTypeList = argumentTypeList.stream() - .map(JavaPythonTypeConversionImplementor::getPythonLikeType) - .collect(Collectors.toList()); - - PythonLikeType returnType = JavaPythonTypeConversionImplementor.getPythonLikeType(javaReturnType); - Map keywordArgumentToIndexMap = new HashMap<>(); - - for (int i = 0; i < argumentNameList.size(); i++) { - if (argumentKindList.get(i).allowKeyword) { - keywordArgumentToIndexMap.put(argumentNameList.get(i), i); - } - } - - return new PythonFunctionSignature(methodDescriptor, defaultParameterValueList, - keywordArgumentToIndexMap, returnType, - parameterTypeList, extraPositionalsArgumentIndex, extraKeywordsArgumentIndex, - this); - } - - public Object getDefaultValue(int defaultIndex) { - return argumentDefaultList.get(defaultIndex); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("ArgumentSpec("); - out.append("name=").append(functionName) - .append(", returnType=").append(functionReturnTypeName) - .append(", arguments=["); - - for (int i = 0; i < argumentNameList.size(); i++) { - out.append(argumentTypeNameList.get(i)); - out.append(" "); - out.append(argumentNameList.get(i)); - - if (nullableArgumentSet.get(i)) { - out.append(" (nullable)"); - } - - if (argumentDefaultList.get(i) != null) { - out.append(" (default: "); - out.append(argumentDefaultList.get(i)); - out.append(")"); - } - - if (argumentKindList.get(i) != ArgumentKind.POSITIONAL_AND_KEYWORD) { - if (extraPositionalsArgumentIndex.isPresent() && extraPositionalsArgumentIndex.get() == i) { - out.append(" (vargs)"); - } else if (extraKeywordsArgumentIndex.isPresent() && extraKeywordsArgumentIndex.get() == i) { - out.append(" (kwargs)"); - } else { - out.append(" ("); - out.append(argumentKindList.get(i)); - out.append(")"); - } - } - if (i != argumentNameList.size() - 1) { - out.append(", "); - } - } - out.append("])"); - - return out.toString(); - } -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/PentaFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/PentaFunction.java deleted file mode 100644 index ddfcbec0..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/PentaFunction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.util.function; - -public interface PentaFunction { - Result_ apply(A_ a, B_ b, C_ c, D_ d, E_ e); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadConsumer.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadConsumer.java deleted file mode 100644 index 4bca6d65..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadConsumer.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.util.function; - -public interface QuadConsumer { - void accept(A a, B b, C c, D d); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadFunction.java deleted file mode 100644 index c053d47b..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/QuadFunction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.util.function; - -public interface QuadFunction { - Result_ apply(A_ a, B_ b, C_ c, D_ d); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriConsumer.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriConsumer.java deleted file mode 100644 index ba96f104..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriConsumer.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.util.function; - -public interface TriConsumer { - void accept(A a, B b, C c); -} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriFunction.java deleted file mode 100644 index 9c082787..00000000 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/function/TriFunction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.timefold.jpyinterpreter.util.function; - -public interface TriFunction { - Result_ apply(A a, B b, C c); -} diff --git a/jpyinterpreter/src/main/python/__init__.py b/jpyinterpreter/src/main/python/__init__.py deleted file mode 100644 index bd832e91..00000000 --- a/jpyinterpreter/src/main/python/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module acts as an interface to the Python bytecode to Java bytecode interpreter -""" -from .jvm_setup import init, set_class_output_directory, get_path -from .annotations import JavaAnnotation, AnnotationValueSupplier, add_class_annotation, add_java_interface -from .conversions import (convert_to_java_python_like_object, unwrap_python_like_object, - update_python_object_from_java, is_c_native, add_python_java_type_mapping) -from .translator import (translate_python_bytecode_to_java_bytecode, - translate_python_class_to_java_class, - force_update_type, - generate_proxy_class_for_translated_function, - generate_proxy_class_for_translated_class, - get_java_type_for_python_type, as_java, - as_untyped_java, as_typed_java, - is_current_python_version_supported, - check_current_python_version_supported, is_python_version_supported, - _force_as_java_generator) diff --git a/jpyinterpreter/src/main/python/annotations.py b/jpyinterpreter/src/main/python/annotations.py deleted file mode 100644 index 3ec55d21..00000000 --- a/jpyinterpreter/src/main/python/annotations.py +++ /dev/null @@ -1,339 +0,0 @@ -from collections import defaultdict -from dataclasses import dataclass -from types import FunctionType, NoneType, UnionType -from typing import TypeVar, Any, List, Tuple, Dict, Union, Annotated, Type, Callable, \ - get_origin, get_args, get_type_hints -from jpype import JClass, JArray - - -class AnnotationValueSupplier: - def __init__(self, supplier: Callable[[], Any]): - self.supplier = supplier - - def get_value(self) -> Any: - return self.supplier() - - -@dataclass -class JavaAnnotation: - annotation_type: JClass - annotation_values: Dict[str, Any] - - def __hash__(self): - return 0 - - -T = TypeVar('T') - - -def add_class_annotation(annotation_type, /, **annotation_values: Any) -> Callable[[Type[T]], Type[T]]: - def decorator(_cls: Type[T]) -> Type[T]: - from .translator import type_to_compiled_java_class, type_to_annotations - if _cls in type_to_compiled_java_class: - raise RuntimeError('Cannot add an annotation after a class has been compiled.') - annotations = type_to_annotations.get(_cls, []) - annotation = JavaAnnotation(annotation_type, annotation_values) - annotations.append(annotation) - type_to_annotations[_cls] = annotations - return _cls - - return decorator - - -def add_java_interface(java_interface: JClass | str, /) -> Callable[[Type[T]], Type[T]]: - def decorator(_cls: Type[T]) -> Type[T]: - from .translator import type_to_compiled_java_class, type_to_java_interfaces - if _cls in type_to_compiled_java_class: - raise RuntimeError('Cannot add an interface after a class has been compiled.') - marker_interfaces = type_to_java_interfaces.get(_cls, []) - marker_interfaces.append(java_interface) - type_to_java_interfaces[_cls] = marker_interfaces - return _cls - - return decorator - - -def copy_type_annotations(hinted_object, default_args, vargs_name, kwargs_name): - from java.util import HashMap, Collections - from ai.timefold.jpyinterpreter import TypeHint - from .translator import type_to_compiled_java_class - from typing import ClassVar - - out = HashMap() - try: - type_hints = get_type_hints(hinted_object, include_extras=True) - except (AttributeError, NameError, TypeError): - # Occurs if get_type_hints cannot resolve a forward reference - type_hints = hinted_object.__annotations__ if hasattr(hinted_object, '__annotations__') else {} - - for name, type_hint in type_hints.items(): - if not isinstance(name, str): - continue - if name == vargs_name: - out.put(name, TypeHint.withoutAnnotations(type_to_compiled_java_class[tuple])) - continue - if name == kwargs_name: - out.put(name, TypeHint.withoutAnnotations(type_to_compiled_java_class[dict])) - continue - - hint_type = type_hint - hint_annotations = Collections.emptyList() - if get_origin(type_hint) is Annotated: - hint_type = get_args(type_hint)[0] - hint_annotations = get_java_annotations(type_hint.__metadata__) # noqa - - if get_origin(type_hint) is ClassVar: - # Skip over class variables, since they are not - # instance attributes - continue - - if name in default_args: - hint_type = Union[hint_type, type(default_args[name])] - - java_type_hint = get_java_type_hint(hint_type) - out.put(name, java_type_hint.addAnnotations(hint_annotations)) - - return out - - -def find_closest_common_ancestor(*cls_list: type): - mros = [(list(cls.__mro__) if hasattr(cls, '__mro__') else [cls]) for cls in cls_list] - track = defaultdict(int) - while mros: - for mro in mros: - cur = mro.pop(0) - track[cur] += 1 - if track[cur] == len(cls_list): - return cur - if len(mro) == 0: - mros.remove(mro) - return object - - -def get_java_type_hint(hint_type): - from .translator import get_java_type_for_python_type, type_to_compiled_java_class - from typing import get_args as get_generic_args - from java.lang import Class as JavaClass - from java.util import Collections - from ai.timefold.jpyinterpreter import TypeHint - from ai.timefold.jpyinterpreter.types import BuiltinTypes - from ai.timefold.jpyinterpreter.types.wrappers import JavaObjectWrapper - - origin_type = get_origin(hint_type) - if origin_type is None: - # Happens for Callable[[parameter_types], return_type] - if isinstance(hint_type, list) or isinstance(hint_type, tuple): - return TypeHint(BuiltinTypes.BASE_TYPE, Collections.emptyList()) - # Not a generic type - elif hint_type in type_to_compiled_java_class: - return TypeHint(type_to_compiled_java_class[hint_type], Collections.emptyList()) - elif isinstance(hint_type, (JClass, JavaClass)): - java_type = JavaObjectWrapper.getPythonTypeForClass(hint_type) - type_to_compiled_java_class[hint_type] = java_type - return TypeHint(java_type, Collections.emptyList()) - elif isinstance(hint_type, (type, str)): - return TypeHint(get_java_type_for_python_type(hint_type), Collections.emptyList()) - else: - return TypeHint(BuiltinTypes.BASE_TYPE, Collections.emptyList()) - - generic_args = get_generic_args(hint_type) - - if origin_type is Union or origin_type is UnionType: - union_types_excluding_none = [] - union_types_including_none = [] - for union_type in generic_args: - union_types_including_none.append(union_type) - if union_type == NoneType: - continue - union_types_excluding_none.append(union_type) - - return TypeHint(get_java_type_hint(find_closest_common_ancestor(*union_types_including_none)).type(), - Collections.emptyList(), - get_java_type_hint(find_closest_common_ancestor(*union_types_excluding_none)).type()) - - origin_type_hint = get_java_type_hint(origin_type) - generic_arg_type_hint_array = JArray(TypeHint)(len(generic_args)) - for i in range(len(generic_args)): - generic_arg_type_hint_array[i] = get_java_type_hint(generic_args[i]) - - return TypeHint(origin_type_hint.type(), Collections.emptyList(), generic_arg_type_hint_array, - origin_type_hint.type()) - - -def get_java_annotations(annotated_metadata: List[Any]): - from java.util import ArrayList - out = ArrayList() - for metadata in annotated_metadata: - if not isinstance(metadata, JavaAnnotation): - if isinstance(metadata, type) and issubclass(metadata, JavaAnnotation): - try: - metadata = metadata() - except TypeError as e: - raise ValueError(f'The annotation class {metadata.__name__} has required attributes.' - f'Create an instance using {metadata.__name__}(...).') from e - else: - continue - out.add(convert_java_annotation(metadata)) - return out - - -def convert_java_annotation(java_annotation: JavaAnnotation): - from java.util import HashMap - from ai.timefold.jpyinterpreter import AnnotationMetadata - annotation_values = HashMap() - for attribute_name, attribute_value in java_annotation.annotation_values.items(): - if isinstance(attribute_value, AnnotationValueSupplier): - attribute_value = attribute_value.get_value() - - annotation_method = java_annotation.annotation_type.class_.getDeclaredMethod(attribute_name) - attribute_type = annotation_method.getReturnType() - java_attribute_value = convert_annotation_value(java_annotation.annotation_type, attribute_type, - attribute_name, attribute_value) - if java_attribute_value is not None: - annotation_values.put(attribute_name, java_attribute_value) - return AnnotationMetadata(java_annotation.annotation_type.class_, annotation_values) - - -def convert_annotation_value(annotation_type: JClass, attribute_type: JClass, attribute_name: str, attribute_value: Any): - from .translator import (get_java_type_for_python_type, - translate_python_bytecode_to_java_bytecode, - generate_proxy_class_for_translated_function) - from jpype import JBoolean, JByte, JChar, JShort, JInt, JLong, JFloat, JDouble, JString, JArray - from ai.timefold.jpyinterpreter import AnnotationMetadata - from org.objectweb.asm import Type as ASMType - - if attribute_value is None: - return None - # See 9.6.1 of the Java spec for possible element values of annotations - if attribute_type == JClass('boolean').class_: - return JBoolean(attribute_value) - elif attribute_type == JClass('byte').class_: - return JByte(attribute_value) - elif attribute_type == JClass('char').class_: - return JChar(attribute_value) - elif attribute_type == JClass('short').class_: - return JShort(attribute_value) - elif attribute_type == JClass('int').class_: - return JInt(attribute_value) - elif attribute_type == JClass('long').class_: - return JLong(attribute_value) - elif attribute_type == JClass('float').class_: - return JFloat(attribute_value) - elif attribute_type == JClass('double').class_: - return JDouble(attribute_value) - elif attribute_type == JClass('java.lang.String').class_: - return JString(attribute_value) - elif attribute_type == JClass('java.lang.Class').class_: - if isinstance(attribute_value, ASMType): - return attribute_value - if isinstance(attribute_value, JClass('java.lang.Class')): - return AnnotationMetadata.getValueAsType(attribute_value.getName()) - elif isinstance(attribute_value, type): - out = get_java_type_for_python_type(attribute_type) - return AnnotationMetadata.getValueAsType(out.getJavaTypeInternalName()) - elif isinstance(attribute_value, FunctionType): - method = annotation_type.class_.getDeclaredMethod(attribute_name) - generic_type = method.getGenericReturnType() - try: - function_type_and_generic_args = resolve_java_function_type_as_tuple(generic_type) - instance = translate_python_bytecode_to_java_bytecode(attribute_value, *function_type_and_generic_args) - return AnnotationMetadata.getValueAsType(generate_proxy_class_for_translated_function( - function_type_and_generic_args[0], instance).getName()) - except ValueError: - raw_type = resolve_raw_type(generic_type.getActualTypeArguments()[0]) - instance = translate_python_bytecode_to_java_bytecode(attribute_value, raw_type) - return AnnotationMetadata.getValueAsType(generate_proxy_class_for_translated_function( - raw_type, instance).getName()) - else: - raise ValueError(f'Illegal value for {attribute_name} in annotation {annotation_type}: {attribute_value}') - elif attribute_type.isEnum(): - return attribute_value - elif attribute_type.isArray(): - dimensions = get_dimensions(attribute_type) - component_type = get_component_type(attribute_type) - return JArray(component_type, dims=dimensions)(convert_annotation_array_elements(annotation_type, - component_type.class_, - attribute_name, - attribute_value)) - elif JClass('java.lang.Annotation').class_.isAssignableFrom(attribute_type): - if not isinstance(attribute_value, JavaAnnotation): - raise ValueError(f'Illegal value for {attribute_name} in annotation {annotation_type}: {attribute_value}') - return convert_java_annotation(attribute_value) - else: - raise ValueError(f'Illegal type for annotation element {attribute_type} for element named ' - f'{attribute_name} on annotation type {annotation_type}.') - - -def resolve_java_function_type_as_tuple(function_class) -> Tuple[JClass]: - from java.lang.reflect import ParameterizedType, WildcardType - if isinstance(function_class, WildcardType): - return resolve_java_type_as_tuple(function_class.getUpperBounds()[0]) - elif isinstance(function_class, ParameterizedType): - return resolve_java_type_as_tuple(function_class.getActualTypeArguments()[0]) - else: - raise ValueError(f'Unable to determine interface for type {function_class}') - - -def resolve_java_type_as_tuple(generic_type) -> Tuple[JClass]: - from java.lang.reflect import ParameterizedType, WildcardType - if isinstance(generic_type, WildcardType): - return (*map(resolve_java_type_as_tuple, generic_type.getUpperBounds()),) - elif isinstance(generic_type, ParameterizedType): - return resolve_raw_types(generic_type.getRawType(), *generic_type.getActualTypeArguments()) - elif isinstance(generic_type, JClass): - return (generic_type,) - else: - raise ValueError(f'Unable to determine interface for type {generic_type}') - - -def resolve_raw_types(*type_arguments) -> Tuple[JClass]: - return (*map(resolve_raw_type, type_arguments),) - - -def resolve_raw_type(type_argument) -> JClass: - from java.lang.reflect import ParameterizedType, WildcardType - if isinstance(type_argument, ParameterizedType): - return resolve_raw_type(type_argument.getRawType()) - elif isinstance(type_argument, WildcardType): - return resolve_raw_type(type_argument.getUpperBounds()[0]) - return type_argument - - -def convert_annotation_array_elements(annotation_type: JClass, component_type: JClass, attribute_name: str, - array_elements: List) -> List: - out = [] - for item in array_elements: - if isinstance(item, (list, tuple)): - out.append(convert_annotation_array_elements(annotation_type, component_type, attribute_name, item)) - else: - out.append(convert_annotation_value(annotation_type, component_type, attribute_name, item)) - return out - - -def get_dimensions(array_type: JClass) -> int: - if array_type.getComponentType() is None: - return 0 - return get_dimensions(array_type.getComponentType()) + 1 - - -def get_component_type(array_type: JClass) -> JClass: - if not array_type.getComponentType().isArray(): - return JClass(array_type.getComponentType().getCanonicalName()) - return get_component_type(array_type.getComponentType()) - - -def erase_generic_args(python_type): - from typing import get_origin - if isinstance(python_type, type): - out = python_type - if get_origin(out) is not None: - return get_origin(out) - return out - elif isinstance(python_type, str): - try: - generics_start = python_type.index('[') - return python_type[generics_start:-2] - except ValueError: - return python_type - else: - raise ValueError diff --git a/jpyinterpreter/src/main/python/conversions.py b/jpyinterpreter/src/main/python/conversions.py deleted file mode 100644 index a719f3c2..00000000 --- a/jpyinterpreter/src/main/python/conversions.py +++ /dev/null @@ -1,769 +0,0 @@ -import builtins -import inspect -import importlib -from dataclasses import dataclass -from typing import TYPE_CHECKING -from traceback import TracebackException, StackSummary, FrameSummary -from copy import copy - -from jpype import JLong, JDouble, JBoolean, JProxy - - -if TYPE_CHECKING: - from java.util import IdentityHashMap - from java.lang import Throwable - - -def extract_frames_from_java_error(java_error: 'Throwable'): - stack_trace = java_error.getStackTrace() - start_index = 0 - stop_index = len(stack_trace) - while start_index < stop_index and (stack_trace[start_index].getFileName() is None or - not stack_trace[start_index].getFileName().endswith('.py')): - start_index += 1 - - # If there is no python part, keep the entire exception - if start_index == stop_index: - start_index = 0 - - for i in range(start_index, stop_index): - stack_trace_element = stack_trace[stop_index - (i - start_index) - 1] - file_name = stack_trace_element.getFileName() or '' - if file_name.endswith('.py'): - class_name = stack_trace_element.getClassName() or '' - function_name = class_name.rsplit('.', 1)[-1].split('$', 1)[0] - else: - function_name = stack_trace_element.getMethodName() or '' - line_number = stack_trace_element.getLineNumber() or 0 - yield FrameSummary(file_name, line_number, function_name) - - -def get_traceback_exception(java_error: 'Throwable', python_exception_type: type, clone_map: 'PythonCloneMap') -> TracebackException: - out = object.__new__(TracebackException) - if java_error.getCause() is not None: - out.__cause__ = get_traceback_exception(java_error.getCause(), - unwrap_python_like_object(java_error.getCause(), - clone_map).__class__.__bases__[0], - clone_map) - else: - out.__cause__ = None - - out.__suppress_context__ = False - out.__context__ = None - out.__notes__ = None - out.exceptions = None - out.exc_type = python_exception_type - out.stack = StackSummary.from_list(extract_frames_from_java_error(java_error)) - out._str = java_error.getMessage() - return out - - -def get_translated_java_system_error_message(error): - from ai.timefold.jpyinterpreter.util import TracebackUtils - top_line = f'{error.getClass().getSimpleName()}: {error.getMessage()}' - traceback = TracebackUtils.getTraceback(error) - return f'{top_line}\n{traceback}' - - -class TranslatedJavaSystemError(SystemError): - def __init__(self, error): - super().__init__(get_translated_java_system_error_message(error)) - - -# Taken from https://stackoverflow.com/a/60953150 -def is_native_module(module): - """ is_native_module(thing) -> boolean predicate, True if `module` - is a native-compiled ("extension") module. - - Q.v. this fine StackOverflow answer on this subject: - https://stackoverflow.com/a/39304199/298171 - """ - import importlib.machinery - import inspect - - QUALIFIER = '.' - EXTENSION_SUFFIXES = tuple(suffix.lstrip(QUALIFIER) - for suffix - in importlib.machinery.EXTENSION_SUFFIXES) - - suffix = lambda filename: QUALIFIER in filename \ - and filename.rpartition(QUALIFIER)[-1] \ - or '' - # Step one: modules only beyond this point: - if not inspect.ismodule(module): - return False - - # Step two: return truly when “__loader__” is set: - if isinstance(getattr(module, '__loader__', None), - importlib.machinery.ExtensionFileLoader): - return True - - # Step three: in leu of either of those indicators, - # check the module path’s file suffix: - try: - ext = suffix(inspect.getfile(module)) - except TypeError as exc: - return 'is a built-in' in str(exc) - - return ext in EXTENSION_SUFFIXES - - -def is_c_native(item): - import importlib - module = getattr(item, '__module__', '') - - # __main__ is a built-in module, according to Python (and this be seen as c-native). We can also compile builtins, - # so return False, so we can compile them - if module == '__main__' or \ - module == 'builtins' \ - or module == '': # if we cannot find module, assume it is not native - return False - - try: - return is_native_module(importlib.import_module(module)) - except: - return True - - -def init_type_to_compiled_java_class(): - from .translator import check_current_python_version_supported, type_to_compiled_java_class - from ai.timefold.jpyinterpreter.builtins import GlobalBuiltins - from ai.timefold.jpyinterpreter.types import BuiltinTypes - import ai.timefold.jpyinterpreter.types.datetime as java_datetime_types - import datetime - import builtins - import decimal - - if len(type_to_compiled_java_class) > 0: - return - - check_current_python_version_supported() - - type_to_compiled_java_class[staticmethod] = BuiltinTypes.STATIC_FUNCTION_TYPE - type_to_compiled_java_class[classmethod] = BuiltinTypes.CLASS_FUNCTION_TYPE - - type_to_compiled_java_class[int] = BuiltinTypes.INT_TYPE - type_to_compiled_java_class[float] = BuiltinTypes.FLOAT_TYPE - type_to_compiled_java_class[complex] = BuiltinTypes.COMPLEX_TYPE - type_to_compiled_java_class[bool] = BuiltinTypes.BOOLEAN_TYPE - type_to_compiled_java_class[decimal.Decimal] = BuiltinTypes.DECIMAL_TYPE - - type_to_compiled_java_class[type(None)] = BuiltinTypes.NONE_TYPE - type_to_compiled_java_class[str] = BuiltinTypes.STRING_TYPE - type_to_compiled_java_class[bytes] = BuiltinTypes.BYTES_TYPE - type_to_compiled_java_class[bytearray] = BuiltinTypes.BYTE_ARRAY_TYPE - type_to_compiled_java_class[object] = BuiltinTypes.BASE_TYPE - - type_to_compiled_java_class[list] = BuiltinTypes.LIST_TYPE - type_to_compiled_java_class[tuple] = BuiltinTypes.TUPLE_TYPE - type_to_compiled_java_class[set] = BuiltinTypes.SET_TYPE - type_to_compiled_java_class[frozenset] = BuiltinTypes.FROZEN_SET_TYPE - type_to_compiled_java_class[dict] = BuiltinTypes.DICT_TYPE - - type_to_compiled_java_class[datetime.datetime] = java_datetime_types.PythonDateTime.DATE_TIME_TYPE - type_to_compiled_java_class[datetime.date] = java_datetime_types.PythonDate.DATE_TYPE - type_to_compiled_java_class[datetime.time] = java_datetime_types.PythonTime.TIME_TYPE - type_to_compiled_java_class[datetime.timedelta] = java_datetime_types.PythonTimeDelta.TIME_DELTA_TYPE - - # Type aliases - type_to_compiled_java_class[any] = BuiltinTypes.BASE_TYPE - type_to_compiled_java_class[type] = BuiltinTypes.TYPE_TYPE - - for java_type in GlobalBuiltins.getBuiltinTypes(): - try: - type_to_compiled_java_class[getattr(builtins, java_type.getTypeName())] = java_type - except AttributeError: - # This version of python does not have this builtin type; pass - pass - - -def add_python_java_type_mapping(mapping): - from .translator import python_java_type_mappings - python_java_type_mappings.append(mapping) - - -def copy_iterable(iterable): - from java.util import ArrayList - if iterable is None: - return None - iterable_copy = ArrayList() - for item in iterable: - iterable_copy.add(item) - return iterable_copy - - -def remove_from_instance_map(instance_map, object_id): - instance_map.remove(object_id) - - -def put_in_instance_map(instance_map, python_object, java_object): - global objects_without_weakref_id_set - instance_map.put(id(python_object), java_object) - - -class CodeWrapper: - def __init__(self, wrapped): - self.wrapped = wrapped - - def __getitem__(self, item): - if item == 'wrapped': - return self.wrapped - else: - raise KeyError(f'No item: {item}') - - -def convert_object_to_java_python_like_object(value, instance_map=None): - import datetime - from .annotations import JavaAnnotation - from .translator import (translate_python_bytecode_to_java_bytecode, - translate_python_class_to_java_class) - from .translator import (translate_python_code_to_java_class, - translate_python_code_to_python_wrapper_class, - type_to_compiled_java_class) - from java.lang import Object, ClassNotFoundException, NoSuchMethodException - from java.util import HashMap - from ai.timefold.jpyinterpreter import PythonInterpreter, CPythonBackedPythonInterpreter - from ai.timefold.jpyinterpreter.types import PythonLikeType, AbstractPythonLikeObject, CPythonBackedPythonLikeObject - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference, CPythonType, JavaObjectWrapper, PythonLikeFunctionWrapper - from ai.timefold.jpyinterpreter.types.datetime import PythonDate, PythonDateTime, PythonTime, PythonTimeDelta - - if instance_map is None: - instance_map = HashMap() - - if isinstance(value, Object): - out = JavaObjectWrapper(value) - put_in_instance_map(instance_map, value, out) - return out - if isinstance(value, JavaAnnotation): - return None - elif isinstance(value, datetime.datetime): - out = PythonDateTime.of(value.year, value.month, value.day, value.hour, value.minute, value.second, - value.microsecond, value.tzname(), value.fold) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, datetime.date): - out = PythonDate.of(value.year, value.month, value.day) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, datetime.time): - out = PythonTime.of(value.hour, value.minute, value.second, value.microsecond, value.tzname(), value.fold) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, datetime.timedelta): - out = PythonTimeDelta.of(value.days, value.seconds, value.microseconds) - put_in_instance_map(instance_map, value, out) - return out - elif inspect.iscode(value): - try: - from ai.timefold.jpyinterpreter.types import PythonLikeFunction, PythonCode - java_class = translate_python_code_to_java_class(value, PythonLikeFunction) - out = PythonCode(java_class) - put_in_instance_map(instance_map, value, out) - return out - except: - from ai.timefold.jpyinterpreter.types import PythonLikeFunction, PythonCode - java_class = translate_python_code_to_python_wrapper_class(value) - out = PythonCode(java_class) - put_in_instance_map(instance_map, value, out) - return out - elif type(value) is object: - java_type = type_to_compiled_java_class[type(value)] - out = CPythonBackedPythonLikeObject(PythonInterpreter.DEFAULT, java_type) - put_in_instance_map(instance_map, value, out) - CPythonBackedPythonInterpreter.updateJavaObjectFromPythonObject(out, - JProxy(OpaquePythonReference, inst=value, - convert=True), - instance_map) - return out - elif not inspect.isfunction(value) and type(value) in type_to_compiled_java_class: - if type_to_compiled_java_class[type(value)] is None: - return None - java_type = type_to_compiled_java_class[type(value)] - if isinstance(java_type, CPythonType): - return None - try: - java_class = java_type.getJavaClass() - except ClassNotFoundException: - # Class is currently being generated - return None - - try: - out = java_class.getConstructor(PythonInterpreter, PythonLikeType).newInstance(PythonInterpreter.DEFAULT, - java_type) - except NoSuchMethodException: - # Value class, such as the error classes ValueError, and are independent of the interpreter - out = java_class.getConstructor(PythonLikeType).newInstance(java_type) - - put_in_instance_map(instance_map, value, out) - CPythonBackedPythonInterpreter.updateJavaObjectFromPythonObject(out, - JProxy(OpaquePythonReference, inst=value, - convert=True), - instance_map) - - if isinstance(out, AbstractPythonLikeObject): - try: - for (key, value) in object.__getattribute__(value, '__dict__').items(): - out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) - except AttributeError: - pass - - return out - elif inspect.isbuiltin(value) or is_c_native(value): - return None - elif inspect.isfunction(value): - try: - from ai.timefold.jpyinterpreter.types import PythonLikeFunction - wrapped = PythonLikeFunctionWrapper() - put_in_instance_map(instance_map, value, wrapped) - out = translate_python_bytecode_to_java_bytecode(value, PythonLikeFunction) - wrapped.setWrapped(out) - put_in_instance_map(instance_map, value, out) - return out - except: - return None - else: - try: - java_type = translate_python_class_to_java_class(type(value)) - if isinstance(java_type, CPythonType): - return None - java_class = java_type.getJavaClass() - try: - out = java_class.getConstructor(PythonInterpreter, PythonLikeType).newInstance(PythonInterpreter.DEFAULT, - java_type) - except NoSuchMethodException: - # Value class, such as the error classes ValueError, and are independent of the interpreter - out = java_class.getConstructor(PythonLikeType).newInstance(java_type) - - put_in_instance_map(instance_map, value, out) - CPythonBackedPythonInterpreter.updateJavaObjectFromPythonObject(out, - JProxy(OpaquePythonReference, inst=value, - convert=True), - instance_map) - - if isinstance(out, AbstractPythonLikeObject): - try: - for (key, value) in object.__getattribute__(value, '__dict__').items(): - out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) - except AttributeError: - pass - - return out - except: - return None - - -def is_banned_module(module: str): - banned_modules = {'jpype', 'importlib', 'builtins'} - for banned_module in banned_modules: - if module == banned_module: - return True - elif module == f'_{banned_module}': - return True - elif module.startswith(f'{banned_module}.'): - return True - elif module.startswith(f'_{banned_module}.'): - return True - return False - - -def convert_to_java_python_like_object(value, instance_map=None): - from .translator import translate_python_class_to_java_class, type_to_compiled_java_class - from .annotations import erase_generic_args - from java.util import HashMap - from java.math import BigInteger - from types import ModuleType - from decimal import Decimal - from ai.timefold.jpyinterpreter import PythonLikeObject, CPythonBackedPythonInterpreter - from ai.timefold.jpyinterpreter.types import PythonString, PythonBytes, PythonByteArray, PythonNone, \ - PythonModule, PythonSlice, PythonRange, NotImplemented as JavaNotImplemented - from ai.timefold.jpyinterpreter.types.collections import PythonLikeList, PythonLikeTuple, PythonLikeSet, \ - PythonLikeFrozenSet, PythonLikeDict - from ai.timefold.jpyinterpreter.types.numeric import PythonInteger, PythonFloat, PythonBoolean, PythonComplex, \ - PythonDecimal - from ai.timefold.jpyinterpreter.types.wrappers import PythonObjectWrapper, CPythonType, OpaquePythonReference - - if instance_map is None: - instance_map = HashMap() - - if instance_map.containsKey(JLong(id(value))): - return instance_map.get(JLong(id(value))) - elif isinstance(value, PythonLikeObject): - put_in_instance_map(instance_map, value, value) - return value - elif value is None: - return PythonNone.INSTANCE - elif value is NotImplemented: - return JavaNotImplemented.INSTANCE - elif isinstance(value, bool): - return PythonBoolean.valueOf(JBoolean(value)) - elif isinstance(value, int): - out = PythonInteger.valueOf(BigInteger("{0:x}".format(value), 16)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, float): - out = PythonFloat.valueOf(JDouble(value)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, Decimal): - out = PythonDecimal.valueOf(str(value)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, complex): - out = PythonComplex.valueOf(convert_to_java_python_like_object(value.real, instance_map), - convert_to_java_python_like_object(value.imag, instance_map)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, str): - out = PythonString.valueOf(value) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, bytes): - out = PythonBytes.fromIntTuple(convert_to_java_python_like_object(tuple(value))) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, bytearray): - out = PythonByteArray.fromIntTuple(convert_to_java_python_like_object(tuple(value))) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, tuple): - out = PythonLikeTuple() - put_in_instance_map(instance_map, value, out) - for item in value: - out.add(convert_to_java_python_like_object(item, instance_map)) - return out - elif isinstance(value, list): - out = PythonLikeList() - put_in_instance_map(instance_map, value, out) - for item in value: - out.add(convert_to_java_python_like_object(item, instance_map)) - return out - elif isinstance(value, set): - out = PythonLikeSet() - put_in_instance_map(instance_map, value, out) - for item in value: - out.add(convert_to_java_python_like_object(item, instance_map)) - return out - elif isinstance(value, frozenset): - out = PythonLikeFrozenSet() - put_in_instance_map(instance_map, value, out) - for item in value: - out.delegate.add(convert_to_java_python_like_object(item, instance_map)) - return out - elif isinstance(value, dict): - out = PythonLikeDict() - put_in_instance_map(instance_map, value, out) - for map_key, map_value in value.items(): - out.put(convert_to_java_python_like_object(map_key, instance_map), - convert_to_java_python_like_object(map_value, instance_map)) - return out - elif isinstance(value, slice): - out = PythonSlice(convert_to_java_python_like_object(value.start, instance_map), - convert_to_java_python_like_object(value.stop, instance_map), - convert_to_java_python_like_object(value.step, instance_map)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, range): - out = PythonRange(convert_to_java_python_like_object(value.start, instance_map), - convert_to_java_python_like_object(value.stop, instance_map), - convert_to_java_python_like_object(value.step, instance_map)) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, type): - raw_type = erase_generic_args(value) - if raw_type in type_to_compiled_java_class: - if type_to_compiled_java_class[raw_type] is None: - return None - out = type_to_compiled_java_class[raw_type] - put_in_instance_map(instance_map, value, out) - return out - else: - out = translate_python_class_to_java_class(raw_type) - put_in_instance_map(instance_map, value, out) - return out - elif isinstance(value, ModuleType) and repr(value).startswith(' ContextManager[pathlib.Path]: - """ - Workaround since importlib.resources.path is now deprecated for removal - """ - return importlib.resources.as_file(importlib.resources.files(package) / - _normalize_path(resource)) - - -def extract_python_translator_jars() -> list[str]: - """Extracts and return a list of the Python Translator Java dependencies - """ - return [str(get_path('jpyinterpreter.jars', p.name).__enter__()) - for p in importlib.resources.files('jpyinterpreter.jars').iterdir() - if p.name.endswith(".jar")] - - -def init(*args, path: List[str] = None, include_translator_jars: bool = True, - class_output_path: pathlib.Path = None): - """Start the JVM. Throws a RuntimeError if it is already started. - - :param args: JVM args. - :param path: If not None, a list of dependencies to use as the classpath. Default to None. - :param include_translator_jars: If True, add translators jars to path. Default to True. - :param class_output_path: If not None, sets the generated class output path. If None, no class - files are written. Can be changed by set_class_output_directory - :return: None - """ - if jpype.isJVMStarted(): # noqa - raise RuntimeError('JVM already started.') - if path is None: - include_translator_jars = True - path = [] - if include_translator_jars: - path = path + extract_python_translator_jars() - - user_locale = locale.getlocale()[0] - extra_jvm_args = [] - if user_locale is not None: - user_locale = locale.normalize(user_locale) - if '.' in user_locale: - user_locale, _ = user_locale.split('.', 1) - if '_' in user_locale: - lang, country = user_locale.rsplit('_', maxsplit=1) - extra_jvm_args.append(f'-Duser.language={lang}') - extra_jvm_args.append(f'-Duser.country={country}') - else: - extra_jvm_args.append(f'-Duser.language={user_locale}') - else: - # C Locale - extra_jvm_args.append(f'-Duser.language=C') - - jpype.startJVM(*args, *extra_jvm_args, classpath=path, convertStrings=True) # noqa - - if class_output_path is not None: - from ai.timefold.jpyinterpreter import InterpreterStartupOptions # noqa - InterpreterStartupOptions.classOutputRootPath = class_output_path - - import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter as CPythonBackedPythonInterpreter - CPythonBackedPythonInterpreter.lookupPythonReferenceIdPythonFunction = GetPythonObjectId() - CPythonBackedPythonInterpreter.lookupPythonReferenceTypePythonFunction = GetPythonObjectType() - CPythonBackedPythonInterpreter.lookupAttributeOnPythonReferencePythonFunction = GetAttributeOnPythonObject() - CPythonBackedPythonInterpreter.loadObjectFromPythonGlobalDict = GetNameFromGlobals() - CPythonBackedPythonInterpreter.lookupPointerForAttributeOnPythonReferencePythonFunction = \ - GetAttributePointerOnPythonObject() - CPythonBackedPythonInterpreter.lookupPointerArrayForAttributeOnPythonReferencePythonFunction = \ - GetAttributePointerArrayOnPythonObject() - CPythonBackedPythonInterpreter.lookupAttributeOnPythonReferenceWithMapPythonFunction = \ - GetAttributeOnPythonObjectWithMap() - CPythonBackedPythonInterpreter.lookupDictOnPythonReferencePythonFunction = GetDictOnPythonObject() - CPythonBackedPythonInterpreter.setAttributeOnPythonReferencePythonFunction = SetAttributeOnPythonObject() - CPythonBackedPythonInterpreter.deleteAttributeOnPythonReferencePythonFunction = DeleteAttributeOnPythonObject() - CPythonBackedPythonInterpreter.callPythonFunction = CallPythonFunction() - CPythonBackedPythonInterpreter.createFunctionFromCodeFunction = CreateFunctionFromCode() - CPythonBackedPythonInterpreter.importModuleFunction = ImportModule() - - -@jpype.JImplements('java.util.function.BiConsumer', deferred=True) -class GetNameFromGlobals: - @jpype.JOverride() - def accept(self, java_globals, name): - from .translator import java_globals_to_python_globals - from .conversions import convert_to_java_python_like_object - from ai.timefold.jpyinterpreter.util import PythonGlobalsBackedMap - - if not isinstance(java_globals, PythonGlobalsBackedMap): - return - - python_globals = java_globals_to_python_globals[java_globals.getPythonGlobalsId()] - try: - python_object = python_globals[name] - java_globals.put(name, convert_to_java_python_like_object(python_object)) - except KeyError: - java_globals.put(name, None) - - -@jpype.JImplements('java.util.function.Function', deferred=True) -class GetPythonObjectId: - @jpype.JOverride() - def apply(self, python_object): - return id(python_object) - - -@jpype.JImplements('java.util.function.Function', deferred=True) -class GetPythonObjectType: - @jpype.JOverride() - def apply(self, python_object): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - return jpype.JProxy(OpaquePythonReference, inst=type(python_object), convert=True) - - -@jpype.JImplements('java.util.function.BiFunction', deferred=True) -class GetAttributeOnPythonObject: - @jpype.JOverride() - def apply(self, python_object, attribute_name): - from .conversions import convert_to_java_python_like_object - if not hasattr(python_object, attribute_name): - return None - out = getattr(python_object, attribute_name) - return convert_to_java_python_like_object(out) - - -@jpype.JImplements('java.util.function.BiFunction', deferred=True) -class GetAttributePointerOnPythonObject: - @jpype.JOverride() - def apply(self, python_object, attribute_name): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - if not hasattr(python_object, attribute_name): - return None - out = getattr(python_object, attribute_name) - return jpype.JProxy(OpaquePythonReference, inst=out, convert=True) - - -@jpype.JImplements('java.util.function.BiFunction', deferred=True) -class GetAttributePointerArrayOnPythonObject: - @jpype.JOverride() - def apply(self, python_object, attribute_name): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - if not hasattr(python_object, attribute_name): - return None - out = getattr(python_object, attribute_name)() - out_array = OpaquePythonReference[len(out)] - - for i in range(len(out)): - out_array[i] = jpype.JProxy(OpaquePythonReference, inst=out[i], convert=True) - - return out_array - - -@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.TriFunction', deferred=True) -class GetAttributeOnPythonObjectWithMap: - @jpype.JOverride() - def apply(self, python_object, attribute_name, instance_map): - from .conversions import convert_to_java_python_like_object - if not hasattr(python_object, attribute_name): - return None - out = getattr(python_object, attribute_name) - return convert_to_java_python_like_object(out, instance_map) - - -@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.QuadConsumer', deferred=True) -class SetAttributeOnPythonObject: - @jpype.JOverride() - def accept(self, python_object, clone_map, attribute_name, value): - from .conversions import unwrap_python_like_object - unwrapped_object = unwrap_python_like_object(value, - clone_map) - try: - setattr(python_object, attribute_name, unwrapped_object) - except: - object.__setattr__(python_object, attribute_name, unwrapped_object) - - -@jpype.JImplements('java.util.function.BiConsumer', deferred=True) -class DeleteAttributeOnPythonObject: - @jpype.JOverride() - def accept(self, python_object, attribute_name): - delattr(python_object, attribute_name) - - -@jpype.JImplements('java.util.function.BiFunction', deferred=True) -class GetDictOnPythonObject: - @jpype.JOverride() - def apply(self, python_object, instance_map): - from java.util import HashMap - from .conversions import convert_to_java_python_like_object - - out = HashMap() - for key in dir(python_object): - out.put(key, convert_to_java_python_like_object(getattr(python_object, key), instance_map)) - - return out - - -@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.TriFunction', deferred=True) -class CallPythonFunction: - @jpype.JOverride() - def apply(self, python_object, var_args_list, keyword_args_map): - from .conversions import unwrap_python_like_object, convert_to_java_python_like_object - actual_vargs = unwrap_python_like_object(var_args_list) - actual_keyword_args = unwrap_python_like_object(keyword_args_map) - if actual_keyword_args is None: - actual_keyword_args = dict() - try: - out = python_object(*actual_vargs, **actual_keyword_args) - return convert_to_java_python_like_object(out) - except Exception as e: - from ai.timefold.jpyinterpreter.types.errors import CPythonException - raise CPythonException(str(e)) - - -@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.QuadFunction', deferred=True) -class CreateFunctionFromCode: - @jpype.JOverride() - def apply(self, code_object, function_globals, closure, name): - from types import FunctionType - from .conversions import unwrap_python_like_object - from .translator import find_globals_dict_for_java_map - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter # noqa - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference, PythonObjectWrapper # noqa - from java.util import HashMap - from jpype import JProxy - - instance_map = HashMap() - python_code = JProxy.unwrap(code_object).wrapped - python_globals_args = find_globals_dict_for_java_map(function_globals) - python_closure = unwrap_python_like_object(closure) - python_name = unwrap_python_like_object(name) - - python_function = FunctionType(code=python_code, - globals=python_globals_args, - name=python_name, - closure=python_closure) - - proxy = JProxy(OpaquePythonReference, inst=python_function, convert=True) - out = PythonObjectWrapper(proxy) - CPythonBackedPythonInterpreter.updateJavaObjectFromPythonObject(out, - proxy, - instance_map) - return out - - - -@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.PentaFunction', deferred=True) -class ImportModule: - @jpype.JOverride() - def apply(self, module_name, globals_map, locals_map, from_list, level): - from .conversions import unwrap_python_like_object, convert_to_java_python_like_object - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter # noqa - python_globals = unwrap_python_like_object(globals_map, None) - python_locals = unwrap_python_like_object(locals_map, None) - python_from_list = unwrap_python_like_object(from_list, None) - return convert_to_java_python_like_object( - __import__(module_name, python_globals, python_locals, python_from_list, level), - CPythonBackedPythonInterpreter.pythonObjectIdToConvertedObjectMap - ) - - -def ensure_init(): - """Start the JVM if it isn't started; does nothing otherwise - - Used by the translator to start the JVM when needed by a method, so - users don't need to start the JVM themselves. - - :return: None - """ - if jpype.isJVMStarted(): # noqa - return - else: - init() - - -def set_class_output_directory(path: pathlib.Path): - ensure_init() - - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = path diff --git a/jpyinterpreter/src/main/python/translator.py b/jpyinterpreter/src/main/python/translator.py deleted file mode 100644 index 1572aef5..00000000 --- a/jpyinterpreter/src/main/python/translator.py +++ /dev/null @@ -1,754 +0,0 @@ -import ctypes -import dis -import inspect -import sys -import abc -from typing import Protocol - -from jpype import JInt, JBoolean, JProxy, JClass, JArray - - -MINIMUM_SUPPORTED_PYTHON_VERSION = (3, 10) -MAXIMUM_SUPPORTED_PYTHON_VERSION = (3, 12) - -global_dict_to_instance = dict() -global_dict_to_key_set = dict() -java_globals_to_python_globals = dict() - -type_to_compiled_java_class = dict() -type_to_annotations = dict() -type_to_java_interfaces = dict() -python_java_type_mappings = list() - -function_interface_pair_to_instance = dict() -function_interface_pair_to_class = dict() - - -def get_file_for_module(module_name): - import pathlib - import sys - - if module_name is None: - return '' - - module = sys.modules[module_name] - if hasattr(module, '__file__'): - file_path = sys.modules[module_name].__file__ - if file_path is not None: - return file_path - - # Do not know file for module; predict file from module name - if module_name == '__main__': - return '' - - path_parts = module_name.split('.') - path_parts[-1] = f'{path_parts[-1]}.py' - return str(pathlib.Path(*path_parts)) - - -def is_python_version_supported(python_version): - python_version_major_minor = python_version[0:2] - return MINIMUM_SUPPORTED_PYTHON_VERSION <= python_version_major_minor <= MAXIMUM_SUPPORTED_PYTHON_VERSION - - -def is_current_python_version_supported(): - return is_python_version_supported(sys.version_info) - - -def check_current_python_version_supported(): - if not is_current_python_version_supported(): - raise NotImplementedError(f'The translator does not support the current Python version ({sys.version}). ' - f'The minimum version currently supported is ' - f'{MINIMUM_SUPPORTED_PYTHON_VERSION[0]}.{MINIMUM_SUPPORTED_PYTHON_VERSION[1]}. ' - f'The maximum version currently supported is ' - f'{MAXIMUM_SUPPORTED_PYTHON_VERSION[0]}.{MAXIMUM_SUPPORTED_PYTHON_VERSION[1]}.') - - -def get_java_type_for_python_type(the_type): - from .annotations import erase_generic_args - from ai.timefold.jpyinterpreter.types import BuiltinTypes - global type_to_compiled_java_class - - if isinstance(the_type, type): - the_type = erase_generic_args(the_type) - if the_type in type_to_compiled_java_class: - return type_to_compiled_java_class[the_type] - else: - try: - return translate_python_class_to_java_class(the_type) - except: - return type_to_compiled_java_class[the_type] - if isinstance(the_type, str): - try: - the_type = erase_generic_args(the_type) - maybe_type = globals()[the_type] - if isinstance(maybe_type, type): - return get_java_type_for_python_type(maybe_type) - return BuiltinTypes.BASE_TYPE - except: - return BuiltinTypes.BASE_TYPE - # return base type, since users could use something like 1 - return BuiltinTypes.BASE_TYPE - - -def get_default_args(func): - signature = inspect.signature(func) - return { - k: v.default - for k, v in signature.parameters.items() - if v.default is not inspect.Parameter.empty - } - - -def generate_proxy_class_for_translated_function(interface_type, translated_function): - from ai.timefold.jpyinterpreter import InterfaceProxyGenerator - return InterfaceProxyGenerator.generateProxyForFunction(interface_type, translated_function) - - -def generate_proxy_class_for_translated_class(interface_type, translated_class): - from ai.timefold.jpyinterpreter import InterfaceProxyGenerator - return InterfaceProxyGenerator.generateProxyForClass(interface_type, translated_class) - - -def copy_constants(constants_iterable): - from .conversions import convert_to_java_python_like_object - from java.util import ArrayList - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter - if constants_iterable is None: - return None - iterable_copy = ArrayList() - for item in constants_iterable: - iterable_copy.add(convert_to_java_python_like_object(item, CPythonBackedPythonInterpreter.pythonObjectIdToConvertedObjectMap)) - return iterable_copy - - -def copy_closure(closure): - from .conversions import convert_to_java_python_like_object - from ai.timefold.jpyinterpreter.types import PythonCell - from ai.timefold.jpyinterpreter.types.collections import PythonLikeTuple - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter - out = PythonLikeTuple() - if closure is None: - return out - else: - for cell in closure: - java_cell = PythonCell() - java_cell.cellValue = convert_to_java_python_like_object(cell.cell_contents, CPythonBackedPythonInterpreter.pythonObjectIdToConvertedObjectMap) - out.add(java_cell) - return out - - -def copy_globals(globals_dict, co_names, python_class): - global global_dict_to_instance - global global_dict_to_key_set - from .conversions import convert_to_java_python_like_object - from ai.timefold.jpyinterpreter.util import PythonGlobalsBackedMap - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter - - globals_dict_key = id(globals_dict) - if globals_dict_key in global_dict_to_instance: - out = global_dict_to_instance[globals_dict_key] - key_set = global_dict_to_key_set[globals_dict_key] - else: - out = PythonGlobalsBackedMap(globals_dict_key) - key_set = set() - global_dict_to_instance[globals_dict_key] = out - global_dict_to_key_set[globals_dict_key] = key_set - java_globals_to_python_globals[globals_dict_key] = globals_dict - - instance_map = CPythonBackedPythonInterpreter.pythonObjectIdToConvertedObjectMap - for key, value in globals_dict.items(): - if key not in key_set and key in co_names: - if python_class is not None: - if isinstance(value, type): - if issubclass(value, python_class): - continue - elif isinstance(value, python_class): - continue - key_set.add(key) - out.put(key, convert_to_java_python_like_object(value, instance_map)) - return out - - -def find_globals_dict_for_java_map(java_globals): - for python_global_id in global_dict_to_instance: - if global_dict_to_instance[python_global_id] == java_globals: - return ctypes.cast(python_global_id, ctypes.py_object).value - - raise ValueError(f'Could not find python globals corresponding to {str(java_globals.toString())}') - - -def get_instructions(python_function): - try: - yield from dis.get_instructions(python_function, show_caches=True) # Python 3.11 and above - except TypeError: # Python 3.10 and below - yield from dis.get_instructions(python_function) - - -# From https://github.com/python/cpython/blob/main/Objects/exception_handling_notes.txt -def parse_varint(iterator): - b = next(iterator) - val = b & 63 - while b&64: - val <<= 6 - b = next(iterator) - val |= b&63 - return val - - -# From https://github.com/python/cpython/blob/main/Objects/exception_handling_notes.txt -def parse_exception_table(code): - iterator = iter(code.co_exceptiontable) - try: - while True: - start = parse_varint(iterator)*2 - length = parse_varint(iterator)*2 - end = start + length - 2 # Present as inclusive, not exclusive - target = parse_varint(iterator)*2 - dl = parse_varint(iterator) - depth = dl >> 1 - lasti = bool(dl&1) - yield start, end, target, depth, lasti - except StopIteration: - return - - -def get_python_exception_table(python_code): - from ai.timefold.jpyinterpreter import PythonExceptionTable, PythonVersion - out = PythonExceptionTable() - - if hasattr(python_code, 'co_exceptiontable'): - python_version = PythonVersion(sys.hexversion) - for start, end, target, depth, lasti in parse_exception_table(python_code): - out.addEntry(python_version, start, end, target, depth, lasti) - - return out - - -def get_function_bytecode_object(python_function, python_class: type = None): - from .annotations import copy_type_annotations - from .conversions import copy_iterable, init_type_to_compiled_java_class, convert_to_java_python_like_object - from java.util import ArrayList - from ai.timefold.jpyinterpreter import PythonBytecodeInstruction, PythonCompiledFunction, PythonVersion # noqa - - init_type_to_compiled_java_class() - - python_compiled_function = PythonCompiledFunction() - instruction_list = ArrayList() - for instruction in get_instructions(python_function): - java_instruction = ( - PythonBytecodeInstruction - .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).withArgRepr(instruction.argrepr) - if instruction.starts_line: - java_instruction = java_instruction.startsLine(instruction.starts_line) - - instruction_list.add(java_instruction) - - python_compiled_function.module = python_function.__module__ - python_compiled_function.moduleFilePath = get_file_for_module(python_function.__module__) - python_compiled_function.qualifiedName = python_function.__qualname__ - python_compiled_function.instructionList = instruction_list - python_compiled_function.co_exceptiontable = get_python_exception_table(python_function.__code__) - python_compiled_function.co_names = copy_iterable(python_function.__code__.co_names) - python_compiled_function.co_varnames = copy_variable_names(python_function.__code__.co_varnames) - python_compiled_function.co_cellvars = copy_variable_names(python_function.__code__.co_cellvars) - python_compiled_function.co_freevars = copy_variable_names(python_function.__code__.co_freevars) - python_compiled_function.co_constants = copy_constants(python_function.__code__.co_consts) - python_compiled_function.co_argcount = python_function.__code__.co_argcount - python_compiled_function.co_kwonlyargcount = python_function.__code__.co_kwonlyargcount - python_compiled_function.closure = copy_closure(python_function.__closure__) - python_compiled_function.globalsMap = copy_globals(python_function.__globals__, python_function.__code__.co_names, - python_class) - python_compiled_function.typeAnnotations = copy_type_annotations(python_function, - get_default_args(python_function), - inspect.getfullargspec(python_function).varargs, - inspect.getfullargspec(python_function).varkw) - python_compiled_function.defaultPositionalArguments = convert_to_java_python_like_object( - python_function.__defaults__ if python_function.__defaults__ else tuple()) - python_compiled_function.defaultKeywordArguments = convert_to_java_python_like_object( - python_function.__kwdefaults__ if python_function.__kwdefaults__ else dict()) - python_compiled_function.supportExtraPositionalArgs = inspect.getfullargspec(python_function).varargs is not None - python_compiled_function.supportExtraKeywordsArgs = inspect.getfullargspec(python_function).varkw is not None - python_compiled_function.pythonVersion = PythonVersion(sys.hexversion) - return python_compiled_function - - -def get_static_function_bytecode_object(the_class, python_function): - return get_function_bytecode_object(python_function.__get__(the_class), python_class=the_class) - - -def copy_variable_names(iterable): - from java.util import ArrayList - from ai.timefold.jpyinterpreter.util import JavaIdentifierUtils - - if iterable is None: - return None - iterable_copy = ArrayList() - for item in iterable: - iterable_copy.add(JavaIdentifierUtils.sanitizeFieldName(item)) - return iterable_copy - - -def get_code_bytecode_object(python_code): - from .conversions import copy_iterable, init_type_to_compiled_java_class, convert_to_java_python_like_object - from java.util import ArrayList, HashMap - from ai.timefold.jpyinterpreter import PythonBytecodeInstruction, PythonCompiledFunction, PythonVersion # noqa - - init_type_to_compiled_java_class() - - python_compiled_function = PythonCompiledFunction() - instruction_list = ArrayList() - for instruction in get_instructions(python_code): - java_instruction = ( - PythonBytecodeInstruction - .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).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__' - python_compiled_function.qualifiedName = '__code__' - python_compiled_function.instructionList = instruction_list - python_compiled_function.co_exceptiontable = get_python_exception_table(python_code) - python_compiled_function.co_names = copy_iterable(python_code.co_names) - python_compiled_function.co_varnames = copy_variable_names(python_code.co_varnames) - python_compiled_function.co_cellvars = copy_variable_names(python_code.co_cellvars) - python_compiled_function.co_freevars = copy_variable_names(python_code.co_freevars) - python_compiled_function.co_constants = copy_constants(python_code.co_consts) - python_compiled_function.co_argcount = python_code.co_argcount - python_compiled_function.co_kwonlyargcount = python_code.co_kwonlyargcount - python_compiled_function.closure = copy_closure(None) - python_compiled_function.globalsMap = HashMap() - python_compiled_function.typeAnnotations = HashMap() - python_compiled_function.defaultPositionalArguments = convert_to_java_python_like_object(tuple()) - python_compiled_function.defaultKeywordArguments = convert_to_java_python_like_object(dict()) - python_compiled_function.typeAnnotations = HashMap() - python_compiled_function.supportExtraPositionalArgs = False - python_compiled_function.supportExtraKeywordsArgs = False - python_compiled_function.pythonVersion = PythonVersion(sys.hexversion) - return python_compiled_function - - -def translate_python_bytecode_to_java_bytecode(python_function, java_function_type, *type_args): - from .conversions import copy_iterable - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - if (python_function, java_function_type, type_args) in function_interface_pair_to_instance: - return function_interface_pair_to_instance[(python_function, java_function_type, type_args)] - - python_compiled_function = get_function_bytecode_object(python_function) - - if len(type_args) == 0: - out = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(python_compiled_function, - java_function_type) - function_interface_pair_to_instance[(python_function, java_function_type, type_args)] = out - return out - else: - out = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(python_compiled_function, - java_function_type, - copy_iterable(type_args)) - function_interface_pair_to_instance[(python_function, java_function_type, type_args)] = out - return out - - -def _force_translate_python_bytecode_to_generator_java_bytecode(python_function, java_function_type): - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - if (python_function, java_function_type) in function_interface_pair_to_instance: - return function_interface_pair_to_instance[(python_function, java_function_type)] - - python_compiled_function = get_function_bytecode_object(python_function) - - out = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(python_compiled_function, - java_function_type) - function_interface_pair_to_instance[(python_function, java_function_type)] = out - return out - - -def translate_python_code_to_java_class(python_function, java_function_type, *type_args): - from .conversions import copy_iterable - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - if (python_function, java_function_type, type_args) in function_interface_pair_to_class: - return function_interface_pair_to_class[(python_function, java_function_type, type_args)] - - python_compiled_function = get_code_bytecode_object(python_function) - - if len(type_args) == 0: - out = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(python_compiled_function, - java_function_type) - function_interface_pair_to_class[(python_function, java_function_type, type_args)] = out - return out - else: - out = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(python_compiled_function, - java_function_type, - copy_iterable(type_args)) - function_interface_pair_to_class[(python_function, java_function_type, type_args)] = out - return out - - -def translate_python_code_to_python_wrapper_class(python_function): - from .conversions import CodeWrapper - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference # noqa - if (python_function,) in function_interface_pair_to_class: - return function_interface_pair_to_class[(python_function,)] - - python_compiled_function = get_code_bytecode_object(python_function) - out = PythonBytecodeToJavaBytecodeTranslator.\ - translatePythonBytecodeToPythonWrapperClass(python_compiled_function, JProxy(OpaquePythonReference, - CodeWrapper(python_function), - convert=True)) - function_interface_pair_to_class[(python_function,)] = out - return out - - -def wrap_untyped_java_function(java_function): - from .conversions import convert_to_java_python_like_object, unwrap_python_like_object - def wrapped_function(*args, **kwargs): - from java.util import ArrayList, HashMap - - instance_map = HashMap() - java_args = ArrayList(len(args)) - java_kwargs = HashMap() - - for arg in args: - java_args.add(convert_to_java_python_like_object(arg, instance_map)) - - for key, value in kwargs: - java_kwargs.put(convert_to_java_python_like_object(key, instance_map), - convert_to_java_python_like_object(value, instance_map)) - - out = None - error = None - - try: - out = unwrap_python_like_object(getattr(java_function, '$call')(java_args, java_kwargs, None)) - except Exception as e: - error = unwrap_python_like_object(e) - - if error is not None: - raise error - - return out - - - return wrapped_function - - -def wrap_typed_java_function(java_function): - def wrapped_function(*args): - from .conversions import convert_to_java_python_like_object, unwrap_python_like_object - from java.util import HashMap - - instance_map = HashMap() - java_args = [convert_to_java_python_like_object(arg, instance_map) for arg in args] - - out = None - error = None - - try: - out = unwrap_python_like_object(java_function.invoke(*java_args)) - except Exception as e: - error = unwrap_python_like_object(e) - - if error is not None: - raise error - - return out - - 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 = try_or_reraise(lambda: translate_python_bytecode_to_java_bytecode(python_function, PythonLikeFunction)) - return wrap_untyped_java_function(java_function) - - -def as_typed_java(python_function): - from ai.timefold.jpyinterpreter import PythonClassTranslator - 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 = 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 = try_or_reraise(lambda: _force_translate_python_bytecode_to_generator_java_bytecode(python_function, - PythonLikeFunction)) - return wrap_untyped_java_function(java_function) - - -class MethodTypeHelper: - @classmethod - def class_method_type(cls): - pass - - @staticmethod - def static_method_type(): - pass - - -__CLASS_METHOD_TYPE = type(MethodTypeHelper.__dict__['class_method_type']) -__STATIC_METHOD_TYPE = type(MethodTypeHelper.__dict__['static_method_type']) - - -def force_update_type(python_type, java_type): - global type_to_compiled_java_class - type_to_compiled_java_class[python_type] = java_type - - -# TODO: Remove me when minimum Python version is 3.11 -def get_members_static(object, predicate): - try: - return inspect.getmembers_static(object, predicate) - except AttributeError: - return _getmembers(object, predicate, type.__getattribute__) - - -# TODO: Remove me when minimum Python version is 3.11 -def _getmembers(object, predicate, getter): - import types - results = [] - processed = set() - names = dir(object) - if inspect.isclass(object): - mro = (object,) + inspect.getmro(object) - # add any DynamicClassAttributes to the list of names if object is a class; - # this may result in duplicate entries if, for example, a virtual - # attribute with the same name as a DynamicClassAttribute exists - try: - for base in object.__bases__: - for k, v in base.__dict__.items(): - if isinstance(v, types.DynamicClassAttribute): - names.append(k) - except AttributeError: - pass - else: - mro = () - for key in names: - # First try to get the value via getattr. Some descriptors don't - # like calling their __get__ (see bug #1785), so fall back to - # looking in the __dict__. - try: - value = getter(object, key) - # handle the duplicate key - if key in processed: - raise AttributeError - except AttributeError: - for base in mro: - if key in base.__dict__: - value = base.__dict__[key] - break - else: - # could be a (currently) missing slot member, or a buggy - # __dir__; discard and move on - continue - if not predicate or predicate(value): - results.append((key, value)) - processed.add(key) - results.sort(key=lambda pair: pair[0]) - return results - - -def translate_python_class_to_java_class(python_class): - import collections.abc as collections_abc - from .annotations import erase_generic_args, convert_java_annotation, copy_type_annotations - from .conversions import ( - init_type_to_compiled_java_class, is_banned_module, is_c_native, convert_to_java_python_like_object - ) - from java.lang import Class as JavaClass - from java.util import ArrayList, HashMap, HashSet - from ai.timefold.jpyinterpreter import AnnotationMetadata, PythonCompiledClass, PythonClassTranslator, CPythonBackedPythonInterpreter # noqa - from ai.timefold.jpyinterpreter.types import BuiltinTypes - from ai.timefold.jpyinterpreter.types.wrappers import JavaObjectWrapper, OpaquePythonReference, CPythonType # noqa - - global type_to_compiled_java_class - - init_type_to_compiled_java_class() - - raw_type = erase_generic_args(python_class) - if raw_type in type_to_compiled_java_class: - return type_to_compiled_java_class[raw_type] - - if Protocol in python_class.__bases__: - python_class_java_type = BuiltinTypes.BASE_TYPE - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - if hasattr(python_class, '__module__') and python_class.__module__ is not None: - if python_class.__module__ == collections_abc.Collection.__module__: - python_class_java_type = BuiltinTypes.BASE_TYPE - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - if is_banned_module(python_class.__module__): - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - if isinstance(python_class, JArray): - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - if isinstance(python_class, (JClass, JavaClass)): - try: - out = JavaObjectWrapper.getPythonTypeForClass(python_class) - type_to_compiled_java_class[python_class] = out - return out - except TypeError: - print(f'Bad type: {type(python_class)}, from {python_class}') - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - if is_c_native(python_class): - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - prepared_class_info = PythonClassTranslator.getPreparedClassInfo(python_class.__name__, - python_class.__module__, - python_class.__qualname__) - type_to_compiled_java_class[python_class] = prepared_class_info.type() - methods = [] - for method_name in python_class.__dict__: - method = inspect.getattr_static(python_class, method_name) - if inspect.isfunction(method) or \ - isinstance(method, __STATIC_METHOD_TYPE) or \ - isinstance(method, __CLASS_METHOD_TYPE): - methods.append((method_name, method)) - - all_static_attributes = get_members_static(python_class, - predicate=lambda member: not (inspect.isfunction(member) - or isinstance(member, - __STATIC_METHOD_TYPE) - or isinstance(member, __CLASS_METHOD_TYPE) - )) - static_attributes = [attribute for attribute in all_static_attributes if attribute[0] in python_class.__dict__] - static_methods = [method for method in methods if isinstance(method[1], __STATIC_METHOD_TYPE)] - class_methods = [method for method in methods if isinstance(method[1], __CLASS_METHOD_TYPE)] - instance_methods = [method for method in methods if method not in static_methods and method not in class_methods] - - superclass_list = ArrayList() - for superclass in python_class.__bases__: - superclass = erase_generic_args(superclass) - if superclass in type_to_compiled_java_class: - if isinstance(type_to_compiled_java_class[superclass], CPythonType): - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - superclass_list.add(type_to_compiled_java_class[superclass]) - else: - try: - superclass_list.add(translate_python_class_to_java_class(superclass)) - if isinstance(type_to_compiled_java_class[superclass], CPythonType): - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - except Exception: - superclass_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=superclass, convert=True)) - type_to_compiled_java_class[superclass] = superclass_java_type - python_class_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, convert=True)) - type_to_compiled_java_class[python_class] = python_class_java_type - return python_class_java_type - - static_method_map = HashMap() - for method in static_methods: - static_method_map.put(method[0], get_static_function_bytecode_object(python_class, method[1])) - - class_method_map = HashMap() - for method in class_methods: - class_method_map.put(method[0], get_static_function_bytecode_object(python_class, method[1])) - - instance_method_map = HashMap() - for method in instance_methods: - instance_method_map.put(method[0], get_function_bytecode_object(method[1], python_class=python_class)) - - static_attributes_map = HashMap() - static_attributes_to_class_instance_map = HashMap() - static_attribute_descriptor_names = HashSet() - static_attribute_descriptor_names.add('__class__') - static_attribute_descriptor_names.add('__module__') - - for attribute in static_attributes: - attribute_type = type(attribute[1]) - if issubclass(attribute_type, python_class): - static_attributes_to_class_instance_map.put(attribute[0], - JProxy(OpaquePythonReference, - inst=attribute[1], convert=True)) - else: - if attribute_type not in type_to_compiled_java_class: - try: - translate_python_class_to_java_class(attribute_type) - except: - superclass_java_type = CPythonType.getType(JProxy(OpaquePythonReference, inst=attribute_type, convert=True)) - type_to_compiled_java_class[attribute_type] = superclass_java_type - - static_attributes_map.put(attribute[0], convert_to_java_python_like_object(attribute[1])) - - for attribute in all_static_attributes: - attribute_type = type(attribute[1]) - if (hasattr(attribute_type, '__get__') or hasattr(attribute_type, '__set__') or - hasattr(attribute[1], '__get__') or hasattr(attribute[1], '__set__')): - static_attribute_descriptor_names.add(attribute[0]) - - python_compiled_class = PythonCompiledClass() - python_compiled_class.annotations = ArrayList() - python_compiled_class.javaInterfaces = ArrayList() - python_compiled_class.pythonJavaTypeMappings = ArrayList() - - for annotation in type_to_annotations.get(python_class, []): - python_compiled_class.annotations.add(convert_java_annotation(annotation)) - - for java_interface in type_to_java_interfaces.get(python_class, []): - if isinstance(java_interface, str): - java_interface = JClass(java_interface) - - python_compiled_class.javaInterfaces.add(java_interface) - - for python_java_type_mapping in python_java_type_mappings: - python_compiled_class.pythonJavaTypeMappings.add(python_java_type_mapping) - - python_compiled_class.binaryType = CPythonType.getType(JProxy(OpaquePythonReference, inst=python_class, - convert=True)) - python_compiled_class.module = python_class.__module__ - python_compiled_class.moduleFilePath = get_file_for_module(python_class.__module__) - python_compiled_class.qualifiedName = python_class.__qualname__ - python_compiled_class.className = python_class.__name__ - python_compiled_class.typeAnnotations = copy_type_annotations(python_class, - dict(), - None, - None) - python_compiled_class.superclassList = superclass_list - python_compiled_class.instanceFunctionNameToPythonBytecode = instance_method_map - python_compiled_class.staticFunctionNameToPythonBytecode = static_method_map - python_compiled_class.classFunctionNameToPythonBytecode = class_method_map - python_compiled_class.staticAttributeNameToObject = static_attributes_map - python_compiled_class.staticAttributeNameToClassInstance = static_attributes_to_class_instance_map - python_compiled_class.staticAttributeDescriptorNames = static_attribute_descriptor_names - - out = PythonClassTranslator.translatePythonClass(python_compiled_class, prepared_class_info) - PythonClassTranslator.setSelfStaticInstances(python_compiled_class, out.getJavaClass(), out, - CPythonBackedPythonInterpreter.pythonObjectIdToConvertedObjectMap) - return out diff --git a/jpyinterpreter/src/main/resources/.gitkeep b/jpyinterpreter/src/main/resources/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/MyObject.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/MyObject.java deleted file mode 100644 index d987c358..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/MyObject.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; - -public class MyObject { - public String name; - public PythonLikeFunction attributeFunction; - - public String concatToName(String other) { - return name + other; - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonClassTranslatorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonClassTranslatorTest.java deleted file mode 100644 index 6fd1b237..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonClassTranslatorTest.java +++ /dev/null @@ -1,339 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.ToIntFunction; - -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -public class PythonClassTranslatorTest { - - @Test - public void testPythonClassTranslation() throws ClassNotFoundException, NoSuchMethodException { - PythonCompiledClass compiledClass = new PythonCompiledClass(); - - PythonCompiledFunction initFunction = PythonFunctionBuilder.newFunction("self", "age") - .loadParameter("age") - .loadParameter("self") - .storeAttribute("age") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction ageFunction = PythonFunctionBuilder.newFunction("self") - .loadParameter("self") - .getAttribute("age") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction helloWorldFunction = PythonFunctionBuilder.newFunction() - .loadConstant("hello world") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - compiledClass.annotations = Collections.emptyList(); - compiledClass.javaInterfaces = Collections.emptyList(); - compiledClass.pythonJavaTypeMappings = List.of(); - compiledClass.className = "MyClass"; - compiledClass.superclassList = List.of(BuiltinTypes.BASE_TYPE); - compiledClass.staticAttributeNameToObject = Map.of("type_variable", new PythonString("type_value")); - compiledClass.staticAttributeNameToClassInstance = Map.of(); - compiledClass.staticAttributeDescriptorNames = Set.of(); - compiledClass.typeAnnotations = Map.of("age", TypeHint.withoutAnnotations(BuiltinTypes.INT_TYPE)); - compiledClass.instanceFunctionNameToPythonBytecode = Map.of("__init__", initFunction, - "get_age", ageFunction); - compiledClass.staticFunctionNameToPythonBytecode = Map.of("hello_world", helloWorldFunction); - compiledClass.classFunctionNameToPythonBytecode = Map.of(); - - PythonLikeType classType = PythonClassTranslator.translatePythonClass(compiledClass); - Class generatedClass = BuiltinTypes.asmClassLoader.loadClass( - classType.getJavaTypeInternalName().replace('/', '.')); - - assertThat(generatedClass).hasPublicFields(PythonClassTranslator.getJavaMethodName("get_age"), - PythonClassTranslator.getJavaFieldName("age")); - assertThat(generatedClass).hasPublicMethods( - PythonClassTranslator.getJavaMethodName("__init__"), - PythonClassTranslator.getJavaMethodName("get_age")); - assertThat(generatedClass.getMethod(PythonClassTranslator.getJavaMethodName("get_age")).getParameterTypes()).isEmpty(); - - PythonLikeObject classObject = classType.$call(List.of(PythonInteger.valueOf(10)), Map.of(), null); - PythonLikeFunction getAgeFunction = - (PythonLikeFunction) classObject.$method$__getattribute__(PythonString.valueOf("get_age")); - assertThat(getAgeFunction.$call(List.of(), Map.of(), null)).isEqualTo(PythonInteger.valueOf(10)); - } - - @Test - public void testPythonClassComparable() throws ClassNotFoundException { - PythonCompiledFunction initFunction = PythonFunctionBuilder.newFunction("self", "key") - .loadParameter("key") - .loadParameter("self") - .storeAttribute("key") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function getCompareFunction = - compareOp -> PythonFunctionBuilder.newFunction("self", "other") - .loadParameter("self") - .getAttribute("key") - .loadParameter("other") - .getAttribute("key") - .compare(compareOp) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - for (CompareOp compareOp : List.of(CompareOp.LESS_THAN, CompareOp.GREATER_THAN, - CompareOp.LESS_THAN_OR_EQUALS, CompareOp.GREATER_THAN_OR_EQUALS)) { - PythonCompiledFunction comparisonFunction = getCompareFunction.apply(compareOp); - - PythonCompiledClass compiledClass = new PythonCompiledClass(); - compiledClass.annotations = Collections.emptyList(); - compiledClass.javaInterfaces = Collections.emptyList(); - compiledClass.pythonJavaTypeMappings = List.of(); - compiledClass.className = "MyClass"; - compiledClass.superclassList = List.of(BuiltinTypes.BASE_TYPE); - compiledClass.staticAttributeNameToObject = Map.of(); - compiledClass.staticAttributeNameToClassInstance = Map.of(); - compiledClass.staticAttributeDescriptorNames = Set.of(); - compiledClass.typeAnnotations = Map.of("key", TypeHint.withoutAnnotations(BuiltinTypes.INT_TYPE)); - compiledClass.instanceFunctionNameToPythonBytecode = Map.of("__init__", initFunction, - compareOp.dunderMethod, comparisonFunction); - compiledClass.staticFunctionNameToPythonBytecode = Map.of(); - compiledClass.classFunctionNameToPythonBytecode = Map.of(); - - PythonLikeType classType = PythonClassTranslator.translatePythonClass(compiledClass); - Class generatedClass = BuiltinTypes.asmClassLoader.loadClass( - classType.getJavaTypeInternalName().replace('/', '.')); - - assertThat(Comparable.class.isAssignableFrom(generatedClass)).isTrue(); - assertThat(generatedClass).hasPublicFields(PythonClassTranslator.getJavaFieldName("key")); - assertThat(generatedClass).hasPublicMethods( - PythonClassTranslator.getJavaMethodName("__init__"), - "compareTo"); - - Comparable object1 = - (Comparable) classType.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null); - Comparable object2 = - (Comparable) classType.$call(List.of(PythonInteger.valueOf(2)), Map.of(), null); - Comparable object1b = - (Comparable) classType.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null); - - assertThat(object1.compareTo(object2)) - .withFailMessage(compareOp.name() + " a < b incorrect") - .isLessThan(0); - assertThat(object2.compareTo(object1)) - .withFailMessage(compareOp.name() + " a > b incorrect") - .isGreaterThan(0); - assertThat(object1.compareTo(object1)) - .withFailMessage(compareOp.name() + " a == a incorrect") - .isEqualTo(0); - assertThat(object1.compareTo(object1b)) - .withFailMessage(compareOp.name() + " a == b incorrect") - .isEqualTo(0); - } - } - - @Test - public void testPythonClassEqualsAndHashCode() throws ClassNotFoundException { - PythonCompiledFunction initFunction = PythonFunctionBuilder.newFunction("self", "key") - .loadParameter("key") - .loadParameter("self") - .storeAttribute("key") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction equalsFunction = PythonFunctionBuilder.newFunction("self", "other") - .loadParameter("self") - .getAttribute("key") - .loadParameter("other") - .getAttribute("key") - .compare(CompareOp.EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction hashFunction = PythonFunctionBuilder.newFunction("self") - .loadParameter("self") - .getAttribute("key") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledClass compiledClass = new PythonCompiledClass(); - compiledClass.annotations = Collections.emptyList(); - compiledClass.javaInterfaces = Collections.emptyList(); - compiledClass.pythonJavaTypeMappings = List.of(); - compiledClass.className = "MyClass"; - compiledClass.superclassList = List.of(BuiltinTypes.BASE_TYPE); - compiledClass.staticAttributeNameToObject = Map.of(); - compiledClass.staticAttributeNameToClassInstance = Map.of(); - compiledClass.staticAttributeDescriptorNames = Set.of(); - compiledClass.typeAnnotations = Map.of("key", TypeHint.withoutAnnotations(BuiltinTypes.INT_TYPE)); - compiledClass.instanceFunctionNameToPythonBytecode = Map.of("__init__", initFunction, - "__eq__", equalsFunction, - "__hash__", hashFunction); - compiledClass.staticFunctionNameToPythonBytecode = Map.of(); - compiledClass.classFunctionNameToPythonBytecode = Map.of(); - - PythonLikeType classType = PythonClassTranslator.translatePythonClass(compiledClass); - Class generatedClass = BuiltinTypes.asmClassLoader.loadClass( - classType.getJavaTypeInternalName().replace('/', '.')); - - assertThat(generatedClass).hasPublicFields(PythonClassTranslator.getJavaFieldName("key")); - assertThat(generatedClass).hasPublicMethods( - PythonClassTranslator.getJavaMethodName("__init__"), - "equals"); - - Object object1a = classType.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null); - Object object1b = classType.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null); - Object object2 = classType.$call(List.of(PythonInteger.valueOf(2)), Map.of(), null); - Object object3 = classType.$call(List.of(PythonInteger.valueOf(Long.MAX_VALUE)), Map.of(), null); - - assertThat(object1a.equals(object2)) - .isFalse(); - assertThat(object2.equals(object1a)) - .isFalse(); - assertThat(object1a.equals(object1b)) - .isTrue(); - assertThat(object1b.equals(object1a)) - .isTrue(); - assertThat(object1a.equals(object1a)) - .isTrue(); - - assertThat(object1a.hashCode()) - .isEqualTo(PythonInteger.valueOf(1).hashCode()); - assertThat(object1b.hashCode()) - .isEqualTo(PythonInteger.valueOf(1).hashCode()); - assertThat(object2.hashCode()) - .isEqualTo(PythonInteger.valueOf(2).hashCode()); - assertThat(object3.hashCode()) - .isEqualTo(PythonInteger.valueOf(Long.MAX_VALUE).hashCode()); - } - - @Test - public void testPythonClassSimpleInterface() throws ClassNotFoundException { - PythonCompiledFunction initFunction = PythonFunctionBuilder.newFunction("self", "value") - .loadParameter("value") - .loadParameter("self") - .storeAttribute("value") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction applyAsInt = PythonFunctionBuilder.newFunction("self", "value") - .loadParameter("self") - .getAttribute("value") - .loadParameter("value") - .op(DunderOpDescriptor.BINARY_ADD) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledClass compiledClass = new PythonCompiledClass(); - compiledClass.annotations = Collections.emptyList(); - compiledClass.javaInterfaces = List.of(ToIntFunction.class); - compiledClass.pythonJavaTypeMappings = List.of(); - compiledClass.className = "MyClass"; - compiledClass.superclassList = List.of(BuiltinTypes.BASE_TYPE); - compiledClass.staticAttributeNameToObject = Map.of(); - compiledClass.staticAttributeNameToClassInstance = Map.of(); - compiledClass.staticAttributeDescriptorNames = Set.of(); - compiledClass.typeAnnotations = Map.of("key", TypeHint.withoutAnnotations(BuiltinTypes.INT_TYPE)); - compiledClass.instanceFunctionNameToPythonBytecode = Map.of("__init__", initFunction, - "applyAsInt", applyAsInt); - compiledClass.staticFunctionNameToPythonBytecode = Map.of(); - compiledClass.classFunctionNameToPythonBytecode = Map.of(); - - PythonLikeType classType = PythonClassTranslator.translatePythonClass(compiledClass); - Class generatedClass = BuiltinTypes.asmClassLoader.loadClass( - classType.getJavaTypeInternalName().replace('/', '.')); - - assertThat(generatedClass).hasPublicFields(PythonClassTranslator.getJavaFieldName("value")); - assertThat(generatedClass).hasPublicMethods( - PythonClassTranslator.getJavaMethodName("__init__"), - "applyAsInt"); - assertThat(generatedClass).isAssignableTo(ToIntFunction.class); - - var object1 = (ToIntFunction) classType.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null); - var object2 = (ToIntFunction) classType.$call(List.of(PythonInteger.valueOf(2)), Map.of(), null); - var object3 = (ToIntFunction) classType.$call(List.of(PythonInteger.valueOf(3)), Map.of(), null); - - assertThat(object1.applyAsInt(PythonInteger.valueOf(1))).isEqualTo(2); - assertThat(object2.applyAsInt(PythonInteger.valueOf(1))).isEqualTo(3); - assertThat(object3.applyAsInt(PythonInteger.valueOf(1))).isEqualTo(4); - } - - public interface ComplexInterface { - int STATIC_FIELD = 10; - - static int staticMethod() { - return STATIC_FIELD; - } - - default void defaultMethod() { - } - - int overloadedMethod(); - - int overloadedMethod(int value); - } - - @Test - public void testPythonClassComplexInterface() throws ClassNotFoundException { - PythonCompiledFunction initFunction = PythonFunctionBuilder.newFunction("self") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction overloadedMethod = PythonFunctionBuilder.newFunction("self", "value") - .loadParameter("value") - .loadConstant(1) - .op(DunderOpDescriptor.BINARY_ADD) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - overloadedMethod.defaultPositionalArguments = PythonLikeTuple.fromItems(PythonInteger.ZERO); - - PythonCompiledClass compiledClass = new PythonCompiledClass(); - compiledClass.annotations = Collections.emptyList(); - compiledClass.javaInterfaces = List.of(ComplexInterface.class); - compiledClass.pythonJavaTypeMappings = List.of(); - compiledClass.className = "MyClass"; - compiledClass.superclassList = List.of(BuiltinTypes.BASE_TYPE); - compiledClass.staticAttributeNameToObject = Map.of(); - compiledClass.staticAttributeNameToClassInstance = Map.of(); - compiledClass.staticAttributeDescriptorNames = Set.of(); - compiledClass.typeAnnotations = Map.of("key", TypeHint.withoutAnnotations(BuiltinTypes.INT_TYPE)); - compiledClass.instanceFunctionNameToPythonBytecode = Map.of("__init__", initFunction, - "overloadedMethod", overloadedMethod); - compiledClass.staticFunctionNameToPythonBytecode = Map.of(); - compiledClass.classFunctionNameToPythonBytecode = Map.of(); - - PythonLikeType classType = PythonClassTranslator.translatePythonClass(compiledClass); - Class generatedClass = BuiltinTypes.asmClassLoader.loadClass( - classType.getJavaTypeInternalName().replace('/', '.')); - - assertThat(generatedClass).hasPublicMethods( - PythonClassTranslator.getJavaMethodName("__init__"), - "overloadedMethod"); - assertThat(generatedClass).isAssignableTo(ComplexInterface.class); - - var instance = (ComplexInterface) classType.$call(List.of(), Map.of(), null); - - assertThat(instance.overloadedMethod()).isEqualTo(1); - assertThat(instance.overloadedMethod(1)).isEqualTo(2); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslatorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslatorTest.java deleted file mode 100644 index d2969f10..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonGeneratorTranslatorTest.java +++ /dev/null @@ -1,390 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.List; -import java.util.function.Function; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ExceptionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.GeneratorOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonGenerator; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.errors.AttributeError; -import ai.timefold.jpyinterpreter.types.errors.PythonAssertionError; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; -import ai.timefold.jpyinterpreter.types.errors.ValueError; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; -import ai.timefold.jpyinterpreter.util.function.TriFunction; - -import org.junit.jupiter.api.Test; - -public class PythonGeneratorTranslatorTest { - - @Test - public void testSimpleGenerator() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("value") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("value") - .op(GeneratorOpDescriptor.YIELD_VALUE) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Function.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.apply(1); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(() -> generator.next()).isInstanceOf(StopIteration.class); - } - - @Test - public void testMultipleYieldsGenerator() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("value1", "value2", "value3") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("value1") - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadParameter("value2") - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadParameter("value3") - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - TriFunction generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, TriFunction.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.apply(1, 2, 3); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(2)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(3)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(() -> generator.next()).isInstanceOf(StopIteration.class); - } - - @Test - public void testGeneratorWithLoop() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction() - .op(GeneratorOpDescriptor.GEN_START) - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.GET_ITER) - .loop(builder -> { - builder.op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP); - }) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Supplier.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.get(); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(2)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(3)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(() -> generator.next()).isInstanceOf(StopIteration.class); - } - - @Test - public void testGeneratorWithTryExcept() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction() - .op(GeneratorOpDescriptor.GEN_START) - .tryCode(tryBuilder -> { - tryBuilder.loadConstant(1) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }, true).except(PythonAssertionError.ASSERTION_ERROR_TYPE, exceptBuilder -> { - exceptBuilder.loadConstant(2) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP); - }, false) - .andFinally(finallyBuilder -> { - finallyBuilder.loadConstant(3) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP); - }, false) - .tryEnd() - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Supplier.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.get(); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(2)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(3)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(() -> generator.next()).isInstanceOf(StopIteration.class); - } - - @Test - public void testSendingValues() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("value1") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("value1") - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Function.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.apply(1); - - assertThat(generator.send(PythonInteger.valueOf(0))).isEqualTo(PythonInteger.valueOf(1)); // first sent value is ignored - assertThat(generator.send(PythonInteger.valueOf(1))).isEqualTo(PythonInteger.valueOf(1)); - assertThat(generator.send(PythonInteger.valueOf(2))).isEqualTo(PythonInteger.valueOf(2)); - assertThatCode(() -> generator.send(PythonInteger.valueOf(3))).isInstanceOf(StopIteration.class) - .matches(error -> ((StopIteration) error).getValue().equals(PythonInteger.valueOf(3))); - } - - @Test - public void testThrowingValues() { - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction() - .op(GeneratorOpDescriptor.GEN_START) - .tryCode(tryBuilder -> { - tryBuilder - .loadConstant(false) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .except(ValueError.VALUE_ERROR_TYPE, exceptBuilder -> { - exceptBuilder.loadConstant(true) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .tryEnd() - .build(); - - Supplier generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Supplier.class); - PythonGenerator generator = (PythonGenerator) generatorCreator.get(); - - assertThat(generator.next()).isEqualTo(PythonBoolean.FALSE); - assertThat(generator.throwValue(new ValueError())).isEqualTo(PythonBoolean.TRUE); - assertThatCode(() -> generator.next()).isInstanceOf(StopIteration.class); - } - - @Test - public void testSimpleYieldFromGenerator() { - PythonCompiledFunction subgeneratorFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(2) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(3) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("subgenerator") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("subgenerator") - .op(GeneratorOpDescriptor.GET_YIELD_FROM_ITER) - .loadConstant(null) - .op(GeneratorOpDescriptor.YIELD_FROM) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier subgeneratorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(subgeneratorFunction, Supplier.class); - - Function generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Function.class); - - PythonGenerator generator = (PythonGenerator) generatorCreator.apply(subgeneratorCreator.get()); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(2)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(3)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(generator::next).isInstanceOf(StopIteration.class) - .matches(stopIteration -> ((StopIteration) stopIteration).getValue().equals(PythonInteger.valueOf(3))); - - generator = (PythonGenerator) generatorCreator.apply(new PythonLikeList<>(List.of(PythonInteger.valueOf(1), - PythonInteger.valueOf(2), - PythonInteger.valueOf(3)))); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(2)); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(3)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(generator::next).isInstanceOf(StopIteration.class) - .matches(stopIteration -> ((StopIteration) stopIteration).getValue().equals(PythonInteger.valueOf(3))); - - generator = (PythonGenerator) generatorCreator.apply(new PythonLikeList<>()); - assertThat(generator.hasNext()).isFalse(); - assertThatCode(generator::next).isInstanceOf(StopIteration.class) - .matches(stopIteration -> ((StopIteration) stopIteration).getValue().equals(PythonNone.INSTANCE)); - } - - @Test - public void testSendYieldFromGenerator() { - PythonCompiledFunction subgeneratorFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .loadConstant(2) - .op(DunderOpDescriptor.BINARY_MULTIPLY) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .loadConstant(3) - .op(DunderOpDescriptor.BINARY_MULTIPLY) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("subgenerator") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("subgenerator") - .op(GeneratorOpDescriptor.GET_YIELD_FROM_ITER) - .loadConstant(null) - .op(GeneratorOpDescriptor.YIELD_FROM) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier subgeneratorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(subgeneratorFunction, Supplier.class); - - Function generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Function.class); - - PythonGenerator generator = (PythonGenerator) generatorCreator.apply(subgeneratorCreator.get()); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - assertThat(generator.send(PythonInteger.valueOf(10))).isEqualTo(PythonInteger.valueOf(20)); - assertThat(generator.send(PythonInteger.valueOf(100))).isEqualTo(PythonInteger.valueOf(300)); - - assertThat(generator.hasNext()).isFalse(); - assertThatCode(generator::next).isInstanceOf(StopIteration.class) - .matches(stopIteration -> ((StopIteration) stopIteration).getValue().equals(PythonInteger.valueOf(300))); - - generator = (PythonGenerator) generatorCreator.apply(new PythonLikeList<>(List.of(PythonInteger.valueOf(1), - PythonInteger.valueOf(2), - PythonInteger.valueOf(3)))); - - assertThat(generator.hasNext()).isTrue(); - assertThat(generator.next()).isEqualTo(PythonInteger.valueOf(1)); - - PythonGenerator finalGenerator = generator; - assertThatCode(() -> finalGenerator.send(PythonInteger.valueOf(1))).isInstanceOf(AttributeError.class); - } - - @Test - public void testThrowYieldFromGenerator() { - PythonCompiledFunction subgeneratorFunction = PythonFunctionBuilder.newFunction() - .tryCode(tryBuilder -> { - tryBuilder - .loadConstant(1) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .except(PythonAssertionError.ASSERTION_ERROR_TYPE, exceptBuilder -> { - exceptBuilder.loadConstant(2) - .op(GeneratorOpDescriptor.YIELD_VALUE) - .op(StackOpDescriptor.POP_TOP) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .tryEnd() - .build(); - - PythonCompiledFunction generatorFunction = PythonFunctionBuilder.newFunction("subgenerator") - .op(GeneratorOpDescriptor.GEN_START) - .loadParameter("subgenerator") - .op(GeneratorOpDescriptor.GET_YIELD_FROM_ITER) - .loadConstant(null) - .op(GeneratorOpDescriptor.YIELD_FROM) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier subgeneratorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(subgeneratorFunction, Supplier.class); - - Function generatorCreator = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(generatorFunction, Function.class); - - PythonGenerator generator1 = (PythonGenerator) generatorCreator.apply(subgeneratorCreator.get()); - - assertThat(generator1.hasNext()).isTrue(); - assertThat(generator1.next()).isEqualTo(PythonInteger.valueOf(1)); - assertThat(generator1.throwValue(new PythonAssertionError())).isEqualTo(PythonInteger.valueOf(2)); - assertThatCode(generator1::next).isInstanceOf(StopIteration.class) - .matches(stopIteration -> ((StopIteration) stopIteration).getValue().equals(PythonInteger.valueOf(2))); - - PythonGenerator generator2 = - (PythonGenerator) generatorCreator.apply(new PythonLikeList<>(List.of(PythonInteger.valueOf(1), - PythonInteger.valueOf(2), - PythonInteger.valueOf(3)))); - - assertThat(generator2.hasNext()).isTrue(); - assertThat(generator2.next()).isEqualTo(PythonInteger.valueOf(1)); - assertThatCode(() -> generator2.throwValue(new PythonAssertionError())).isInstanceOf(PythonAssertionError.class); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonOverloadImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonOverloadImplementorTest.java deleted file mode 100644 index a9ddbc79..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/PythonOverloadImplementorTest.java +++ /dev/null @@ -1,184 +0,0 @@ -package ai.timefold.jpyinterpreter; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -import org.junit.jupiter.api.Test; - -public class PythonOverloadImplementorTest { - - @Test - public void testSingleOverload() throws NoSuchMethodException { - SingleOverload.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - SingleOverload.class.getMethod("overload")), BuiltinTypes.STRING_TYPE)); - PythonOverloadImplementor.createDispatchesFor(SingleOverload.TYPE); - - SingleOverload instance = new SingleOverload(); - PythonLikeFunction overload = (PythonLikeFunction) SingleOverload.TYPE.$getAttributeOrError("overload"); - assertThat(overload.$call(List.of(instance), Map.of(), null)).isEqualTo(PythonString.valueOf("1")); - } - - @Test - public void testDifferentArgCountOverloads() throws NoSuchMethodException { - DifferentArgCountOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - DifferentArgCountOverloads.class.getMethod("overload")), BuiltinTypes.STRING_TYPE)); - DifferentArgCountOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - DifferentArgCountOverloads.class.getMethod("overload", PythonInteger.class)), BuiltinTypes.INT_TYPE, - BuiltinTypes.INT_TYPE)); - PythonOverloadImplementor.createDispatchesFor(DifferentArgCountOverloads.TYPE); - - DifferentArgCountOverloads instance = new DifferentArgCountOverloads(); - PythonLikeFunction overload = (PythonLikeFunction) DifferentArgCountOverloads.TYPE.$getAttributeOrError("overload"); - assertThat(overload.$call(List.of(instance), Map.of(), null)).isEqualTo(PythonString.valueOf("1")); - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(2)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(2)); - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(3)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(3)); - } - - @Test - public void testDifferentArgTypeOverloads() throws NoSuchMethodException { - DifferentArgTypeOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - DifferentArgTypeOverloads.class.getMethod("overload", PythonString.class)), BuiltinTypes.STRING_TYPE, - BuiltinTypes.STRING_TYPE)); - DifferentArgTypeOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - DifferentArgTypeOverloads.class.getMethod("overload", PythonInteger.class)), BuiltinTypes.INT_TYPE, - BuiltinTypes.INT_TYPE)); - DifferentArgTypeOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - DifferentArgTypeOverloads.class.getMethod("overload", PythonBoolean.class)), BuiltinTypes.BOOLEAN_TYPE, - BuiltinTypes.BOOLEAN_TYPE)); - PythonOverloadImplementor.createDispatchesFor(DifferentArgTypeOverloads.TYPE); - - DifferentArgTypeOverloads instance = new DifferentArgTypeOverloads(); - PythonLikeFunction overload = (PythonLikeFunction) DifferentArgTypeOverloads.TYPE.$getAttributeOrError("overload"); - assertThat(overload.$call(List.of(instance, PythonString.valueOf("1")), Map.of(), null)) - .isEqualTo(PythonString.valueOf("1")); - - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(2)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(2)); - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(3)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(3)); - - assertThat(overload.$call(List.of(instance, PythonBoolean.TRUE), Map.of(), null)).isEqualTo(PythonBoolean.FALSE); - assertThat(overload.$call(List.of(instance, PythonBoolean.FALSE), Map.of(), null)).isEqualTo(PythonBoolean.TRUE); - } - - @Test - public void testVariousOverloads() throws NoSuchMethodException { - VariousOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - VariousOverloads.class.getMethod("overload")), BuiltinTypes.STRING_TYPE)); - VariousOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - VariousOverloads.class.getMethod("overload", PythonString.class)), BuiltinTypes.STRING_TYPE, - BuiltinTypes.STRING_TYPE)); - VariousOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - VariousOverloads.class.getMethod("overload", PythonInteger.class)), BuiltinTypes.INT_TYPE, - BuiltinTypes.INT_TYPE)); - VariousOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - VariousOverloads.class.getMethod("overload", PythonString.class, PythonString.class)), BuiltinTypes.STRING_TYPE, - BuiltinTypes.STRING_TYPE, - BuiltinTypes.STRING_TYPE)); - VariousOverloads.TYPE.addMethod("overload", new PythonFunctionSignature(new MethodDescriptor( - VariousOverloads.class.getMethod("overload", PythonInteger.class, PythonInteger.class)), BuiltinTypes.INT_TYPE, - BuiltinTypes.INT_TYPE, - BuiltinTypes.INT_TYPE)); - PythonOverloadImplementor.createDispatchesFor(VariousOverloads.TYPE); - - VariousOverloads instance = new VariousOverloads(); - PythonLikeFunction overload = (PythonLikeFunction) VariousOverloads.TYPE.$getAttributeOrError("overload"); - assertThat(overload.$call(List.of(instance), Map.of(), null)).isEqualTo(PythonString.valueOf("1")); - assertThat(overload.$call(List.of(instance, PythonString.valueOf("a")), Map.of(), null)) - .isEqualTo(PythonString.valueOf("a 1")); - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(2)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(2)); - assertThat(overload.$call(List.of(instance, PythonString.valueOf("a"), PythonString.valueOf("b")), Map.of(), null)) - .isEqualTo(PythonString.valueOf("a b")); - assertThat(overload.$call(List.of(instance, PythonInteger.valueOf(1), PythonInteger.valueOf(2)), Map.of(), null)) - .isEqualTo(PythonInteger.valueOf(3)); - } - - public static class SingleOverload extends AbstractPythonLikeObject { - static PythonLikeType TYPE = new PythonLikeType("single-overload", SingleOverload.class); - - public SingleOverload() { - super(TYPE); - } - - public PythonString overload() { - return PythonString.valueOf("1"); - } - } - - public static class DifferentArgCountOverloads extends AbstractPythonLikeObject { - static PythonLikeType TYPE = new PythonLikeType("different-arg-count-overloads", DifferentArgCountOverloads.class); - - public DifferentArgCountOverloads() { - super(TYPE); - } - - public PythonString overload() { - return PythonString.valueOf("1"); - } - - public PythonInteger overload(PythonInteger arg) { - return arg; - } - } - - public static class DifferentArgTypeOverloads extends AbstractPythonLikeObject { - static PythonLikeType TYPE = new PythonLikeType("different-arg-type-overloads", DifferentArgTypeOverloads.class); - - public DifferentArgTypeOverloads() { - super(TYPE); - } - - public PythonString overload(PythonString string) { - return string; - } - - public PythonInteger overload(PythonInteger integer) { - return integer; - } - - public PythonBoolean overload(PythonBoolean bool) { - return bool.not(); - } - } - - public static class VariousOverloads extends AbstractPythonLikeObject { - static PythonLikeType TYPE = new PythonLikeType("various-overloads", VariousOverloads.class); - - public VariousOverloads() { - super(TYPE); - } - - public PythonString overload() { - return PythonString.valueOf("1"); - } - - public PythonString overload(PythonString string) { - return PythonString.valueOf(string.getValue() + " 1"); - } - - public PythonInteger overload(PythonInteger integer) { - return integer; - } - - public PythonString overload(PythonString a, PythonString b) { - return PythonString.valueOf(a.getValue() + " " + b.getValue()); - } - - public PythonInteger overload(PythonInteger a, PythonInteger b) { - return PythonInteger.valueOf(a.getValue().intValue() + b.getValue().intValue()); - } - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/dag/FlowGraphTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/dag/FlowGraphTest.java deleted file mode 100644 index 1c3468a2..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/dag/FlowGraphTest.java +++ /dev/null @@ -1,600 +0,0 @@ -package ai.timefold.jpyinterpreter.dag; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Objects; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.FunctionMetadata; -import ai.timefold.jpyinterpreter.LocalVariableHelper; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.StackMetadata; -import ai.timefold.jpyinterpreter.ValueSourceInfo; -import ai.timefold.jpyinterpreter.opcodes.Opcode; -import ai.timefold.jpyinterpreter.opcodes.OpcodeWithoutSource; -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ExceptionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ModuleOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StringOpDescriptor; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonString; -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.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; -import org.objectweb.asm.Type; - -public class FlowGraphTest { - - private static PythonLikeType OBJECT_TYPE = BuiltinTypes.BASE_TYPE; - - static FlowGraph getFlowGraph(FunctionMetadata functionMetadata, StackMetadata initialStackMetadata, - PythonCompiledFunction function) { - List out = new ArrayList<>(function.instructionList.size()); - for (PythonBytecodeInstruction instruction : function.instructionList) { - out.add(Opcode.lookupOpcodeForInstruction(instruction, PythonVersion.PYTHON_3_10)); - } - return FlowGraph.createFlowGraph(functionMetadata, initialStackMetadata, out); - } - - static FunctionMetadata getFunctionMetadata(PythonCompiledFunction function) { - FunctionMetadata out = new FunctionMetadata(); - out.functionType = PythonBytecodeToJavaBytecodeTranslator.getFunctionType(function); - out.className = FlowGraphTest.class.getName(); - out.pythonCompiledFunction = function; - out.bytecodeCounterToLabelMap = new HashMap<>(); - out.bytecodeCounterToCodeArgumenterList = new HashMap<>(); - return out; - } - - static StackMetadata getInitialStackMetadata(PythonCompiledFunction pythonCompiledFunction) { - return new StackMetadata(new LocalVariableHelper(pythonCompiledFunction.getParameterTypes() - .stream() - .map(type -> Type.getType(type.getJavaTypeDescriptor())) - .toArray(Type[]::new), - pythonCompiledFunction)); - } - - static List getFrameData(FlowGraph flowGraph) { - List stackMetadataList = flowGraph.getStackMetadataForOperations(); - List out = new ArrayList<>(stackMetadataList.size()); - - for (int i = 0; i < stackMetadataList.size(); i++) { - out.add(FrameData.from(i, stackMetadataList.get(i))); - } - - return out; - } - - @Test - public void testStackMetadataForBasicOps() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant("Hi") - .op(StackOpDescriptor.ROT_TWO) - .tuple(2) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0), - new FrameData(1).stack(BuiltinTypes.INT_TYPE), - new FrameData(2).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.STRING_TYPE), - new FrameData(3).stack(BuiltinTypes.STRING_TYPE, BuiltinTypes.INT_TYPE), - new FrameData(4).stack(BuiltinTypes.TUPLE_TYPE)); - } - - @Test - public void testStackMetadataForLocalVariables() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .storeVariable("one") - .loadConstant("2") - .storeVariable("two") - .loadVariable("one") - .loadVariable("two") - .tuple(2) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).locals(null, null), - new FrameData(1).stack(BuiltinTypes.INT_TYPE).locals(null, null), - new FrameData(2).stack().locals(BuiltinTypes.INT_TYPE, null), - new FrameData(3).stack(BuiltinTypes.STRING_TYPE).locals(BuiltinTypes.INT_TYPE, null), - new FrameData(4).stack().locals(BuiltinTypes.INT_TYPE, BuiltinTypes.STRING_TYPE), - new FrameData(5).stack(BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE, BuiltinTypes.STRING_TYPE), - new FrameData(6).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.STRING_TYPE).locals(BuiltinTypes.INT_TYPE, - BuiltinTypes.STRING_TYPE), - new FrameData(7).stack(BuiltinTypes.TUPLE_TYPE).locals(BuiltinTypes.INT_TYPE, BuiltinTypes.STRING_TYPE)); - } - - @Test - public void testStackMetadataForLoops() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(0) - .storeVariable("sum") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.GET_ITER) - .loop(block -> { - block.loadVariable("sum"); - block.op(DunderOpDescriptor.BINARY_ADD); - block.storeVariable("sum"); - }) - .loadVariable("sum") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).locals((PythonLikeType) null), // LOAD_CONSTANT - new FrameData(1).stack(BuiltinTypes.INT_TYPE).locals((PythonLikeType) null), // STORE - new FrameData(2).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(3).stack(BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(4).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(5).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE) - .locals(BuiltinTypes.INT_TYPE), // TUPLE(3) - - // Type information is lost because Tuple is not generic - new FrameData(6).stack(BuiltinTypes.TUPLE_TYPE).locals(BuiltinTypes.INT_TYPE), // ITERATOR - new FrameData(7).stack(BuiltinTypes.ITERATOR_TYPE).locals(OBJECT_TYPE), // NEXT - new FrameData(8).stack(BuiltinTypes.ITERATOR_TYPE, OBJECT_TYPE).locals(OBJECT_TYPE), // LOAD_VAR - new FrameData(9).stack(BuiltinTypes.ITERATOR_TYPE, OBJECT_TYPE, OBJECT_TYPE).locals(OBJECT_TYPE), // ADD - new FrameData(10).stack(BuiltinTypes.ITERATOR_TYPE, OBJECT_TYPE).locals(OBJECT_TYPE), // STORE - new FrameData(11).stack(BuiltinTypes.ITERATOR_TYPE).locals(OBJECT_TYPE), // JUMP_ABS - new FrameData(12).stack().locals(OBJECT_TYPE), // NOP - new FrameData(13).stack().locals(OBJECT_TYPE), // LOAD_VAR - new FrameData(14).stack(OBJECT_TYPE).locals(OBJECT_TYPE) // RETURN - ); - } - - @Test - public void testStackMetadataForExceptions() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .tryCode(code -> { - code.loadConstant(5) - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTrue(block -> { - block.loadConstant("Try").op(ControlOpDescriptor.RETURN_VALUE); - }) - .op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }, true) - .except(PythonAssertionError.ASSERTION_ERROR_TYPE, except -> { - except.loadConstant("Assert").op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .tryEnd() - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).stack(), // SETUP_TRY - new FrameData(1).stack(), // SETUP_TRY - new FrameData(2).stack(), // LOAD_CONSTANT - new FrameData(3).stack(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(4).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE), // COMPARE - new FrameData(5).stack(BuiltinTypes.BOOLEAN_TYPE), // POP_JUMP_IF_TRUE - new FrameData(6).stack(), // LOAD_CONSTANT - new FrameData(7).stack(BuiltinTypes.STRING_TYPE), // RETURN - new FrameData(8).stack(), // NOP - new FrameData(9).stack(), // LOAD_ASSERTION_ERROR - new FrameData(10).stack(PythonAssertionError.ASSERTION_ERROR_TYPE), // RAISE - new FrameData(11).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // except handler; DUP_TOP, - new FrameData(12).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // LOAD_CONSTANT - new FrameData(13).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - BuiltinTypes.TYPE_TYPE), // JUMP_IF_NOT_EXC_MATCH - new FrameData(14).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(15).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(16).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE), // POP_TOP - new FrameData(17).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE), // POP_EXCEPT - new FrameData(18).stack(), // LOAD_CONSTANT - new FrameData(19).stack(BuiltinTypes.STRING_TYPE), // RETURN - new FrameData(20).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(21).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(22).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE) // RERAISE - ); - } - - @Test - public void testStackMetadataForTryFinally() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .tryCode(code -> { - code.loadConstant(1) - .loadConstant(1) - .compare(CompareOp.EQUALS) - .ifTrue(block -> { - block.op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }) - .loadConstant(1) - .loadConstant(2) - .compare(CompareOp.EQUALS) - .ifTrue(block -> { - block.loadConstant(new StopIteration()) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }); - }, false) - .except(PythonAssertionError.ASSERTION_ERROR_TYPE, except -> { - except.loadConstant("Assert").storeGlobalVariable("exception"); - }, false) - .andFinally(code -> { - code.loadConstant("Finally") - .storeGlobalVariable("finally"); - }, false) - .tryEnd() - .loadConstant(1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).stack(), // SETUP_TRY - new FrameData(1).stack(), // SETUP_TRY - new FrameData(2).stack(), // LOAD_CONSTANT - new FrameData(3).stack(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(4).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE), // COMPARE - new FrameData(5).stack(BuiltinTypes.BOOLEAN_TYPE), // POP_JUMP_IF_TRUE - new FrameData(6).stack(), // LOAD_ASSERTION_ERROR - new FrameData(7).stack(PythonAssertionError.ASSERTION_ERROR_TYPE), // RAISE - new FrameData(8).stack(), // NOP - new FrameData(9).stack(), // LOAD_CONSTANT - new FrameData(10).stack(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(11).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE), // COMPARE - new FrameData(12).stack(BuiltinTypes.BOOLEAN_TYPE), // POP_JUMP_IF_TRUE - new FrameData(13).stack(), // LOAD_CONSTANT - new FrameData(14).stack(StopIteration.STOP_ITERATION_TYPE), // RAISE - new FrameData(15).stack(), // NOP - new FrameData(16).stack(), // JUMP_ABSOLUTE - new FrameData(17).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // except handler; DUP_TOP, - new FrameData(18).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // LOAD_CONSTANT - new FrameData(19).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - BuiltinTypes.TYPE_TYPE), // JUMP_IF_NOT_EXC_MATCH - new FrameData(20).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(21).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(22).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE), // POP_TOP - new FrameData(23).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE), // POP_EXCEPT - new FrameData(24).stack(), // LOAD_CONSTANT - new FrameData(25).stack(BuiltinTypes.STRING_TYPE), // STORE_GLOBAL - new FrameData(26).stack(), // JUMP_ABSOLUTE - new FrameData(27).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // RERAISE - new FrameData(28).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP_TOP - new FrameData(29).stack(), // POP_TOP - new FrameData(30).stack(), // POP_TOP - new FrameData(31).stack(BuiltinTypes.STRING_TYPE), // STORE - new FrameData(32).stack(), // JUMP_ABSOLUTE - new FrameData(33).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // NO-OP; Uncaught exception handler - new FrameData(34).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // LOAD-CONSTANT - new FrameData(35).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE, - BuiltinTypes.STRING_TYPE), // STORE - new FrameData(36).stack(BuiltinTypes.NONE_TYPE, BuiltinTypes.INT_TYPE, BuiltinTypes.NONE_TYPE, - PythonTraceback.TRACEBACK_TYPE, PythonBaseException.BASE_EXCEPTION_TYPE, - PythonBaseException.BASE_EXCEPTION_TYPE), // POP-TOP - new FrameData(37).stack(), // RERAISE - new FrameData(38).stack(), // NO-OP; After try - new FrameData(39).stack(BuiltinTypes.INT_TYPE)); - } - - @Test - public void testStackMetadataForIfStatementsThatExitEarly() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .storeVariable("a") - .loadVariable("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTrue(block -> { - block.loadConstant("10"); - block.storeVariable("a"); - block.loadVariable("a"); - block.op(ControlOpDescriptor.RETURN_VALUE); - }) - .loadConstant(-10) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).stack().locals((PythonLikeType) null), // LOAD_CONSTANT - new FrameData(1).stack(BuiltinTypes.INT_TYPE).locals((PythonLikeType) null), // STORE - new FrameData(2).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_VARIABLE - new FrameData(3).stack(BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(4).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // COMPARE_OP - new FrameData(5).stack(BuiltinTypes.BOOLEAN_TYPE).locals(BuiltinTypes.INT_TYPE), // POP_JUMP_IF_TRUE - new FrameData(6).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(7).stack(BuiltinTypes.STRING_TYPE).locals(BuiltinTypes.INT_TYPE), // STORE - new FrameData(8).stack().locals(BuiltinTypes.STRING_TYPE), // LOAD_VARIABLE - new FrameData(9).stack(BuiltinTypes.STRING_TYPE).locals(BuiltinTypes.STRING_TYPE), // RETURN - new FrameData(10).stack().locals(BuiltinTypes.INT_TYPE), // NOP - new FrameData(11).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(12).stack(BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE) // RETURN - ); - } - - @Test - public void testStackMetadataForIfStatementsThatDoNotExitEarly() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .storeVariable("a") - .loadVariable("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTrue(block -> { - block.loadConstant("10"); - block.storeVariable("a"); - }) - .loadConstant(-10) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - List stackMetadataList = getFrameData(flowGraph); - - assertThat(stackMetadataList).containsExactly( - new FrameData(0).stack().locals((PythonLikeType) null), // LOAD_CONSTANT - new FrameData(1).stack(BuiltinTypes.INT_TYPE).locals((PythonLikeType) null), // STORE - new FrameData(2).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_VARIABLE - new FrameData(3).stack(BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(4).stack(BuiltinTypes.INT_TYPE, BuiltinTypes.INT_TYPE).locals(BuiltinTypes.INT_TYPE), // COMPARE_OP - new FrameData(5).stack(BuiltinTypes.BOOLEAN_TYPE).locals(BuiltinTypes.INT_TYPE), // POP_JUMP_IF_TRUE - new FrameData(6).stack().locals(BuiltinTypes.INT_TYPE), // LOAD_CONSTANT - new FrameData(7).stack(BuiltinTypes.STRING_TYPE).locals(BuiltinTypes.INT_TYPE), // STORE - new FrameData(8).stack().locals(OBJECT_TYPE), // NOP - new FrameData(9).stack().locals(OBJECT_TYPE), // LOAD_CONSTANT - new FrameData(10).stack(BuiltinTypes.INT_TYPE).locals(OBJECT_TYPE) // RETURN - ); - } - - @Test - public void testStackMetadataWithExceptionDeadCode() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("self", "resource") - .loadParameter("self") - .getAttribute("fullname") - .loadMethod("replace") - .loadConstant(".") - .loadConstant("/") - .callMethod(2) - .storeVariable("fullname_as_path") - .loadVariable("fullname_as_path") - .op(StringOpDescriptor.FORMAT_VALUE, 0) - .loadConstant("/") - .loadParameter("resource") - .op(StringOpDescriptor.FORMAT_VALUE, 0) - .op(StringOpDescriptor.BUILD_STRING, 3) - .storeVariable("path") - .loadConstant(0) - .loadConstant(List.of(PythonString.valueOf("BytesIO"))) - .op(ModuleOpDescriptor.IMPORT_NAME, 2) - .op(ModuleOpDescriptor.IMPORT_FROM, 3) - .storeVariable("BytesIO") - .op(StackOpDescriptor.POP_TOP) - .op(ExceptionOpDescriptor.SETUP_FINALLY, 9) - .loadVariable("BytesIO") - .loadParameter("self") - .getAttribute("zipimporter") - .loadMethod("get_data") - .loadVariable("path") - .callMethod(1) - .callFunction(1) - .op(ExceptionOpDescriptor.POP_BLOCK) - .op(ControlOpDescriptor.RETURN_VALUE) - .op(StackOpDescriptor.DUP_TOP) - .loadGlobalVariable("OSError") - .op(ControlOpDescriptor.JUMP_IF_NOT_EXC_MATCH, 42) - .op(StackOpDescriptor.POP_TOP) - .op(StackOpDescriptor.POP_TOP) - .op(StackOpDescriptor.POP_TOP) - .loadGlobalVariable("FileNotFoundError") - .loadVariable("path") - .callFunction(1) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1) - .op(ExceptionOpDescriptor.POP_EXCEPT) - .op(ControlOpDescriptor.JUMP_FORWARD, 1) - .op(ExceptionOpDescriptor.RERAISE, 0) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - FunctionMetadata functionMetadata = getFunctionMetadata(pythonCompiledFunction); - StackMetadata metadata = getInitialStackMetadata(pythonCompiledFunction) - .setLocalVariableValueSource(0, ValueSourceInfo.of(new OpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)) - .setLocalVariableValueSource(1, ValueSourceInfo.of(new OpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)); - FlowGraph flowGraph = getFlowGraph(functionMetadata, metadata, pythonCompiledFunction); - getFrameData(flowGraph); - // simply test no exception is raised - } - - private static class FrameData { - int index; - boolean isDead; - List stackTypes; - List localVariableTypes; - List cellTypes; - - public FrameData(int index) { - this.index = index; - isDead = false; - stackTypes = new ArrayList<>(); - localVariableTypes = new ArrayList<>(); - cellTypes = new ArrayList<>(); - } - - public static FrameData from(int index, StackMetadata stackMetadata) { - FrameData out = new FrameData(index); - - if (stackMetadata.isDeadCode()) { - out.isDead = true; - return out; - } - stackMetadata.getValueSourcesUpToStackIndex(stackMetadata.getStackSize()).forEach(valueSourceInfo -> { - if (valueSourceInfo != null) { - out.stackTypes.add(valueSourceInfo.getValueType()); - } else { - out.stackTypes.add(null); - } - }); - IntStream.range(0, stackMetadata.localVariableHelper.getNumberOfLocalVariables()).forEach(i -> { - ValueSourceInfo valueSourceInfo = stackMetadata.getLocalVariableValueSource(i); - if (valueSourceInfo != null) { - out.localVariableTypes.add(valueSourceInfo.getValueType()); - } else { - out.localVariableTypes.add(null); - } - }); - IntStream.range(0, stackMetadata.localVariableHelper.getNumberOfCells()).forEach(i -> { - ValueSourceInfo valueSourceInfo = stackMetadata.getCellVariableValueSource(i); - if (valueSourceInfo != null) { - out.cellTypes.add(valueSourceInfo.getValueType()); - } else { - out.cellTypes.add(null); - } - }); - return out; - } - - public FrameData copy() { - FrameData out = new FrameData(index); - out.isDead = isDead; - out.stackTypes.addAll(stackTypes); - out.localVariableTypes.addAll(localVariableTypes); - out.cellTypes.addAll(cellTypes); - return out; - } - - public FrameData stack(PythonLikeType... valueTypes) { - FrameData out = copy(); - out.stackTypes.addAll(Arrays.asList(valueTypes)); - return out; - } - - public FrameData locals(PythonLikeType... valueTypes) { - FrameData out = copy(); - out.localVariableTypes.addAll(Arrays.asList(valueTypes)); - return out; - } - - public FrameData cells(PythonLikeType... valueTypes) { - FrameData out = copy(); - out.cellTypes.addAll(Arrays.asList(valueTypes)); - return out; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FrameData frameData = (FrameData) o; - return index == frameData.index - && isDead == frameData.isDead - && Objects.equals(stackTypes, frameData.stackTypes) - && Objects.equals(localVariableTypes, frameData.localVariableTypes) - && Objects.equals(cellTypes, frameData.cellTypes); - } - - @Override - public int hashCode() { - return Objects.hash(index, isDead, stackTypes, localVariableTypes, cellTypes); - } - - @Override - public String toString() { - return "FrameData{" + - "index=" + index + - ", isDead=" + isDead + - ", stackTypes=" + stackTypes + - ", localVariableTypes=" + localVariableTypes + - ", cellTypes=" + cellTypes + - '}'; - } - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementorTest.java deleted file mode 100644 index d784f830..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/CollectionImplementorTest.java +++ /dev/null @@ -1,487 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonSlice; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; -import ai.timefold.jpyinterpreter.util.function.TriFunction; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class CollectionImplementorTest { - - @Test - public void testGetIterator() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.GET_ITER) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Iterable javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Iterable.class); - assertThat(javaFunction).containsExactly(1L, 2L, 3L); - } - - @Test - public void testIteration() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(0) - .storeVariable("sum") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.GET_ITER) - .loop(block -> { - block.loadVariable("sum"); - block.op(DunderOpDescriptor.BINARY_ADD); - block.storeVariable("sum"); - }) - .loadVariable("sum") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(6L); - } - - @Test - public void testContains() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.CONTAINS_OP, 0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Predicate javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Predicate.class); - assertThat(javaFunction.test(1L)).isEqualTo(true); - assertThat(javaFunction.test(4L)).isEqualTo(false); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .op(CollectionOpDescriptor.CONTAINS_OP, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Predicate.class); - assertThat(javaFunction.test(1L)).isEqualTo(false); - assertThat(javaFunction.test(4L)).isEqualTo(true); - } - - @Test - public void testSet() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadConstant(1) - .loadConstant(2) - .loadConstant(2) - .set(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Set.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2))); - } - - @Test - public void testListAppend() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .list(0) - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .op(CollectionOpDescriptor.LIST_APPEND, 3) - .op(CollectionOpDescriptor.LIST_APPEND, 2) - .op(CollectionOpDescriptor.LIST_APPEND, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()) - .isEqualTo(List.of(PythonInteger.valueOf(3), PythonInteger.valueOf(2), PythonInteger.valueOf(1))); - } - - @Test - public void testSetAdd() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .set(0) - .loadConstant(1) - .loadConstant(2) - .loadConstant(2) - .op(CollectionOpDescriptor.SET_ADD, 3) - .op(CollectionOpDescriptor.SET_ADD, 2) - .op(CollectionOpDescriptor.SET_ADD, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Set.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2))); - } - - @Test - public void testListExtend() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .list(0) - .loadConstant(1) - .list(1) - .loadConstant(2) - .loadConstant(3) - .list(2) - .op(CollectionOpDescriptor.LIST_EXTEND, 2) - .op(CollectionOpDescriptor.LIST_EXTEND, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()) - .isEqualTo(List.of(PythonInteger.valueOf(2), PythonInteger.valueOf(3), PythonInteger.valueOf(1))); - } - - @Test - public void testListToTuple() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .list(3) - .op(CollectionOpDescriptor.LIST_TO_TUPLE) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - Object out = javaFunction.get(); - assertThat(out).isInstanceOf(PythonLikeTuple.class); - assertThat(out).asList().containsExactly(1, 2, 3); - } - - @Test - public void testUnpackSequence() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("sequence") - .loadParameter("sequence") - .op(CollectionOpDescriptor.UNPACK_SEQUENCE, 3) - .storeVariable("a") - .storeVariable("b") - .storeVariable("c") - .loadVariable("a") - .loadVariable("b") - .loadVariable("c") - .tuple(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(List.of(1, 2, 3))).isEqualTo(List.of(1, 2, 3)); - assertThatCode(() -> javaFunction.apply(List.of(1, 2))).hasMessage("not enough values to unpack (expected 3, got 2)"); - assertThatCode(() -> javaFunction.apply(List.of(1, 2, 3, 4))).hasMessage("too many values to unpack (expected 3)"); - } - - @Test - public void testUnpackSequenceWithTail() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("sequence") - .loadParameter("sequence") - .op(CollectionOpDescriptor.UNPACK_EX, 3) - .storeVariable("a") - .storeVariable("b") - .storeVariable("c") - .storeVariable("tail") - .loadVariable("tail") - .loadVariable("a") - .op(CollectionOpDescriptor.LIST_APPEND, 1) - .loadVariable("b") - .op(CollectionOpDescriptor.LIST_APPEND, 1) - .loadVariable("c") - .op(CollectionOpDescriptor.LIST_APPEND, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(List.of(1, 2, 3))).isEqualTo(List.of(1, 2, 3)); - assertThatCode(() -> javaFunction.apply(List.of(1, 2))).hasMessage("not enough values to unpack (expected 3, got 2)"); - assertThat(javaFunction.apply(List.of(1, 2, 3, 4, 5))).isEqualTo(List.of(4, 5, 1, 2, 3)); - } - - @Test - public void testSetUpdate() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .set(0) - .loadConstant(1) - .loadConstant(2) - .set(2) - .loadConstant(2) - .loadConstant(3) - .set(2) - .op(CollectionOpDescriptor.SET_UPDATE, 2) - .op(CollectionOpDescriptor.SET_UPDATE, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()) - .isEqualTo(Set.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), PythonInteger.valueOf(3))); - } - - @Test - public void testMapAdd() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .dict(0) - .loadConstant(1) - .loadConstant(2) - .loadConstant(2) - .loadConstant(4) - .loadConstant(3) - .loadConstant(6) - .op(CollectionOpDescriptor.MAP_ADD, 5) - .op(CollectionOpDescriptor.MAP_ADD, 3) - .op(CollectionOpDescriptor.MAP_ADD, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), - PythonInteger.valueOf(2), PythonInteger.valueOf(4), - PythonInteger.valueOf(3), PythonInteger.valueOf(6))); - } - - @Test - public void testMapUpdate() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .dict(0) - .loadConstant(1) - .loadConstant(2) - .dict(1) - .loadConstant(2) - .loadConstant(4) - .loadConstant(3) - .loadConstant(6) - .dict(2) - .op(CollectionOpDescriptor.DICT_UPDATE, 2) - .op(CollectionOpDescriptor.DICT_UPDATE, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), - PythonInteger.valueOf(2), PythonInteger.valueOf(4), - PythonInteger.valueOf(3), PythonInteger.valueOf(6))); - } - - @Test - public void testMapMergeNoDuplicates() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .dict(0) - .loadConstant(1) - .loadConstant(2) - .dict(1) - .loadConstant(2) - .loadConstant(4) - .loadConstant(3) - .loadConstant(6) - .dict(2) - .op(CollectionOpDescriptor.DICT_MERGE, 2) - .op(CollectionOpDescriptor.DICT_MERGE, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), - PythonInteger.valueOf(2), PythonInteger.valueOf(4), - PythonInteger.valueOf(3), PythonInteger.valueOf(6))); - } - - @Test - public void testMapMergeDuplicates() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .dict(0) - .loadConstant(1) - .loadConstant(2) - .dict(1) - .loadConstant(1) - .loadConstant(2) - .loadConstant(2) - .loadConstant(4) - .dict(2) - .op(CollectionOpDescriptor.DICT_MERGE, 2) - .op(CollectionOpDescriptor.DICT_MERGE, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThatCode(javaFunction::get).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void testMap() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadConstant(1) - .loadConstant(2) - .loadConstant(2) - .loadConstant(4) - .loadConstant(3) - .loadConstant(6) - .dict(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), - PythonInteger.valueOf(2), PythonInteger.valueOf(4), - PythonInteger.valueOf(3), PythonInteger.valueOf(6))); - } - - @Test - public void testConstKeyMap() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadConstant(2) - .loadConstant(4) - .loadConstant(6) - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .constDict(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2), - PythonInteger.valueOf(2), PythonInteger.valueOf(4), - PythonInteger.valueOf(3), PythonInteger.valueOf(6))); - } - - @Test - public void testSetItem() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("key", "value") - .dict(0) - .op(StackOpDescriptor.DUP_TOP) - .loadParameter("value") - .op(StackOpDescriptor.ROT_TWO) - .loadParameter("key") - .op(CollectionOpDescriptor.STORE_SUBSCR) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - assertThat(javaFunction.apply(1, 2)).isEqualTo(Map.of(PythonInteger.valueOf(1), PythonInteger.valueOf(2))); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("key", "value") - .loadConstant(1) - .loadConstant(2) - .list(2) - .op(StackOpDescriptor.DUP_TOP) - .loadParameter("value") - .op(StackOpDescriptor.ROT_TWO) - .loadParameter("key") - .op(CollectionOpDescriptor.STORE_SUBSCR) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - assertThat(javaFunction.apply(1, 0)).isEqualTo(List.of(PythonInteger.valueOf(1), PythonInteger.valueOf(0))); - } - - @Test - public void testBuildSliceTwoArgs() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("start", "stop") - .loadParameter("start") - .loadParameter("stop") - .op(CollectionOpDescriptor.BUILD_SLICE, 2) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - assertThat(javaFunction.apply(1, 2)) - .isEqualTo(new PythonSlice(PythonInteger.valueOf(1), PythonInteger.valueOf(2), PythonInteger.valueOf(1))); - } - - @Test - public void testBuildSliceThreeArgs() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("start", "stop", "step") - .loadParameter("start") - .loadParameter("stop") - .loadParameter("step") - .op(CollectionOpDescriptor.BUILD_SLICE, 3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - TriFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, TriFunction.class); - assertThat(javaFunction.apply(1, 2, 3)) - .isEqualTo(new PythonSlice(PythonInteger.valueOf(1), PythonInteger.valueOf(2), PythonInteger.valueOf(3))); - } - - @Test - public void testSequenceSlice() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("sequence", "start", "stop") - .loadParameter("sequence") - .loadParameter("start") - .loadParameter("stop") - .op(CollectionOpDescriptor.BUILD_SLICE, 2) - .op(DunderOpDescriptor.BINARY_SUBSCR) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - TriFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, TriFunction.class); - assertThat(javaFunction.apply(new PythonLikeList(List.of(PythonInteger.valueOf(1), - PythonInteger.valueOf(2), - PythonInteger.valueOf(3), - PythonInteger.valueOf(4), - PythonInteger.valueOf(5))), 1, 2)) - .isEqualTo(new PythonLikeList(List.of(PythonInteger.valueOf(2)))); - - assertThat(javaFunction.apply(PythonLikeTuple.fromItems(PythonInteger.valueOf(1), - PythonInteger.valueOf(2), - PythonInteger.valueOf(3), - PythonInteger.valueOf(4), - PythonInteger.valueOf(5)), 1, 2)) - .isEqualTo(PythonLikeTuple.fromItems(PythonInteger.valueOf(2))); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementorTest.java deleted file mode 100644 index 94a82db7..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementorTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.BiFunction; -import java.util.function.BiPredicate; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class DunderOperatorImplementorTest { - @Test - public void testComparisons() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.LESS_THAN) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiPredicate javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(true); - assertThat(javaFunction.test(2, 1)).isEqualTo(false); - assertThat(javaFunction.test(1, 1)).isEqualTo(false); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.LESS_THAN_OR_EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(true); - assertThat(javaFunction.test(2, 1)).isEqualTo(false); - assertThat(javaFunction.test(1, 1)).isEqualTo(true); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.GREATER_THAN) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(false); - assertThat(javaFunction.test(2, 1)).isEqualTo(true); - assertThat(javaFunction.test(1, 1)).isEqualTo(false); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.GREATER_THAN_OR_EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(false); - assertThat(javaFunction.test(2, 1)).isEqualTo(true); - assertThat(javaFunction.test(1, 1)).isEqualTo(true); - } - - @Test - public void testEquals() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiPredicate javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(false); - assertThat(javaFunction.test(2, 1)).isEqualTo(false); - assertThat(javaFunction.test(1, 1)).isEqualTo(true); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.NOT_EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiPredicate.class); - - assertThat(javaFunction.test(1, 2)).isEqualTo(true); - assertThat(javaFunction.test(2, 1)).isEqualTo(true); - assertThat(javaFunction.test(1, 1)).isEqualTo(false); - } - - private BiFunction getMathFunction(DunderOpDescriptor opcodeIdentifier) { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .op(opcodeIdentifier) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - return PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - } - - @Test - public void testMathOp() { - BiFunction javaFunction = getMathFunction(DunderOpDescriptor.BINARY_ADD); - - assertThat(javaFunction.apply(1L, 2L)).isEqualTo(3L); - - javaFunction = getMathFunction(DunderOpDescriptor.BINARY_SUBTRACT); - assertThat(javaFunction.apply(3L, 2L)).isEqualTo(1L); - - javaFunction = getMathFunction(DunderOpDescriptor.BINARY_TRUE_DIVIDE); - assertThat(javaFunction.apply(3L, 2L)).isEqualTo(1.5d); - - javaFunction = getMathFunction(DunderOpDescriptor.BINARY_FLOOR_DIVIDE); - assertThat(javaFunction.apply(3L, 2L)).isEqualTo(1L); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementorTest.java deleted file mode 100644 index d86c4e75..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementorTest.java +++ /dev/null @@ -1,233 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ExceptionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.errors.PythonAssertionError; -import ai.timefold.jpyinterpreter.types.errors.PythonException; -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; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class ExceptionImplementorTest { - @Test - public void testTryExcept() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .tryCode(code -> { - code.loadParameter("item") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTrue(block -> { - block.loadConstant("Try").op(ControlOpDescriptor.RETURN_VALUE); - }) - .op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }, true) - .except(PythonAssertionError.ASSERTION_ERROR_TYPE, except -> { - except.loadConstant("Assert").op(ControlOpDescriptor.RETURN_VALUE); - }, true) - .tryEnd() - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - - assertThat(javaFunction.apply(1)).isEqualTo("Try"); - assertThat(javaFunction.apply(6)).isEqualTo("Assert"); - } - - @Test - public void testTryExceptFinally() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadConstant(null).storeGlobalVariable("exception") - .loadConstant("Before Try").storeGlobalVariable("finally") - .tryCode(code -> { - code.loadParameter("item") - .loadConstant(1) - .compare(CompareOp.EQUALS) - .ifTrue(block -> { - block.op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }) - .loadParameter("item") - .loadConstant(2) - .compare(CompareOp.EQUALS) - .ifTrue(block -> { - block.loadConstant(new StopIteration()) - .op(ExceptionOpDescriptor.RAISE_VARARGS, 1); - }); - }, false) - .except(PythonAssertionError.ASSERTION_ERROR_TYPE, except -> { - except.loadConstant("Assert").storeGlobalVariable("exception"); - }, false) - .andFinally(code -> { - code.loadConstant("Finally") - .storeGlobalVariable("finally"); - }, false) - .tryEnd() - .loadConstant(1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Class javaFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(pythonCompiledFunction, Function.class); - - Map globalsMap = new HashMap<>(); - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - - Mockito.when(interpreter.getGlobal(Mockito.any(), Mockito.any())) - .thenAnswer(invocationOnMock -> globalsMap.get(invocationOnMock.getArgument(0, String.class))); - Mockito.doAnswer(invocationOnMock -> { - globalsMap.put(invocationOnMock.getArgument(1, String.class), - invocationOnMock.getArgument(2, PythonLikeObject.class)); - return null; - }).when(interpreter).setGlobal(Mockito.any(), Mockito.any(), Mockito.any()); - - Function javaFunction = - (Function) PythonBytecodeToJavaBytecodeTranslator.createInstance(javaFunctionClass, interpreter); - - assertThat(javaFunction.apply(0)).isEqualTo(1); - assertThat(globalsMap.get("exception")).isEqualTo(PythonNone.INSTANCE); - assertThat(globalsMap.get("finally")).isEqualTo(PythonString.valueOf("Finally")); - - assertThat(javaFunction.apply(1)).isEqualTo(1); - assertThat(globalsMap.get("exception")).isEqualTo(PythonString.valueOf("Assert")); - assertThat(globalsMap.get("finally")).isEqualTo(PythonString.valueOf("Finally")); - - assertThatCode(() -> javaFunction.apply(2)).isInstanceOf(StopIteration.class); - assertThat(globalsMap.get("exception")).isEqualTo(PythonNone.INSTANCE); - assertThat(globalsMap.get("finally")).isEqualTo(PythonString.valueOf("Finally")); - } - - @Test - public void testExceptionInLoop() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("start", "last_values") - .loadGlobalVariable("reversed") - .loadParameter("last_values") - .callFunction(1) - .op(CollectionOpDescriptor.GET_ITER) - .loop(loopBuilder -> { - loopBuilder.storeVariable("last_value") - .tryCode(tryBuilder -> { - tryBuilder.loadVariable("last_value") - .loadConstant(1) - .op(DunderOpDescriptor.BINARY_ADD) - .op(ExceptionOpDescriptor.POP_BLOCK) - .op(StackOpDescriptor.ROT_TWO) - .op(StackOpDescriptor.POP_TOP) - .op(ControlOpDescriptor.RETURN_VALUE); - }, true).except(PythonException.EXCEPTION_TYPE, exceptBuilder -> { - exceptBuilder - .op(ControlOpDescriptor.JUMP_ABSOLUTE, 4); - }, true) - .tryEnd(); - }, true) - .loadParameter("start") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiFunction biFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - } - - @Test - public void testWithBlocksWithoutException() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("cxt") - .loadConstant(0) - .storeVariable("result") - .loadParameter("cxt") - .with(withBuilder -> { - withBuilder - .loadConstant(1) - .op(DunderOpDescriptor.BINARY_ADD) - .storeVariable("result"); - }) - .loadVariable("result") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function function = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - - TestContextManager contextManager = new TestContextManager(); - assertThat(function.apply(contextManager)).isEqualTo(2); - assertThat(contextManager.hasExited()).isTrue(); - assertThat(contextManager.getException()).isNull(); - } - - @Test - public void testWithBlocksWithException() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("cxt") - .loadParameter("cxt") - .with(withBuilder -> { - withBuilder - .ifTrue(ifBuilder -> { - ifBuilder.op(ExceptionOpDescriptor.LOAD_ASSERTION_ERROR) - .op(ExceptionOpDescriptor.RERAISE); - }); - }) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function function = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - - TestContextManager contextManager = new TestContextManager(); - assertThatCode(() -> function.apply(contextManager)).isInstanceOf(PythonAssertionError.class); - assertThat(contextManager.hasExited()).isTrue(); - assertThat(contextManager.getException()).isInstanceOf(PythonAssertionError.class); - } - - public static class TestContextManager { - Throwable exception; - boolean exitCalled; - - public TestContextManager() { - exception = null; - exitCalled = false; - } - - public PythonInteger __enter__() { - return PythonInteger.valueOf(1); - } - - public PythonBoolean __exit__(PythonLikeType type, Throwable exception, PythonTraceback traceback) { - exitCalled = true; - this.exception = exception; - return PythonBoolean.FALSE; - } - - public boolean hasExited() { - return exitCalled; - } - - public Throwable getException() { - return exception; - } - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementorTest.java deleted file mode 100644 index cfc8d9cb..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementorTest.java +++ /dev/null @@ -1,328 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.MyObject; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.FunctionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.VariableOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonCode; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.wrappers.JavaMethodReference; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class FunctionImplementorTest { - @Test - public void testCallFunction() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadParameter("item") - .getAttribute("concatToName") - .loadConstant(" is awesome!") - .callFunction(1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - MyObject object = new MyObject(); - object.name = "My name"; - assertThat(javaFunction.apply(object)).isEqualTo("My name is awesome!"); - } - - public static int keywordTestFunction(int first, int second, int third) { - return first + 2 * second + 3 * third; - } - - @Test - public void testCallFunctionWithKeywords() throws NoSuchMethodException { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("function") - .loadParameter("function") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .loadConstant(List.of("third", "second")) - .callFunctionWithKeywords(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - PythonLikeFunction pythonLikeFunction = new JavaMethodReference( - FunctionImplementorTest.class.getMethod("keywordTestFunction", int.class, int.class, int.class), - Map.of("first", 0, "second", 1, "third", 2)); - assertThat(javaFunction.apply(pythonLikeFunction)).isEqualTo(13); // 1 + 2*3 + 3*2 - } - - @Test - public void testCallFunctionUnpackIterable() throws NoSuchMethodException { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("function") - .loadParameter("function") - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .tuple(3) - .callFunctionUnpack(false) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - PythonLikeFunction pythonLikeFunction = new JavaMethodReference( - FunctionImplementorTest.class.getMethod("keywordTestFunction", int.class, int.class, int.class), - Map.of("first", 0, "second", 1, "third", 2)); - assertThat(javaFunction.apply(pythonLikeFunction)).isEqualTo(14); // 1 + 2*2 + 3*3 - } - - @Test - public void testCallFunctionUnpackIterableAndKeywords() throws NoSuchMethodException { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("function") - .loadParameter("function") - .loadConstant(1) - .tuple(1) - .loadConstant(2) - .loadConstant(3) - .loadConstant(List.of("third", "second")) - .constDict(2) - .callFunctionUnpack(true) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - PythonLikeFunction pythonLikeFunction = new JavaMethodReference( - FunctionImplementorTest.class.getMethod("keywordTestFunction", int.class, int.class, int.class), - Map.of("first", 0, "second", 1, "third", 2)); - assertThat(javaFunction.apply(pythonLikeFunction)).isEqualTo(13); // 1 + 2*3 + 3*2 - } - - @Test - public void testCallMethodOnType() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadParameter("item") - .loadMethod("concatToName") - .loadConstant(" is awesome!") - .callMethod(1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - MyObject object = new MyObject(); - object.name = "My name"; - assertThat(javaFunction.apply(object)).isEqualTo("My name is awesome!"); - } - - @Test - public void testCallMethodOnInstance() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadParameter("item") - .loadMethod("attributeFunction") - .loadConstant(" is awesome!") - .callMethod(1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - MyObject object = new MyObject(); - object.attributeFunction = - (positionalArgs, namedArgs, callerInstance) -> PythonString.valueOf("My name" + positionalArgs.get(0)); - - assertThat(javaFunction.apply(object)).isEqualTo("My name is awesome!"); - } - - @Test - public void testMakeFunction() { - PythonCompiledFunction dependentFunction = PythonFunctionBuilder.newFunction() - .loadFreeVariable("a") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Class dependentFunctionClass = PythonBytecodeToJavaBytecodeTranslator - .translatePythonBytecodeToClass(dependentFunction, PythonLikeFunction.class); - - PythonCompiledFunction parentFunction = PythonFunctionBuilder.newFunction() - .loadConstant(0) - .storeCellVariable("a") - .op(VariableOpDescriptor.LOAD_CLOSURE, 0) - .tuple(1) - .loadConstant(dependentFunctionClass) - .loadConstant("parent.sub") - .op(FunctionOpDescriptor.MAKE_FUNCTION, 8) - .storeVariable("sub") - .loadConstant(1) - .storeCellVariable("a") - .loadVariable("sub") - .callFunction(0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(parentFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(1); - } - - @Test - public void testMakeFunctionComplex() { - PythonCompiledFunction secondDependentFunction = PythonFunctionBuilder.newFunction() - .loadFreeVariable("sub1") - .loadFreeVariable("sub2") - .loadFreeVariable("parent") - .op(DunderOpDescriptor.BINARY_ADD) - .op(DunderOpDescriptor.BINARY_ADD) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Class secondDependentFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(secondDependentFunction, - PythonLikeFunction.class); - - PythonCompiledFunction firstDependentFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .storeCellVariable("sub1") - .loadConstant(100) - .storeCellVariable("sub2") - .loadFreeVariable("parent") - .op(StackOpDescriptor.POP_TOP) - .op(VariableOpDescriptor.LOAD_CLOSURE, 0) - .op(VariableOpDescriptor.LOAD_CLOSURE, 1) - .op(VariableOpDescriptor.LOAD_CLOSURE, 2) - .tuple(3) - .loadConstant(secondDependentFunctionClass) - .loadConstant("parent.sub.sub") - .op(FunctionOpDescriptor.MAKE_FUNCTION, 8) - .storeVariable("function") - .loadConstant(300) - .storeCellVariable("sub2") - .loadVariable("function") - .callFunction(0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Class firstDependentFunctionClass = PythonBytecodeToJavaBytecodeTranslator - .translatePythonBytecodeToClass(firstDependentFunction, PythonLikeFunction.class); - - PythonCompiledFunction parentFunction = PythonFunctionBuilder.newFunction() - .loadConstant(10) - .storeCellVariable("parent") - .op(VariableOpDescriptor.LOAD_CLOSURE, 0) - .tuple(1) - .loadConstant(firstDependentFunctionClass) - .loadConstant("parent.sub") - .op(FunctionOpDescriptor.MAKE_FUNCTION, 8) - .storeVariable("sub") - .loadConstant(20) - .storeCellVariable("parent") - .loadVariable("sub") - .callFunction(0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(parentFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(321); - } - - @Test - public void testEnumDir() { - // Test a complicated list comp used in enums (ensuring it creates valid Java bytecode) - PythonCompiledFunction firstListComp = PythonFunctionBuilder.newFunction(".0") - .list(0) - .loadParameter(".0") - .loop(loopBuilder -> { - loopBuilder.storeVariable("cls") - .loadVariable("cls") - .getAttribute("__dict__") - .op(CollectionOpDescriptor.GET_ITER) - .loop(innerLoopBuilder -> { - innerLoopBuilder.storeVariable("m") - .loadVariable("m") - .loadConstant(0) - .op(DunderOpDescriptor.BINARY_SUBSCR) - .loadConstant("_") - .compare(CompareOp.NOT_EQUALS) - .ifTrue(ifBuilder -> { - ifBuilder.loadVariable("m") - .loadFreeVariable("self") - .getAttribute("_member_map_") - .op(CollectionOpDescriptor.CONTAINS_OP, 1) - .ifTrue(innerIfBuilder -> { - ifBuilder.loadVariable("m") - .op(CollectionOpDescriptor.LIST_APPEND, 3); - }); - }); - }); - }) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction secondListComp = PythonFunctionBuilder.newFunction(".0") - .list(0) - .loadParameter(".0") - .loop(loopBuilder -> { - loopBuilder.storeVariable("m") - .loadVariable("m") - .loadConstant(0) - .op(DunderOpDescriptor.BINARY_SUBSCR) - .loadConstant("_") - .compare(CompareOp.NOT_EQUALS) - .ifTrue(ifBuilder -> { - ifBuilder.loadVariable("m") - .op(CollectionOpDescriptor.LIST_APPEND, 2); - }); - }) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCode firstListCompCode = new PythonCode( - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(firstListComp, PythonLikeFunction.class)); - - PythonCode secondListCompCode = new PythonCode( - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(secondListComp, - PythonLikeFunction.class)); - - PythonCompiledFunction dirFunction = PythonFunctionBuilder.newFunction("self") - .op(VariableOpDescriptor.LOAD_CLOSURE, 0) - .tuple(1) - .loadConstant(firstListCompCode) - .loadConstant("Enum.__dir__..") - .op(FunctionOpDescriptor.MAKE_FUNCTION, 8) - .loadCellVariable("self") - .getAttribute("__class__") - .loadMethod("mro") - .callMethod(0) - .op(CollectionOpDescriptor.GET_ITER) - .callFunction(1) - .loadConstant(secondListCompCode) - .loadConstant("Enum.__dir__..") - .op(FunctionOpDescriptor.MAKE_FUNCTION, 0) - .loadCellVariable("self") - .getAttribute("__dict__") - .op(CollectionOpDescriptor.GET_ITER) - .callFunction(1) - .op(DunderOpDescriptor.BINARY_ADD) - .storeVariable("added_behavior") - .list(0) - .loadConstant(List.of("__class__", "__doc__", "__module__")) - .op(CollectionOpDescriptor.LIST_EXTEND, 1) - .loadVariable("added_behavior") - .op(DunderOpDescriptor.BINARY_ADD) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(dirFunction, Function.class); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementorTest.java deleted file mode 100644 index 18331f56..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementorTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.LongToIntFunction; -import java.util.function.Predicate; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class JavaPythonTypeConversionImplementorTest { - @Test - public void testLoadParameter() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("b") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - - assertThat(javaFunction.apply("a", "b")).isEqualTo("b"); - assertThat(javaFunction.apply(1, 2)).isEqualTo(2); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - assertThat(javaFunction.apply("a", "b")).isEqualTo("a"); - assertThat(javaFunction.apply(1, 2)).isEqualTo(1); - } - - @Test - public void testLoadPrimitiveParameter() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - LongToIntFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, LongToIntFunction.class); - - assertThat(javaFunction.applyAsInt(1L)).isEqualTo(1); - } - - @Test - public void testReturnBoolean() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("ignored") - .loadConstant(Boolean.TRUE) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Predicate javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Predicate.class); - - assertThat(javaFunction.test("Hi")).isEqualTo(true); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction("ignored") - .loadConstant(Boolean.FALSE) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Predicate.class); - - assertThat(javaFunction.test("Hi")).isEqualTo(false); - } - - @Test - public void testReturnVoid() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("ignored") - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Consumer javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Consumer.class); - - assertThatCode(() -> javaFunction.accept(null)).doesNotThrowAnyException(); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JumpImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JumpImplementorTest.java deleted file mode 100644 index 40491df1..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/JumpImplementorTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class JumpImplementorTest { - @Test - public void testIfTrue() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTrue(block -> { - block.loadConstant(10); - block.op(ControlOpDescriptor.RETURN_VALUE); - }) - .loadConstant(-10) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(1L)).isEqualTo(10L); - assertThat(javaFunction.apply(10L)).isEqualTo(-10L); - } - - @Test - public void testIfFalse() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifFalse(block -> { - block.loadConstant(10); - block.op(ControlOpDescriptor.RETURN_VALUE); - }) - .loadConstant(-10) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(1L)).isEqualTo(-10L); - assertThat(javaFunction.apply(10L)).isEqualTo(10L); - } - - @Test - public void testIfTrueKeepTop() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifTruePopTop(block -> { - block.loadConstant(true); - block.op(ControlOpDescriptor.RETURN_VALUE); - }) - .op(ControlOpDescriptor.RETURN_VALUE) // Top is False (block was skipped) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(1L)).isEqualTo(PythonBoolean.TRUE); - assertThat(javaFunction.apply(10L)).isEqualTo(PythonBoolean.FALSE); - } - - @Test - public void testIfFalseKeepTop() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadParameter("a") - .loadConstant(5) - .compare(CompareOp.LESS_THAN) - .ifFalsePopTop(block -> { - block.loadConstant(false); - block.op(ControlOpDescriptor.RETURN_VALUE); - }) - .op(ControlOpDescriptor.RETURN_VALUE) // Top is True (block was skipped) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - assertThat(javaFunction.apply(1L)).isEqualTo(PythonBoolean.TRUE); - assertThat(javaFunction.apply(10L)).isEqualTo(PythonBoolean.FALSE); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementorTest.java deleted file mode 100644 index 3e2b8ef2..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ModuleImplementorTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonModule; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class ModuleImplementorTest { - - @Test - public void testImportName() { - Map globalsMap = Map.of("__module__", PythonString.valueOf("__main__")); - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("from_list", "level") - .usingGlobalsMap(globalsMap) - .loadParameter("level") - .loadParameter("from_list") - .loadModule("module") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - PythonModule module = new PythonModule(new HashMap<>()); - - when(interpreter.importModule(PythonInteger.ZERO, List.of(), globalsMap, Map.of(), "module")).thenReturn(module); - - Class javaFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(pythonCompiledFunction, BiFunction.class); - BiFunction javaFunction = PythonBytecodeToJavaBytecodeTranslator.createInstance(javaFunctionClass, interpreter); - - assertThat(javaFunction.apply(List.of(), PythonInteger.ZERO)).isSameAs(module); - verify(interpreter).importModule(PythonInteger.ZERO, List.of(), globalsMap, Map.of(), "module"); - } - - @Test - public void testImportNameWithAttributes() { - Map globalsMap = Map.of("__module__", PythonString.valueOf("__main__")); - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("from_list", "level") - .usingGlobalsMap(globalsMap) - .loadParameter("level") - .loadParameter("from_list") - .loadModule("module") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - PythonModule module = new PythonModule(new HashMap<>()); - - when(interpreter.importModule(PythonInteger.ZERO, List.of(PythonString.valueOf("item")), globalsMap, Map.of(), - "module")).thenReturn(module); - - Class javaFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(pythonCompiledFunction, BiFunction.class); - BiFunction javaFunction = PythonBytecodeToJavaBytecodeTranslator.createInstance(javaFunctionClass, interpreter); - - assertThat(javaFunction.apply(List.of(PythonString.valueOf("item")), PythonInteger.ZERO)).isSameAs(module); - verify(interpreter).importModule(PythonInteger.ZERO, List.of(PythonString.valueOf("item")), globalsMap, Map.of(), - "module"); - } - - @Test - public void testImportFrom() { - Map globalsMap = Map.of("__module__", PythonString.valueOf("__main__")); - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .usingGlobalsMap(globalsMap) - .loadConstant(0) - .loadConstant(List.of(PythonString.valueOf("item1"), PythonString.valueOf("item2"))) - .loadModule("module") - .getFromModule("item1") - .storeVariable("a") - .getFromModule("item2") - .storeVariable("b") - .op(StackOpDescriptor.POP_TOP) - .loadVariable("a") - .loadVariable("b") - .op(DunderOpDescriptor.BINARY_ADD) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - PythonModule module = new PythonModule(new HashMap<>()); - module.addItem("item1", PythonInteger.valueOf(1)); - module.addItem("item2", PythonInteger.valueOf(2)); - - when(interpreter.importModule(PythonInteger.ZERO, List.of(PythonString.valueOf("item1"), PythonString.valueOf("item2")), - globalsMap, Map.of(), "module")).thenReturn(module); - - Class javaFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(pythonCompiledFunction, Supplier.class); - Supplier javaFunction = PythonBytecodeToJavaBytecodeTranslator.createInstance(javaFunctionClass, interpreter); - - assertThat(javaFunction.get()).isEqualTo(PythonInteger.valueOf(3)); - verify(interpreter).importModule(PythonInteger.ZERO, - List.of(PythonString.valueOf("item1"), PythonString.valueOf("item2")), globalsMap, Map.of(), "module"); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementorTest.java deleted file mode 100644 index d32e2843..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementorTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.MyObject; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ObjectOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class ObjectImplementorTest { - - @Test - public void testIs() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .op(StackOpDescriptor.DUP_TOP) - .op(ObjectOpDescriptor.IS_OP, 0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(PythonBoolean.TRUE); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .op(StackOpDescriptor.DUP_TOP) - .loadConstant(0) - .op(DunderOpDescriptor.BINARY_ADD) - .op(ObjectOpDescriptor.IS_OP, 0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(PythonBoolean.FALSE); - - // Test is not - pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .op(StackOpDescriptor.DUP_TOP) - .op(ObjectOpDescriptor.IS_OP, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(PythonBoolean.FALSE); - - pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(5) - .op(StackOpDescriptor.DUP_TOP) - .loadConstant(0) - .op(DunderOpDescriptor.BINARY_ADD) - .op(ObjectOpDescriptor.IS_OP, 1) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - javaFunction = PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo(PythonBoolean.TRUE); - } - - @Test - public void testGetAttribute() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadParameter("item") - .getAttribute("name") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - MyObject object = new MyObject(); - object.name = "My name"; - assertThat(javaFunction.apply(object)).isEqualTo("My name"); - } - - @Test - public void testSetAttribute() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item", "value") - .loadParameter("value") - .loadParameter("item") - .storeAttribute("name") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiConsumer javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiConsumer.class); - MyObject object = new MyObject(); - object.name = "My name"; - javaFunction.accept(object, "New name"); - assertThat(object.name).isEqualTo("New name"); - } - -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementorTest.java deleted file mode 100644 index e8db923a..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementorTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -public class StackManipulationImplementorTest { - @Test - public void testRot2() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant(2) - .op(StackOpDescriptor.ROT_TWO) - .tuple(2) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - - assertThat(javaFunction.get()).isEqualTo(List.of(2, 1)); - } - - @Test - public void testRot3() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .op(StackOpDescriptor.ROT_THREE) - .tuple(3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - - assertThat(javaFunction.get()).isEqualTo(List.of(3, 1, 2)); - } - - @Test - public void testRot4() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction() - .loadConstant(1) - .loadConstant(2) - .loadConstant(3) - .loadConstant(4) - .op(StackOpDescriptor.ROT_FOUR) - .tuple(4) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - - assertThat(javaFunction.get()).isEqualTo(List.of(4, 1, 2, 3)); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StringImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StringImplementorTest.java deleted file mode 100644 index c85c7a25..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/StringImplementorTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StringOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonNone; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class StringImplementorTest { - @Test - public void testBuildString() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("a") - .loadConstant("My name") - .loadConstant(" is ") - .loadConstant("awesome!") - .op(StringOpDescriptor.BUILD_STRING, 3) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Supplier javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Supplier.class); - assertThat(javaFunction.get()).isEqualTo("My name is awesome!"); - } - - @Test - public void testFormat() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item", "format") - .loadParameter("item") - .loadParameter("format") - .op(StringOpDescriptor.FORMAT_VALUE, 4) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - BiFunction javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, BiFunction.class); - - assertThat(javaFunction.apply(12, "x")).isEqualTo("c"); - assertThat(javaFunction.apply(12, "o")).isEqualTo("14"); - assertThat(javaFunction.apply(12, "")).isEqualTo("12"); - assertThat(javaFunction.apply("hello", "")).isEqualTo("hello"); - } - - @Test - public void testFormatWithoutParameter() { - PythonCompiledFunction pythonCompiledFunction = PythonFunctionBuilder.newFunction("item") - .loadParameter("item") - .op(StringOpDescriptor.FORMAT_VALUE, 0) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - Function javaFunction = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(pythonCompiledFunction, Function.class); - - assertThat(javaFunction.apply(12)).isEqualTo("12"); - assertThat(javaFunction.apply("hello")).isEqualTo("hello"); - } - - @Test - public void testPrint() { - PythonCompiledFunction compiledFunction = PythonFunctionBuilder.newFunction("to_print") - .loadParameter("to_print") - .op(StringOpDescriptor.PRINT_EXPR) - .loadConstant(null) - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - - Mockito.when(interpreter.getGlobal(Mockito.anyMap(), Mockito.same("print"))) - .thenReturn(((PythonLikeFunction) (pos, keywords, callerInstance) -> { - interpreter.write(UnaryDunderBuiltin.STR.invoke(pos.get(0)) + "\n"); - return PythonNone.INSTANCE; - })); - - Class functionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(compiledFunction, Consumer.class); - - Consumer consumer = PythonBytecodeToJavaBytecodeTranslator.createInstance(functionClass, interpreter); - - consumer.accept("Value 1"); - - Mockito.verify(interpreter).write("Value 1\n"); - - Mockito.clearInvocations(interpreter); - - consumer.accept(10); - - Mockito.verify(interpreter).write("10\n"); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/VariableImplementorTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/VariableImplementorTest.java deleted file mode 100644 index 35d7bdbf..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/implementors/VariableImplementorTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package ai.timefold.jpyinterpreter.implementors; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonInterpreter; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class VariableImplementorTest { - @Test - public void testGlobalVariables() { - PythonCompiledFunction setterCompiledFunction = PythonFunctionBuilder.newFunction("value") - .loadParameter("value") - .storeGlobalVariable("my_global") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - PythonCompiledFunction getterCompiledFunction = PythonFunctionBuilder.newFunction() - .loadGlobalVariable("my_global") - .op(ControlOpDescriptor.RETURN_VALUE) - .build(); - - AtomicReference myGlobalReference = new AtomicReference<>(); - PythonInterpreter interpreter = Mockito.mock(PythonInterpreter.class); - - Mockito.when(interpreter.getGlobal(Mockito.any(), Mockito.eq("my_global"))) - .thenAnswer(invocationOnMock -> myGlobalReference.get()); - Mockito.doAnswer(invocationOnMock -> { - myGlobalReference.set(invocationOnMock.getArgument(2, PythonLikeObject.class)); - return null; - }).when(interpreter).setGlobal(Mockito.any(), Mockito.eq("my_global"), Mockito.any()); - - Class setterFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(setterCompiledFunction, Consumer.class); - Class getterFunctionClass = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecodeToClass(getterCompiledFunction, Supplier.class); - - Consumer setter = PythonBytecodeToJavaBytecodeTranslator.createInstance(setterFunctionClass, interpreter); - Supplier getter = PythonBytecodeToJavaBytecodeTranslator.createInstance(getterFunctionClass, interpreter); - - setter.accept("Value 1"); - - Mockito.verify(interpreter).setGlobal(Mockito.any(), Mockito.eq("my_global"), - Mockito.eq(PythonString.valueOf("Value 1"))); - assertThat(getter.get()).isEqualTo(PythonString.valueOf("Value 1")); - - setter.accept("Value 2"); - - Mockito.verify(interpreter).setGlobal(Mockito.any(), Mockito.eq("my_global"), - Mockito.eq(PythonString.valueOf("Value 2"))); - assertThat(getter.get()).isEqualTo(PythonString.valueOf("Value 2")); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/PythonStringTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/PythonStringTest.java deleted file mode 100644 index 1d55d734..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/PythonStringTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package ai.timefold.jpyinterpreter.types; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -class PythonStringTest { - - // Other methods are tested in test_str.py - // These methods are tested here since they are internal, - // and has edge cases CPython won't hit - - @Test - void asAsciiBytes() { - var simple = PythonString.valueOf("abc"); - assertThat(simple.asAsciiBytes().asByteArray()).isEqualTo(new byte[] { 'a', 'b', 'c' }); - - var unicode = PythonString.valueOf("π"); - // UTF-16 encoding - assertThat(unicode.asAsciiBytes().asByteArray()).isEqualTo(new byte[] { (byte) 0x03, (byte) 0xC0 }); - - var mixed = PythonString.valueOf("aπc"); - // UTF-16 encoding - assertThat(mixed.asAsciiBytes().asByteArray()).isEqualTo(new byte[] { 'a', (byte) 0x03, (byte) 0xC0, 'c' }); - } - - @Test - void asAsciiByteArray() { - var simple = PythonString.valueOf("abc"); - assertThat(simple.asAsciiByteArray().asByteArray()).isEqualTo(new byte[] { 'a', 'b', 'c' }); - - var unicode = PythonString.valueOf("π"); - // UTF-16 encoding - assertThat(unicode.asAsciiByteArray().asByteArray()).isEqualTo(new byte[] { (byte) 0x03, (byte) 0xC0 }); - - var mixed = PythonString.valueOf("aπc"); - // UTF-16 encoding - assertThat(mixed.asAsciiByteArray().asByteArray()).isEqualTo(new byte[] { 'a', (byte) 0x03, (byte) 0xC0, 'c' }); - } -} \ No newline at end of file diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTest.java deleted file mode 100644 index 78940704..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.time.Duration; -import java.time.LocalDate; - -import ai.timefold.jpyinterpreter.types.PythonString; - -import org.junit.jupiter.api.Test; - -public class PythonDateTest { - @Test - public void testIsoFormat() { - PythonDate pythonDate = new PythonDate(LocalDate.of(2002, 3, 11)); - assertThat(pythonDate.iso_format()).isEqualTo(PythonString.valueOf("2002-03-11")); - } - - @Test - public void testCTime() { - PythonDate pythonDate = new PythonDate(LocalDate.of(2002, 3, 11)); - assertThat(pythonDate.ctime()).isEqualTo(PythonString.valueOf("Mon Mar 11 00:00:00 2002")); - } - - @Test - public void testAddTimeDelta() { - PythonDate a = new PythonDate(LocalDate.of(2000, 1, 1)); - PythonTimeDelta timeDelta = new PythonTimeDelta(Duration.ofDays(2)); - assertThat(a.add_time_delta(timeDelta)).isEqualTo(new PythonDate(LocalDate.of(2000, 1, 3))); - } - - @Test - public void testSubtractTimeDelta() { - PythonDate a = new PythonDate(LocalDate.of(2000, 1, 1)); - PythonTimeDelta timeDelta = new PythonTimeDelta(Duration.ofDays(2)); - assertThat(a.subtract_time_delta(timeDelta)).isEqualTo(new PythonDate(LocalDate.of(1999, 12, 30))); - } - - @Test - public void testSubtractDate() { - PythonDate a = new PythonDate(LocalDate.of(2000, 1, 1)); - PythonDate b = new PythonDate(LocalDate.of(2000, 1, 3)); - assertThat(b.subtract_date(a)).isEqualTo(new PythonTimeDelta(Duration.ofDays(2L))); - assertThat(a.subtract_date(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(-2L))); - } - - @Test - public void testCompareDate() { - PythonDate a = new PythonDate(LocalDate.of(2000, 1, 1)); - PythonDate b = new PythonDate(LocalDate.of(2000, 1, 3)); - assertThat(b.compareTo(a)).isGreaterThan(0); - assertThat(a.compareTo(b)).isLessThan(0); - assertThat(a.compareTo(a)).isEqualTo(0); - assertThat(a.equals(b)).isFalse(); - assertThat(a.equals(a)).isTrue(); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeTest.java deleted file mode 100644 index 2203ab2d..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonDateTimeTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.time.Duration; -import java.time.LocalDateTime; - -import ai.timefold.jpyinterpreter.types.PythonString; - -import org.junit.jupiter.api.Test; - -public class PythonDateTimeTest { - @Test - public void testIsoFormat() { - PythonDateTime pythonDateTime = new PythonDateTime(LocalDateTime.of(2002, 3, 11, 1, 30, 45)); - assertThat(pythonDateTime.iso_format()).isEqualTo(PythonString.valueOf("2002-03-11T01:30:45")); - } - - @Test - public void testCTime() { - PythonDateTime pythonDateTime = new PythonDateTime(LocalDateTime.of(2002, 3, 11, 1, 30, 45)); - assertThat(pythonDateTime.ctime()).isEqualTo(PythonString.valueOf("Mon Mar 11 01:30:45 2002")); - } - - @Test - public void testAddTimeDelta() { - PythonDateTime a = new PythonDateTime(LocalDateTime.of(2000, 1, 1, 0, 0, 0)); - PythonTimeDelta timeDelta = new PythonTimeDelta(Duration.ofDays(2).plusHours(1)); - assertThat(a.add_time_delta(timeDelta)).isEqualTo(new PythonDateTime(LocalDateTime.of(2000, 1, 3, 1, 0, 0))); - } - - @Test - public void testSubtractTimeDelta() { - PythonDateTime a = new PythonDateTime(LocalDateTime.of(2000, 1, 3, 1, 0, 0)); - PythonTimeDelta timeDelta = new PythonTimeDelta(Duration.ofDays(2).plusHours(1)); - assertThat(a.subtract_time_delta(timeDelta)).isEqualTo(new PythonDateTime(LocalDateTime.of(2000, 1, 1, 0, 0, 0))); - } - - @Test - public void testSubtractDateTime() { - PythonDateTime a = new PythonDateTime(LocalDateTime.of(2000, 1, 1, 0, 0, 0)); - PythonDateTime b = new PythonDateTime(LocalDateTime.of(2000, 1, 2, 2, 3, 4)); - assertThat(b.subtract_date_time(a)).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L) - .plusHours(2L) - .plusMinutes(3L) - .plusSeconds(4L))); - assertThat(a.subtract_date_time(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L) - .plusHours(2L) - .plusMinutes(3L) - .plusSeconds(4L) - .negated())); - } - - @Test - public void testCompareDateTime() { - PythonDateTime a = new PythonDateTime(LocalDateTime.of(2000, 1, 1, 0, 0, 0)); - PythonDateTime b = new PythonDateTime(LocalDateTime.of(2000, 1, 3, 0, 0, 0)); - assertThat(b.compareTo(a)).isGreaterThan(0); - assertThat(a.compareTo(b)).isLessThan(0); - assertThat(a.compareTo(a)).isEqualTo(0); - assertThat(a.equals(b)).isFalse(); - assertThat(a.equals(a)).isTrue(); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDeltaTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDeltaTest.java deleted file mode 100644 index 1cf34323..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeDeltaTest.java +++ /dev/null @@ -1,208 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.nio.file.Path; -import java.time.Duration; -import java.util.List; -import java.util.function.BiPredicate; -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.numeric.PythonNumber; -import ai.timefold.jpyinterpreter.util.PythonFunctionBuilder; - -import org.junit.jupiter.api.Test; - -public class PythonTimeDeltaTest { - @Test - public void testConstructTimeDeltaPositional() { - PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = Path.of("target/generated-classes"); - PythonFunctionBuilder builder = PythonFunctionBuilder.newFunction("days") - .loadConstant(PythonTimeDelta.TIME_DELTA_TYPE) - .loadParameter("days") - .callFunction(1) - .op(ControlOpDescriptor.RETURN_VALUE); - - Function constructor = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(builder.build(), - Function.class, - List.of(PythonNumber.class, - PythonTimeDelta.class)); - assertThat(constructor.apply(PythonInteger.valueOf(30))).isEqualTo(new PythonTimeDelta(Duration.ofDays(30))); - assertThat(constructor.apply(PythonFloat.valueOf(1.5))) - .isEqualTo(new PythonTimeDelta(Duration.ofDays(1).plusHours(12))); - } - - @Test - public void testConstructTimeDeltaKeywords() { - PythonFunctionBuilder builder = PythonFunctionBuilder.newFunction("minutes") - .loadConstant(PythonTimeDelta.TIME_DELTA_TYPE) - .loadParameter("minutes") - .loadConstant("minutes") - .tuple(1) - .callFunctionWithKeywords(1) - .op(ControlOpDescriptor.RETURN_VALUE); - - Function constructor = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(builder.build(), - Function.class, - List.of(PythonNumber.class, - PythonTimeDelta.class)); - assertThat(constructor.apply(PythonInteger.valueOf(30))).isEqualTo(new PythonTimeDelta(Duration.ofMinutes(30))); - assertThat(constructor.apply(PythonFloat.valueOf(1.5))) - .isEqualTo(new PythonTimeDelta(Duration.ofMinutes(1).plusSeconds(30))); - } - - @Test - public void testAddTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonTimeDelta b = new PythonTimeDelta(Duration.ofHours(12L)); - assertThat(a.add_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L).plusHours(12L))); - } - - @Test - public void testSubtractTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonTimeDelta b = new PythonTimeDelta(Duration.ofHours(12L)); - assertThat(a.subtract_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L).minusHours(12L))); - assertThat(b.subtract_time_delta(a)).isEqualTo(new PythonTimeDelta(Duration.ofHours(12L).minusDays(1L))); - } - - @Test - public void testIntegerMultiplyTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonInteger b = PythonInteger.valueOf(3); - assertThat(a.get_integer_multiple(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(3L))); - } - - @Test - public void testFloatMultiplyTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonFloat b = PythonFloat.valueOf(2.5); - assertThat(a.get_float_multiple(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(2L).plusHours(12))); - } - - @Test - public void testDivideByTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonTimeDelta b = new PythonTimeDelta(Duration.ofHours(16L)); - assertThat(a.divide_time_delta(b)).isEqualTo(new PythonFloat(1.5)); - } - - @Test - public void testDivideByInt() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonInteger b = PythonInteger.valueOf(2); - assertThat(a.divide_integer(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(12L))); - } - - @Test - public void testDivideByFloat() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonFloat b = PythonFloat.valueOf(0.5); - assertThat(a.divide_float(b)).isEqualTo(new PythonTimeDelta(Duration.ofDays(2L))); - } - - @Test - public void testFloorDivideByTimeDelta() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonTimeDelta b = new PythonTimeDelta(Duration.ofHours(16L)); - assertThat(a.floor_divide_time_delta(b)).isEqualTo(PythonInteger.valueOf(1)); - } - - @Test - public void testFloorDivideByInteger() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonInteger b = PythonInteger.valueOf(2); - assertThat(a.floor_divide_integer(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(12L))); - } - - @Test - public void testTimeDeltaRemainder() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - PythonTimeDelta b = new PythonTimeDelta(Duration.ofHours(16L)); - assertThat(a.remainder_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(8L))); - - a = new PythonTimeDelta(Duration.ofHours(-15L)); - b = new PythonTimeDelta(Duration.ofHours(10L)); - assertThat(a.remainder_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(5L))); - - a = new PythonTimeDelta(Duration.ofHours(15L)); - b = new PythonTimeDelta(Duration.ofHours(-10L)); - assertThat(a.remainder_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(-5L))); - - a = new PythonTimeDelta(Duration.ofHours(-15L)); - b = new PythonTimeDelta(Duration.ofHours(-10L)); - assertThat(a.remainder_time_delta(b)).isEqualTo(new PythonTimeDelta(Duration.ofHours(-5L))); - } - - @Test - public void testTimeDeltaCopy() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - assertThat(a.pos()).isEqualTo(a); - } - - @Test - public void testTimeDeltaNegate() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L)); - assertThat(a.negate()).isEqualTo(new PythonTimeDelta(Duration.ofDays(-1L))); - } - - @Test - public void testTimeDeltaAbs() { - assertThat(new PythonTimeDelta(Duration.ofDays(1L)).abs()).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L))); - assertThat(new PythonTimeDelta(Duration.ofDays(-1L)).abs()).isEqualTo(new PythonTimeDelta(Duration.ofDays(1L))); - } - - @Test - public void testTimeDeltaToString() { - PythonTimeDelta a = new PythonTimeDelta(Duration.ofDays(1L).plusHours(6).plusMinutes(30).plusSeconds(15)); - assertThat(a.toString()).isEqualTo("1 day, 6:30:15"); - - a = new PythonTimeDelta(Duration.ofHours(6).plusMinutes(30).plusSeconds(15)); - assertThat(a.toString()).isEqualTo("6:30:15"); - - a = new PythonTimeDelta(Duration.ofDays(2).plusHours(6).plusMinutes(30).plusSeconds(15).plusMillis(333L)); - assertThat(a.toString()).isEqualTo("2 days, 6:30:15.333000"); - } - - @Test - public void testCompareTimeDelta() { - PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = Path.of("target", "generated-classes"); - PythonFunctionBuilder builder = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.LESS_THAN) - .op(ControlOpDescriptor.RETURN_VALUE); - - BiPredicate lessThan = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(builder.build(), - BiPredicate.class, - List.of(PythonTimeDelta.class, - PythonTimeDelta.class)); - assertThat(lessThan.test(new PythonTimeDelta(Duration.ofDays(1)), new PythonTimeDelta(Duration.ofDays(2)))).isTrue(); - assertThat(lessThan.test(new PythonTimeDelta(Duration.ofDays(2)), new PythonTimeDelta(Duration.ofDays(1)))).isFalse(); - } - - @Test - public void testEqualsTimeDelta() { - PythonFunctionBuilder builder = PythonFunctionBuilder.newFunction("a", "b") - .loadParameter("a") - .loadParameter("b") - .compare(CompareOp.EQUALS) - .op(ControlOpDescriptor.RETURN_VALUE); - - BiPredicate lessThan = - PythonBytecodeToJavaBytecodeTranslator.translatePythonBytecode(builder.build(), - BiPredicate.class, - List.of(PythonTimeDelta.class, - PythonTimeDelta.class)); - assertThat(lessThan.test(new PythonTimeDelta(Duration.ofDays(1)), new PythonTimeDelta(Duration.ofDays(1)))).isTrue(); - assertThat(lessThan.test(new PythonTimeDelta(Duration.ofDays(1)), new PythonTimeDelta(Duration.ofDays(2)))).isFalse(); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeTest.java deleted file mode 100644 index d6ffed81..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/datetime/PythonTimeTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.jpyinterpreter.types.datetime; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.time.LocalTime; - -import ai.timefold.jpyinterpreter.types.PythonString; - -import org.junit.jupiter.api.Test; - -public class PythonTimeTest { - @Test - public void testIsoFormat() { - PythonTime pythonTime = new PythonTime(LocalTime.of(1, 30, 45)); - assertThat(pythonTime.isoformat(PythonString.valueOf("auto"))).isEqualTo(PythonString.valueOf("01:30:45")); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapperTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapperTest.java deleted file mode 100644 index 245e0d87..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapperTest.java +++ /dev/null @@ -1,143 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonBinaryOperator; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.PythonLikeFunction; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.jpyinterpreter.types.wrappers.inaccessible.PublicInterface; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class JavaObjectWrapperTest { - public record TestObject(String name) { - public String getName() { - return name; - } - } - - @Test - void testEquals() { - assertThat(new TestObject("a")).isEqualTo(new TestObject("a")); - assertThat(new TestObject("a")).isNotEqualTo(new TestObject("b")); - } - - @Test - void testHashCode() { - assertThat(new TestObject("a").hashCode()).isEqualTo(new TestObject("a").hashCode()); - } - - @Test - void testCallingMethod() { - TestObject object = new TestObject("My name"); - JavaObjectWrapper wrapper = new JavaObjectWrapper(object); - PythonLikeObject wrappedFunction = wrapper.$method$__getattribute__(PythonString.valueOf("getName")); - assertThat(wrappedFunction).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) wrappedFunction; - assertThat(function.$call(List.of(), Map.of(), null)).isEqualTo(PythonString.valueOf("My name")); - } - - @Test - void testCallingMethodOnInaccessibleClass() { - PublicInterface object = PublicInterface.getInstance(); - JavaObjectWrapper wrapper = new JavaObjectWrapper(object); - PythonLikeObject wrappedFunction = wrapper.$method$__getattribute__(PythonString.valueOf("interfaceMethod")); - assertThat(wrappedFunction).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) wrappedFunction; - assertThat(function.$call(List.of(), Map.of(), null)).isEqualTo(PythonString.valueOf("PrivateObject")); - } - - @Test - void testCallingRecordGetter() { - TestObject object = new TestObject("My name"); - JavaObjectWrapper wrapper = new JavaObjectWrapper(object); - PythonLikeObject result = wrapper.$method$__getattribute__(PythonString.valueOf("name")); - if (result instanceof PythonLikeFunction getter) { - assertThat(getter.$call(List.of(), Map.of(), null)) - .isEqualTo(PythonString.valueOf("My name")); - } else { - Assertions.fail("Not an instance of a function"); - } - - } - - @Test - void testComparable() { - JavaObjectWrapper v1 = new JavaObjectWrapper(1); - JavaObjectWrapper v2 = new JavaObjectWrapper(2); - JavaObjectWrapper v3 = new JavaObjectWrapper(3); - assertThat(v1.compareTo(v1)).isZero(); - assertThat(v1.compareTo(v2)).isNegative(); - assertThat(v1.compareTo(v3)).isNegative(); - - assertThat(v2.compareTo(v1)).isPositive(); - assertThat(v2.compareTo(v2)).isZero(); - assertThat(v2.compareTo(v3)).isNegative(); - - assertThat(v3.compareTo(v1)).isPositive(); - assertThat(v3.compareTo(v2)).isPositive(); - assertThat(v3.compareTo(v3)).isZero(); - } - - @Test - void testIterable() { - JavaObjectWrapper iterable = new JavaObjectWrapper(List.of(1, 2, 3)); - assertThat((Iterable) iterable) - .containsExactly( - new JavaObjectWrapper(1), - new JavaObjectWrapper(2), - new JavaObjectWrapper(3)); - } - - @Test - void testLength() { - JavaObjectWrapper iterable = new JavaObjectWrapper(List.of(1, 2, 3)); - PythonLikeObject object = iterable.$method$__getattribute__( - PythonString.valueOf(PythonUnaryOperator.LENGTH.getDunderMethod())); - assertThat(object).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) object; - assertThat(function.$call(List.of(), Map.of(), null)).isEqualTo(PythonInteger.valueOf(3)); - } - - @Test - void testGetList() { - JavaObjectWrapper iterable = new JavaObjectWrapper(List.of(1, 2, 3)); - PythonLikeObject object = iterable.$method$__getattribute__( - PythonString.valueOf(PythonBinaryOperator.GET_ITEM.getDunderMethod())); - assertThat(object).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) object; - assertThat(function.$call(List.of(PythonInteger.valueOf(0)), Map.of(), null)).isEqualTo(PythonInteger.valueOf(1)); - assertThat(function.$call(List.of(PythonInteger.valueOf(1)), Map.of(), null)).isEqualTo(PythonInteger.valueOf(2)); - assertThat(function.$call(List.of(PythonInteger.valueOf(2)), Map.of(), null)).isEqualTo(PythonInteger.valueOf(3)); - } - - @Test - void testGetMap() { - JavaObjectWrapper iterable = new JavaObjectWrapper(Map.of("a", 1, "b", 2)); - PythonLikeObject object = iterable.$method$__getattribute__( - PythonString.valueOf(PythonBinaryOperator.GET_ITEM.getDunderMethod())); - assertThat(object).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) object; - assertThat(function.$call(List.of(PythonString.valueOf("a")), Map.of(), null)).isEqualTo(PythonInteger.valueOf(1)); - assertThat(function.$call(List.of(PythonString.valueOf("b")), Map.of(), null)).isEqualTo(PythonInteger.valueOf(2)); - } - - @Test - void testContains() { - JavaObjectWrapper iterable = new JavaObjectWrapper(List.of(1, 2, 3)); - PythonLikeObject object = iterable.$method$__getattribute__( - PythonString.valueOf(PythonBinaryOperator.CONTAINS.getDunderMethod())); - assertThat(object).isInstanceOf(PythonLikeFunction.class); - PythonLikeFunction function = (PythonLikeFunction) object; - assertThat(function.$call(List.of(PythonInteger.valueOf(0)), Map.of(), null)).isEqualTo(PythonBoolean.FALSE); - assertThat(function.$call(List.of(PythonInteger.valueOf(2)), Map.of(), null)).isEqualTo(PythonBoolean.TRUE); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PrivateObject.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PrivateObject.java deleted file mode 100644 index 77942208..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PrivateObject.java +++ /dev/null @@ -1,7 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers.inaccessible; - -record PrivateObject() implements PublicInterface { - public String interfaceMethod() { - return "PrivateObject"; - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PublicInterface.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PublicInterface.java deleted file mode 100644 index 907f8075..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/types/wrappers/inaccessible/PublicInterface.java +++ /dev/null @@ -1,9 +0,0 @@ -package ai.timefold.jpyinterpreter.types.wrappers.inaccessible; - -public interface PublicInterface { - String interfaceMethod(); - - static PublicInterface getInstance() { - return new PrivateObject(); - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/ExceptBuilder.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/ExceptBuilder.java deleted file mode 100644 index c2da00a4..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/ExceptBuilder.java +++ /dev/null @@ -1,184 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ExceptionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.MetaOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.types.PythonLikeType; - -/** - * Builds except and finally blocks - */ -public class ExceptBuilder { - - /** - * The {@link PythonFunctionBuilder} that created this {@link ExceptBuilder}. - */ - final PythonFunctionBuilder delegate; - - /** - * The {@link ControlOpDescriptor#JUMP_ABSOLUTE} instruction at the end - * of the try block, which is where the try should go if it completed without error - * (finally block if it was specified, else catch block). - */ - PythonBytecodeInstruction tryEndGoto; - - /** - * The {@link ExceptionOpDescriptor#SETUP_FINALLY} instruction before the try block that - * handles the case where the exception is not caught - */ - PythonBytecodeInstruction exceptFinallyInstruction; - - final List exceptEndJumpList = new ArrayList<>(); - - boolean hasFinally = false; - - boolean allExceptsExitEarly = true; - - public ExceptBuilder(PythonFunctionBuilder delegate, PythonBytecodeInstruction tryEndGoto, - PythonBytecodeInstruction exceptFinallyInstruction) { - this.delegate = delegate; - this.tryEndGoto = tryEndGoto; - this.exceptFinallyInstruction = exceptFinallyInstruction; - } - - /** - * Add an except block for the {@code type} argument. - * - * @param type The exception type handled by the except block - * @param exceptBuilder The code in the except block - */ - public ExceptBuilder except(PythonLikeType type, Consumer exceptBuilder, - boolean exitEarly) { - - PythonBytecodeInstruction exceptBlockStartInstruction = delegate.instruction(StackOpDescriptor.DUP_TOP) - .markAsJumpTarget(); - delegate.instructionList.add(exceptBlockStartInstruction); - delegate.loadConstant(type); - PythonBytecodeInstruction instruction = delegate.instruction(ControlOpDescriptor.JUMP_IF_NOT_EXC_MATCH); - delegate.instructionList.add(instruction); - - delegate.op(StackOpDescriptor.POP_TOP); - delegate.op(StackOpDescriptor.POP_TOP); - delegate.op(StackOpDescriptor.POP_TOP); - delegate.op(ExceptionOpDescriptor.POP_EXCEPT); - exceptBuilder.accept(delegate); - - if (!exitEarly) { - allExceptsExitEarly = false; - PythonBytecodeInstruction exceptEndJumpInstruction = delegate.instruction(ControlOpDescriptor.JUMP_ABSOLUTE); - delegate.instructionList.add(exceptEndJumpInstruction); - exceptEndJumpList.add(exceptEndJumpInstruction); - } - - delegate.update(instruction.withArg(delegate.instructionList.size())); - return this; - } - - /** - * Creates a finally block. - * - * @param finallyBuilder The code in the finally block - */ - public ExceptBuilder andFinally(Consumer finallyBuilder, boolean exitEarly) { - hasFinally = true; - - if (!exitEarly) { - allExceptsExitEarly = false; - } - - PythonBytecodeInstruction exceptGotoTarget = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(exceptGotoTarget); - - delegate.op(ExceptionOpDescriptor.RERAISE, 0); - - if (tryEndGoto != null) { - delegate.update(tryEndGoto = tryEndGoto.withArg(delegate.instructionList.size())); - } - - for (int i = 0; i < exceptEndJumpList.size(); i++) { - var instruction = exceptEndJumpList.get(i).withArg(delegate.instructionList.size()); - exceptEndJumpList.set(i, instruction); - delegate.update(instruction); - } - - PythonBytecodeInstruction finallyFromTryStartInstruction = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(finallyFromTryStartInstruction); - - finallyBuilder.accept(delegate); // finally from try - - PythonBytecodeInstruction finallyEndInstruction = delegate.instruction(ControlOpDescriptor.JUMP_ABSOLUTE); - delegate.instructionList.add(finallyEndInstruction); - - delegate.update(exceptFinallyInstruction = - exceptFinallyInstruction.withArg(delegate.instructionList.size() - exceptFinallyInstruction.offset() - 1)); - - PythonBytecodeInstruction finallyFromUncaughtStartInstruction = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(finallyFromUncaughtStartInstruction); - - finallyBuilder.accept(delegate); - - delegate.op(ExceptionOpDescriptor.RERAISE); - - delegate.update(finallyEndInstruction.withArg(delegate.instructionList.size())); - - PythonBytecodeInstruction tryCatchBlockEnd = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(tryCatchBlockEnd); - return this; - } - - /** - * Ends the except/finally blocks, returning the {@link PythonFunctionBuilder} that created this - * {@link ExceptBuilder}. - * - * @return the {@link PythonFunctionBuilder} that created this {@link ExceptBuilder}. - */ - public PythonFunctionBuilder tryEnd() { - if (!hasFinally) { - PythonBytecodeInstruction exceptGotoTarget = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(exceptGotoTarget); - - delegate.op(ExceptionOpDescriptor.RERAISE, 0); - } - - if (tryEndGoto == null || tryEndGoto.arg() == 0) { - if (!hasFinally) { - delegate.update(exceptFinallyInstruction = exceptFinallyInstruction - .withArg(delegate.instructionList.size() - exceptFinallyInstruction.offset() - 1)); - PythonBytecodeInstruction reraiseInstruction = - delegate.instruction(ExceptionOpDescriptor.RERAISE) - .withArg(0) - .markAsJumpTarget(); - delegate.instructionList.add(reraiseInstruction); - } - - if (!allExceptsExitEarly) { - if (tryEndGoto != null) { - delegate.update(tryEndGoto = tryEndGoto.withArg(delegate.instructionList.size())); - } - PythonBytecodeInstruction noopInstruction = delegate.instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - delegate.instructionList.add(noopInstruction); - - if (!hasFinally) { - for (int i = 0; i < exceptEndJumpList.size(); i++) { - var instruction = exceptEndJumpList.get(i).withArg(delegate.instructionList.size()); - exceptEndJumpList.set(i, instruction); - delegate.update(instruction); - } - } - } - } - return delegate; - } -} \ No newline at end of file diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java deleted file mode 100644 index bb2ca73d..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java +++ /dev/null @@ -1,700 +0,0 @@ -package ai.timefold.jpyinterpreter.util; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import ai.timefold.jpyinterpreter.CompareOp; -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator; -import ai.timefold.jpyinterpreter.PythonCompiledFunction; -import ai.timefold.jpyinterpreter.PythonExceptionTable; -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.TypeHint; -import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.CollectionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ControlOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.DunderOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ExceptionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.FunctionOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.MetaOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ModuleOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.ObjectOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.OpcodeDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.StackOpDescriptor; -import ai.timefold.jpyinterpreter.opcodes.descriptor.VariableOpDescriptor; - -/** - * A builder for Python bytecode. - */ -public class PythonFunctionBuilder { - - /** - * The list of bytecode instructions - */ - List instructionList = new ArrayList<>(); - - /** - * List of global names used in the bytecode - */ - List co_names = new ArrayList<>(); - - /** - * List of names of local variables in the bytecode - */ - List co_varnames = new ArrayList<>(); - - /** - * List of names of shared variables in the bytecode - */ - List co_cellvars = new ArrayList<>(); - - /** - * List of free variables in the bytecode - */ - List co_freevars = new ArrayList<>(); - - /** - * Constants used in the bytecode - */ - List co_consts = new ArrayList<>(); - - Map globalsMap = new HashMap<>(); - - Map typeAnnotations = new HashMap<>(); - - int co_argcount = 0; - int co_kwonlyargcount = 0; - - /** - * Creates a new function builder for a Python function with the given parameters - * - * @param parameters The names of the function's parameters - * @return - */ - public static PythonFunctionBuilder newFunction(String... parameters) { - PythonFunctionBuilder out = new PythonFunctionBuilder(); - out.co_varnames.addAll(Arrays.asList(parameters)); - out.co_names.addAll(out.co_varnames); - out.co_argcount = parameters.length; - out.co_kwonlyargcount = 0; - return out; - } - - /** - * Creates the bytecode data for the function - * - * @return The bytecode data, which can be used by - * {@link PythonBytecodeToJavaBytecodeTranslator#translatePythonBytecode(PythonCompiledFunction, Class)} or - * {@link PythonBytecodeToJavaBytecodeTranslator#translatePythonBytecodeToClass(PythonCompiledFunction, Class)}. - */ - public PythonCompiledFunction build() { - PythonCompiledFunction out = new PythonCompiledFunction(); - out.module = "test"; - out.qualifiedName = "TestFunction"; - out.instructionList = instructionList; - out.typeAnnotations = typeAnnotations; - out.globalsMap = globalsMap; - out.co_exceptiontable = new PythonExceptionTable(); // we use an empty exception table since it for Python 3.10 - // (i.e. use block try...except instead of co_exceptiontable) - out.co_constants = co_consts; - out.co_varnames = co_varnames; - out.co_names = co_names; - out.co_argcount = co_argcount; - out.co_kwonlyargcount = co_kwonlyargcount; - out.co_cellvars = co_cellvars; - out.co_freevars = co_freevars; - out.pythonVersion = PythonVersion.PYTHON_3_10; - return out; - } - - PythonBytecodeInstruction instruction(OpcodeDescriptor opcode) { - return PythonBytecodeInstruction.atOffset(opcode, instructionList.size()); - } - - void update(PythonBytecodeInstruction instruction) { - instructionList.set(instruction.offset(), instruction); - } - - /** - * Perform the specified opcode with no argument - */ - public PythonFunctionBuilder op(OpcodeDescriptor opcode) { - PythonBytecodeInstruction instruction = instruction(opcode); - instructionList.add(instruction); - return this; - } - - /** - * Perform the specified opcode with an argument - */ - public PythonFunctionBuilder op(OpcodeDescriptor opcode, int arg) { - PythonBytecodeInstruction instruction = instruction(opcode) - .withArg(arg); - instructionList.add(instruction); - return this; - } - - /** - * TOS is an iterator. While the iterator is not empty, push the - * iterator's next element to the top of the stack and run the specified - * code. Repeat until the iterator is empty, and then pop the iterator off the stack. - * - * @param blockBuilder the bytecode to run in the loop - */ - public PythonFunctionBuilder loop(Consumer blockBuilder) { - return loop(blockBuilder, false); - } - - /** - * TOS is an iterator. While the iterator is not empty, push the - * iterator's next element to the top of the stack and run the specified - * code. Repeat until the iterator is empty, and then pop the iterator off the stack. - * - * @param blockBuilder the bytecode to run in the loop - */ - public PythonFunctionBuilder loop(Consumer blockBuilder, boolean alwaysExitEarly) { - PythonBytecodeInstruction instruction = instruction(ControlOpDescriptor.FOR_ITER) - .markAsJumpTarget(); - instructionList.add(instruction); - - blockBuilder.accept(this); - - if (!alwaysExitEarly) { - PythonBytecodeInstruction jumpBackInstruction = instruction(ControlOpDescriptor.JUMP_ABSOLUTE) - .withArg(instruction.offset()); - instructionList.add(jumpBackInstruction); - } - - update(instruction.withArg(instructionList.size() - instruction.offset() - 1)); - - PythonBytecodeInstruction afterLoopInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterLoopInstruction); - - return this; - } - - /** - * Declare a try block, and return an except builder for that try block. - * - * @param tryBlockBuilder The code to execute inside the try block - * @return An {@link ExceptBuilder} for the try block - */ - public ExceptBuilder tryCode(Consumer tryBlockBuilder, boolean tryExitEarly) { - PythonBytecodeInstruction notCatchedFinallyBlock = instruction(ExceptionOpDescriptor.SETUP_FINALLY); - instructionList.add(notCatchedFinallyBlock); - - PythonBytecodeInstruction setupFinallyInstruction = instruction(ExceptionOpDescriptor.SETUP_FINALLY); - instructionList.add(setupFinallyInstruction); - int tryStart = instructionList.size(); - - tryBlockBuilder.accept(this); - - update(setupFinallyInstruction.withArg(instructionList.size() - tryStart + (tryExitEarly ? 0 : 1))); - - PythonBytecodeInstruction tryJumpToEnd = null; - - if (!tryExitEarly) { - tryJumpToEnd = instruction(ControlOpDescriptor.JUMP_ABSOLUTE) - .withArg(0); - instructionList.add(tryJumpToEnd); - } - - return new ExceptBuilder(this, tryJumpToEnd, notCatchedFinallyBlock); - } - - /** - * Execute the code generated by the parameter if TOS is True; skip it otherwise. - * TOS is popped. - * - * @param blockBuilder The code inside the if statement - */ - public PythonFunctionBuilder ifTrue(Consumer blockBuilder) { - PythonBytecodeInstruction instruction = instruction(ControlOpDescriptor.POP_JUMP_IF_FALSE); - instructionList.add(instruction); - - blockBuilder.accept(this); - - PythonBytecodeInstruction afterIfInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterIfInstruction); - - update(instruction.withArg(afterIfInstruction.offset())); - - return this; - } - - /** - * Execute the code generated by the parameter if TOS is False; skip it otherwise. - * TOS is popped. - * - * @param blockBuilder The code inside the if statement - */ - public PythonFunctionBuilder ifFalse(Consumer blockBuilder) { - PythonBytecodeInstruction instruction = instruction(ControlOpDescriptor.POP_JUMP_IF_TRUE); - instructionList.add(instruction); - - blockBuilder.accept(this); - - PythonBytecodeInstruction afterIfInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterIfInstruction); - - update(instruction.withArg(afterIfInstruction.offset())); - - return this; - } - - /** - * Execute the code generated by the parameter if TOS is True; skip it otherwise. - * If TOS is True, TOS is popped; otherwise it remains on the stack. - * - * @param blockBuilder The code inside the if statement - */ - public PythonFunctionBuilder ifTruePopTop(Consumer blockBuilder) { - PythonBytecodeInstruction instruction = instruction(ControlOpDescriptor.JUMP_IF_FALSE_OR_POP); - instructionList.add(instruction); - - blockBuilder.accept(this); - - PythonBytecodeInstruction afterIfInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterIfInstruction); - - update(instruction.withArg(afterIfInstruction.offset())); - - return this; - } - - /** - * Execute the code generated by the parameter if TOS is False; skip it otherwise. - * If TOS is False, TOS is popped; otherwise it remains on the stack. - * - * @param blockBuilder The code inside the if statement - */ - public PythonFunctionBuilder ifFalsePopTop(Consumer blockBuilder) { - PythonBytecodeInstruction instruction = instruction(ControlOpDescriptor.JUMP_IF_TRUE_OR_POP); - instructionList.add(instruction); - - blockBuilder.accept(this); - - PythonBytecodeInstruction afterIfInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterIfInstruction); - - update(instruction.withArg(afterIfInstruction.offset())); - - return this; - } - - /** - * Use TOS as a context_manager, pushing the result of its __enter__ method to TOS, and calling its - * __exit__ method on exit of the with block (both normal and exceptional exits) - * - * @param blockBuilder The code inside the with block - */ - public PythonFunctionBuilder with(Consumer blockBuilder) { - var instruction = instruction(ExceptionOpDescriptor.SETUP_WITH); - instructionList.add(instruction); - - blockBuilder.accept(this); - - // Call the exit function - loadConstant(null); - loadConstant(null); - loadConstant(null); - callFunction(3); - op(StackOpDescriptor.POP_TOP); - - var skipExceptionHandler = instruction(ControlOpDescriptor.JUMP_ABSOLUTE); - instructionList.add(skipExceptionHandler); - - var exceptionHandler = instruction(ExceptionOpDescriptor.WITH_EXCEPT_START) - .markAsJumpTarget(); - instructionList.add(exceptionHandler); - - update(instruction.withArg(exceptionHandler.offset() - instruction.offset() - 1)); - - ifFalse(reraiseExceptionBlock -> reraiseExceptionBlock - .op(ExceptionOpDescriptor.RERAISE)); - - op(StackOpDescriptor.POP_TOP); - op(StackOpDescriptor.POP_TOP); - op(StackOpDescriptor.POP_TOP); - op(ExceptionOpDescriptor.POP_EXCEPT); - op(StackOpDescriptor.POP_TOP); - - update(skipExceptionHandler.withArg(instructionList.size())); - - PythonBytecodeInstruction afterWithInstruction = instruction(MetaOpDescriptor.NOP) - .markAsJumpTarget(); - instructionList.add(afterWithInstruction); - - return this; - } - - /** - * Create a list from the {@code count} top items on the stack. TOS is the last element in the list - * - * @param count The number of elements to pop and put into the list. - */ - public PythonFunctionBuilder list(int count) { - return op(CollectionOpDescriptor.BUILD_LIST, count); - } - - /** - * Create a tuple from the {@code count} top items on the stack. TOS is the last element in the tuple - * - * @param count The number of elements to pop and put into the tuple. - */ - public PythonFunctionBuilder tuple(int count) { - return op(CollectionOpDescriptor.BUILD_TUPLE, count); - } - - /** - * Create a dict from the {@code 2 * count} top items on the stack, which are read as key-value pairs. - * - * @param count The number of key-value pairs to pop and put into the dict. - */ - public PythonFunctionBuilder dict(int count) { - return op(CollectionOpDescriptor.BUILD_MAP, count); - } - - /** - * TOS is a tuple containing keys, and below TOS are {@code count} items representing the keys values. - * The last item in the tuple maps to TOS1, the second last item to TOS2, etc. - * - * @param count The number of values in the dict - */ - public PythonFunctionBuilder constDict(int count) { - return op(CollectionOpDescriptor.BUILD_CONST_KEY_MAP, count); - } - - /** - * Creates a set from the top {@code count} items in the stack. - * - * @param count The number of elements to pop and put into the set. - */ - public PythonFunctionBuilder set(int count) { - return op(CollectionOpDescriptor.BUILD_SET, count); - } - - /** - * Call a function with {@code argc} parameters. TOS[argc+1] is the function; above it are its arguments. - * - * @param argc The number of arguments the function takes - */ - public PythonFunctionBuilder callFunction(int argc) { - return op(FunctionOpDescriptor.CALL_FUNCTION, argc); - } - - /** - * Call a function with {@code argc} parameters, some of which are keywords. - * TOS[argc+1] is the function; above it are its arguments; keyword-only parameters are store - * in a dict at TOS, and positional parameters are stored in the stack. - * - * @param argc The number of arguments the function takes - */ - public PythonFunctionBuilder callFunctionWithKeywords(int argc) { - return op(FunctionOpDescriptor.CALL_FUNCTION_KW, argc); - } - - /** - * Call the function at TOS1 with the parameters specified in the tuple at TOS is {@code hasKeywords} is false, - * otherwise call the function at TOS2 with the parameters specified in the tuple at TOS1 and the keyword dict at TOS. - * - * @param hasKeywords true if keyword-only parameters are being passed - */ - public PythonFunctionBuilder callFunctionUnpack(boolean hasKeywords) { - return op(FunctionOpDescriptor.CALL_FUNCTION_EX, hasKeywords ? 1 : 0); - } - - /** - * Load the specified method on TOS. If type(TOS) has the method, self, method is pushed; otherwise - * null, TOS.__getattribute__(method) is pushed. - * - * @param methodName the method to load - */ - public PythonFunctionBuilder loadMethod(String methodName) { - int methodIndex = co_names.indexOf(methodName); - if (methodIndex == -1) { - methodIndex = co_names.size(); - co_names.add(methodName); - } - - return op(FunctionOpDescriptor.LOAD_METHOD, methodIndex); - } - - /** - * Call a method with {@code argc} arguments. Keyword-only arguments are not allowed. - * - * @param argc The number of arguments the method accepts - */ - public PythonFunctionBuilder callMethod(int argc) { - return op(FunctionOpDescriptor.CALL_METHOD, argc); - } - - /** - * Get an attribute on TOS. - * - * @param attributeName The attribute to get - */ - public PythonFunctionBuilder getAttribute(String attributeName) { - int attributeIndex = co_names.indexOf(attributeName); - if (attributeIndex == -1) { - attributeIndex = co_names.size(); - co_names.add(attributeName); - } - - return op(ObjectOpDescriptor.LOAD_ATTR, attributeIndex); - } - - /** - * TOS is an object, and TOS1 is a value. Store TOS1 into the {@code attributeName} attribute of TOS. - * TOS and TOS1 are popped. - * - * @param attributeName The attribute to store. - * @return - */ - public PythonFunctionBuilder storeAttribute(String attributeName) { - int attributeIndex = co_names.indexOf(attributeName); - if (attributeIndex == -1) { - attributeIndex = co_names.size(); - co_names.add(attributeName); - } - - return op(ObjectOpDescriptor.STORE_ATTR, attributeIndex); - } - - /** - * Loads a constant (converting it to the Interpreter equivalent if required). - * - * @param constant The Java constant to load - */ - public PythonFunctionBuilder loadConstant(Object constant) { - PythonLikeObject wrappedConstant = JavaPythonTypeConversionImplementor.wrapJavaObject(constant); - - int index = co_consts.indexOf(wrappedConstant); - if (index == -1) { - index = co_consts.size(); - co_consts.add(JavaPythonTypeConversionImplementor.wrapJavaObject(constant)); - } - - return op(VariableOpDescriptor.LOAD_CONST, index); - } - - /** - * Load the specified parameter - * - * @param parameterName The parameter to load - * @throws IllegalArgumentException if the parameter is not in the function's parameter list - */ - public PythonFunctionBuilder loadParameter(String parameterName) { - var arg = co_varnames.indexOf(parameterName); - - if (arg == -1) { - throw new IllegalArgumentException("Parameter (" + parameterName + ") is not in the parameter list (" + - co_varnames + ")."); - } - - return op(VariableOpDescriptor.LOAD_FAST, arg); - } - - /** - * Loads a variable with the given name (creating an entry in co_varnames if needed). - * - * @param variableName The variable to load - */ - public PythonFunctionBuilder loadVariable(String variableName) { - var arg = co_varnames.indexOf(variableName); - - if (arg == -1) { - co_varnames.add(variableName); - arg = co_varnames.size() - 1; - } - - return op(VariableOpDescriptor.LOAD_FAST, arg); - } - - /** - * Store TOS into a variable with the given name (creating an entry in co_varnames if needed). - * - * @param variableName The variable to store TOS in - */ - public PythonFunctionBuilder storeVariable(String variableName) { - var arg = co_varnames.indexOf(variableName); - - if (arg == -1) { - co_varnames.add(variableName); - arg = co_varnames.size() - 1; - } - - return op(VariableOpDescriptor.STORE_FAST, arg); - } - - /** - * Loads a variable that is shared with an inner function with the given name (creating an entry in co_cellvars if needed). - * - * @param variableName The variable to load - */ - public PythonFunctionBuilder loadCellVariable(String variableName) { - var arg = co_cellvars.indexOf(variableName); - - if (arg == -1) { - co_cellvars.add(variableName); - arg = co_cellvars.size() - 1; - - if (!co_varnames.contains(variableName)) { - co_varnames.add(variableName); - } - } - - return op(VariableOpDescriptor.LOAD_DEREF, arg); - } - - /** - * Stores TOS into a variable that is shared with an inner function with the given name (creating an entry in co_cellvars if - * needed). - * - * @param variableName The variable to store TOS in - */ - public PythonFunctionBuilder storeCellVariable(String variableName) { - var arg = co_cellvars.indexOf(variableName); - - if (arg == -1) { - co_cellvars.add(variableName); - arg = co_cellvars.size() - 1; - - if (!co_varnames.contains(variableName)) { - co_varnames.add(variableName); - } - } - - return op(VariableOpDescriptor.STORE_DEREF, arg); - } - - /** - * Loads a free variable (creating an entry in co_freevars if needed). - * - * @param variableName The variable to load - */ - public PythonFunctionBuilder loadFreeVariable(String variableName) { - var arg = co_freevars.indexOf(variableName); - - if (arg == -1) { - co_freevars.add(variableName); - arg = co_freevars.size() - 1; - - if (!co_varnames.contains(variableName)) { - co_varnames.add(variableName); - } - } - - return op(VariableOpDescriptor.LOAD_DEREF, arg); - } - - /** - * Stores TOS into a free variable (creating an entry in co_freevars if needed). - * - * @param variableName The variable to store TOS in - */ - public PythonFunctionBuilder storeFreeVariable(String variableName) { - var arg = co_freevars.indexOf(variableName); - - if (arg == -1) { - co_freevars.add(variableName); - arg = co_freevars.size() - 1; - - if (!co_varnames.contains(variableName)) { - co_varnames.add(variableName); - } - } - - return op(VariableOpDescriptor.STORE_DEREF, arg); - } - - public PythonFunctionBuilder usingGlobalsMap(Map globalsMap) { - this.globalsMap = globalsMap; - return this; - } - - /** - * Loads a global variable - * - * @param variableName The variable to load - */ - public PythonFunctionBuilder loadGlobalVariable(String variableName) { - var arg = co_names.indexOf(variableName); - - if (arg == -1) { - co_names.add(variableName); - arg = co_names.size() - 1; - } - - return op(VariableOpDescriptor.LOAD_GLOBAL, arg); - } - - /** - * Store TOS into a global variable. - * - * @param variableName The global variable to store TOS in - */ - public PythonFunctionBuilder storeGlobalVariable(String variableName) { - var arg = co_names.indexOf(variableName); - - if (arg == -1) { - co_names.add(variableName); - arg = co_names.size() - 1; - } - - return op(VariableOpDescriptor.STORE_GLOBAL, arg); - } - - /** - * Loads a module using TOS as level and TOS1 as from_list - * - * @param moduleName The module to get - */ - public PythonFunctionBuilder loadModule(String moduleName) { - int attributeIndex = co_names.indexOf(moduleName); - if (attributeIndex == -1) { - attributeIndex = co_names.size(); - co_names.add(moduleName); - } - - return op(ModuleOpDescriptor.IMPORT_NAME, attributeIndex); - } - - /** - * Loads an attribute from TOS (which is a module) - * - * @param attributeName The attribute to get - */ - public PythonFunctionBuilder getFromModule(String attributeName) { - int attributeIndex = co_names.indexOf(attributeName); - if (attributeIndex == -1) { - attributeIndex = co_names.size(); - co_names.add(attributeName); - } - - return op(ModuleOpDescriptor.IMPORT_FROM, attributeIndex); - } - - /** - * Perform a comparison on TOS and TOS1, popping TOS, TOS1 and pushing the result. - * - * @param compareOp The comparison to perform - */ - public PythonFunctionBuilder compare(CompareOp compareOp) { - PythonBytecodeInstruction instruction = instruction(DunderOpDescriptor.COMPARE_OP) - .withArg(0) - .withArgRepr(compareOp.id); - instructionList.add(instruction); - return this; - } -} diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpecTest.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpecTest.java deleted file mode 100644 index 55a5daa2..00000000 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpecTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package ai.timefold.jpyinterpreter.util.arguments; - -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.errors.TypeError; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; - -public class ArgumentSpecTest { - @Test - public void testSpec() { - ArgumentSpec current = ArgumentSpec.forFunctionReturning("myFunction", PythonLikeTuple.class.getName()); - - List argumentNameList = new ArrayList<>(); - List argumentValueList = new ArrayList<>(); - - for (int i = 0; i < 20; i++) { - List positionalArguments = new ArrayList<>(argumentValueList); - Map keywordArguments = new HashMap<>(); - - current.extractArgumentList(positionalArguments, keywordArguments); - while (!positionalArguments.isEmpty()) { - int toRemove = positionalArguments.size() - 1; - PythonLikeObject removed = positionalArguments.remove(toRemove); - keywordArguments.put(PythonString.valueOf(argumentNameList.get(toRemove)), removed); - List out = current.extractArgumentList(positionalArguments, keywordArguments); - assertThat(out).containsExactlyElementsOf(argumentValueList); - } - - current = current.addArgument("arg" + i, PythonInteger.class.getName()); - argumentNameList.add("arg" + i); - argumentValueList.add(PythonInteger.valueOf(i)); - } - } - - @Test - public void testSpecWithDefaults() { - ArgumentSpec current = ArgumentSpec.forFunctionReturning("myFunction", PythonLikeTuple.class.getName()); - - List argumentNameList = new ArrayList<>(); - List argumentValueList = new ArrayList<>(); - - for (int i = 0; i < 20; i++) { - for (int missingArgs = 0; missingArgs < i; missingArgs++) { - List positionalArguments = - new ArrayList<>(argumentValueList.subList(0, argumentValueList.size() - missingArgs)); - Map keywordArguments = new HashMap<>(); - - current.extractArgumentList(positionalArguments, keywordArguments); - while (!positionalArguments.isEmpty()) { - int toRemove = positionalArguments.size() - 1; - PythonLikeObject removed = positionalArguments.remove(toRemove); - keywordArguments.put(PythonString.valueOf(argumentNameList.get(toRemove)), removed); - List out = current.extractArgumentList(positionalArguments, keywordArguments); - List expected = - new ArrayList<>(argumentValueList.subList(0, argumentValueList.size() - missingArgs)); - for (int j = i - missingArgs; j < i; j++) { - expected.add(PythonInteger.valueOf(-j)); - } - assertThat(out).containsExactlyElementsOf(expected); - } - } - - current = current.addArgument("arg" + i, PythonInteger.class.getName(), PythonInteger.valueOf(-i)); - argumentNameList.add("arg" + i); - argumentValueList.add(PythonInteger.valueOf(i)); - } - } - - @Test - public void testSpecMissingArgument() { - ArgumentSpec current = ArgumentSpec.forFunctionReturning("myFunction", PythonLikeTuple.class.getName()) - .addArgument("_arg0", PythonInteger.class.getName()); - - List argumentNameList = new ArrayList<>(); - List argumentValueList = new ArrayList<>(); - - for (int i = 0; i < 19; i++) { - ArgumentSpec finalCurrent = current; - - List positionalArguments = new ArrayList<>(argumentValueList); - Map keywordArguments = new HashMap<>(); - assertThatCode(() -> finalCurrent.extractArgumentList(positionalArguments, keywordArguments)) - .isInstanceOf(TypeError.class) - .hasMessageContaining("myFunction() missing 1 required positional argument: '"); - while (!positionalArguments.isEmpty()) { - int toRemove = positionalArguments.size() - 1; - PythonLikeObject removed = positionalArguments.remove(toRemove); - keywordArguments.put(PythonString.valueOf(argumentNameList.get(toRemove)), removed); - - assertThatCode(() -> finalCurrent.extractArgumentList(positionalArguments, keywordArguments)) - .isInstanceOf(TypeError.class) - .hasMessageContaining("myFunction() missing 1 required positional argument: '"); - } - - current = current.addArgument("arg" + i, PythonInteger.class.getName()); - argumentNameList.add("arg" + i); - argumentValueList.add(PythonInteger.valueOf(i)); - } - } - - @Test - public void testSpecExtraArgument() { - ArgumentSpec current = ArgumentSpec.forFunctionReturning("myFunction", PythonLikeTuple.class.getName()); - - List argumentNameList = new ArrayList<>(); - List argumentValueList = new ArrayList<>(); - argumentNameList.add("_arg0"); - argumentValueList.add(PythonInteger.valueOf(-1)); - - for (int i = 0; i < 20; i++) { - ArgumentSpec finalCurrent = current; - - List positionalArguments = new ArrayList<>(argumentValueList); - Map keywordArguments = new HashMap<>(); - String[] possibleErrorMessages = new String[2 + i]; - possibleErrorMessages[0] = "myFunction() takes " + i + " positional arguments but " + (i + 1) + " were given"; - possibleErrorMessages[1] = "myFunction() got an unexpected keyword argument '_arg0'"; - for (int arg = 0; arg < i; arg++) { - possibleErrorMessages[2 + arg] = "myFunction() got multiple values for argument 'arg" + arg + "'"; - } - - assertThatCode(() -> finalCurrent.extractArgumentList(positionalArguments, keywordArguments)) - .isInstanceOf(TypeError.class) - .extracting(Throwable::getMessage, as(InstanceOfAssertFactories.STRING)) - .containsAnyOf(possibleErrorMessages); - while (!positionalArguments.isEmpty()) { - int toRemove = positionalArguments.size() - 1; - PythonLikeObject removed = positionalArguments.remove(toRemove); - keywordArguments.put(PythonString.valueOf(argumentNameList.get(toRemove)), removed); - - assertThatCode(() -> finalCurrent.extractArgumentList(positionalArguments, keywordArguments)) - .isInstanceOf(TypeError.class) - .extracting(Throwable::getMessage, as(InstanceOfAssertFactories.STRING)) - .containsAnyOf(possibleErrorMessages); - } - - current = current.addArgument("arg" + i, PythonInteger.class.getName()); - argumentNameList.add("arg" + i); - argumentValueList.add(PythonInteger.valueOf(i)); - } - } -} diff --git a/jpyinterpreter/tests/__init__.py b/jpyinterpreter/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/jpyinterpreter/tests/conftest.py b/jpyinterpreter/tests/conftest.py deleted file mode 100644 index 103e6be4..00000000 --- a/jpyinterpreter/tests/conftest.py +++ /dev/null @@ -1,234 +0,0 @@ -import pytest -from typing import Callable, Any -from copy import deepcopy -import locale - - -def get_argument_cloner(clone_arguments): - return (lambda x: deepcopy(x)) if clone_arguments else (lambda x: x) - - -class FunctionVerifier: - def __init__(self, python_function, java_function, untyped_java_function): - self.python_function = python_function - self.java_function = java_function - self.untyped_java_function = untyped_java_function - - def verify(self, *args, expected_result=..., expected_error=..., clone_arguments=True, type_check=True): - cloner = get_argument_cloner(clone_arguments) - try: - expected = self.python_function(*cloner(args)) - except Exception as error: - if expected_error is not ... and isinstance(expected_error, type) and not isinstance(error, expected_error): - raise AssertionError(f'Python function did not raise expected error ({expected_error}) ' - f'for arguments {args}; it raised {type(error)}({error}) instead.') - - if expected_error is not ... and not isinstance(expected_error, type) and error != expected_error: - raise AssertionError(f'Python function did not raise expected error ({expected_error}) ' - f'for arguments {args}; it raised {type(error)}({error}) instead.') - - if expected_result is not ...: - raise AssertionError(f'Python function did not return expected result ({expected_result}) ' - f'for arguments {args}; it raised {type(error)}({error}) instead.') - - self.expect_error(error, *args, clone_arguments=clone_arguments) - return - - if expected_result is not ... and expected_result != expected: - raise AssertionError(f'Python function did not return expected result ({expected_result}) ' - f'for arguments {args}; it returned ({expected}) instead.') - - if expected_error is not ...: - raise AssertionError(f'Python function did not raise expected error ({expected_error}) ' - f'for arguments {args}; it returned ({expected}) instead.') - - if type_check and expected_result is not ... and type(expected_result) is not type(expected): - raise AssertionError(f'Python function did not return expected result ({expected_result}, ' - f'{type(expected_result)}) for arguments {args}; it returned the equal but different ' - f'type ({expected}, {type(expected)}) instead.') - - self.expect(expected, *args, clone_arguments=clone_arguments, type_check=type_check) - - - def verify_property(self, *args, predicate, clone_arguments=True): - cloner = get_argument_cloner(clone_arguments) - python_result = self.python_function(*cloner(args)) - if not predicate(python_result): - import inspect - raise AssertionError(f'Python function result ({python_result}) does not satisfy the property ' - f'({inspect.getsource(predicate).strip()}) ' - f'for arguments {args}.') - - java_result = self.java_function(*cloner(args)) - untyped_java_result = self.untyped_java_function(*cloner(args)) - if not predicate(java_result) or not predicate(untyped_java_result): - import inspect - if not predicate(java_result) and not predicate(untyped_java_result): - raise AssertionError(f'Typed and untyped translated bytecode result ({java_result}) does not satisfy ' - f'the property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - elif not predicate(untyped_java_result): - raise AssertionError(f'Untyped translated bytecode result ({untyped_java_result}) does not satisfy the ' - f'property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - else: - raise AssertionError(f'Typed translated bytecode result ({java_result}) does not satisfy the ' - f'property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - - def verify_error_property(self, *args, predicate, clone_arguments=True): - cloner = get_argument_cloner(clone_arguments) - try: - python_result = self.python_function(*cloner(args)) - raise AssertionError(f'Python function did not raise an error and returned ({python_result})') - except AssertionError: - raise - except Exception as python_result: - if not predicate(python_result): - import inspect - raise AssertionError(f'Python function error ({python_result}) does not satisfy the property ' - f'({inspect.getsource(predicate).strip()}) ' - f'for arguments {args}.') - - try: - java_result = self.java_function(*cloner(args)) - raise AssertionError(f'Typed Java function did not raise an error and returned ({java_result})') - except AssertionError: - raise - except Exception as err: - java_result = err - - try: - untyped_java_result = self.untyped_java_function(*cloner(args)) - raise AssertionError(f'Untyped Java function did not raise an error and returned ({untyped_java_result})') - except AssertionError: - raise - except Exception as err: - untyped_java_result = err - - if not predicate(java_result) or not predicate(untyped_java_result): - import inspect - if not predicate(java_result) and not predicate(untyped_java_result): - raise AssertionError(f'Typed and untyped translated bytecode error ({java_result}) does not satisfy ' - f'the property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - elif not predicate(untyped_java_result): - raise AssertionError(f'Untyped translated bytecode error ({untyped_java_result}) does not satisfy the ' - f'property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - else: - raise AssertionError(f'Typed translated bytecode error ({java_result}) does not satisfy the ' - f'property ({inspect.getsource(predicate)}) ' - f'for arguments {args}.') - - def expect(self, expected, *args, clone_arguments=True, type_check=True): - cloner = get_argument_cloner(clone_arguments) - java_result = self.java_function(*cloner(args)) - untyped_java_result = self.untyped_java_function(*cloner(args)) - if java_result != expected or untyped_java_result != expected: - if java_result != expected and untyped_java_result != expected: - raise AssertionError(f'Typed and untyped translated bytecode did not return expected result ' - f'({expected}) for arguments {args}; it returned ({java_result}) (typed) ' - f'and ({untyped_java_result}) (untyped) instead.') - elif untyped_java_result != expected: - raise AssertionError(f'Untyped translated bytecode did not return expected result ' - f'({expected}) for arguments {args}; it returned ({untyped_java_result}) ' - f'(untyped) instead.') - else: - raise AssertionError(f'Typed translated bytecode did not return expected result ' - f'({expected}) for arguments {args}; it returned ({java_result}) ' - f'(typed) instead.') - if type_check and (type(java_result) is not type(expected) or type(untyped_java_result) is not type(expected)): - if type(java_result) is not type(expected) and type(untyped_java_result) is not type(expected): - raise AssertionError(f'Typed and untyped translated bytecode did not return expected result ' - f'({expected}, {type(expected)}) for arguments {args}; it returned the equal but ' - f'different type ({java_result}, {type(java_result)}) (typed) and ' - f'({untyped_java_result}, {type(untyped_java_result)}) (untyped) instead.') - elif type(untyped_java_result) is not type(expected): - raise AssertionError(f'Untyped translated bytecode did not return expected result ' - f'({expected}, {type(expected)}) for arguments {args}; it returned the equal but ' - f'different type ' - f'({untyped_java_result}, {type(untyped_java_result)}) (untyped) instead.') - else: - raise AssertionError(f'Typed translated bytecode did not return expected result ' - f'({expected}, {type(expected)}) for arguments {args}; it returned the equal but ' - f'different type ({java_result}, {type(java_result)}) (typed) ' - f'instead.') - - def expect_error(self, error, *args, clone_arguments=True): - cloner = get_argument_cloner(clone_arguments) - with pytest.raises(type(error)) as error_info: - self.java_function(*cloner(args)) - - with pytest.raises(type(error)) as error_info: - self.untyped_java_function(*cloner(args)) - - -def verifier_for(the_function: Callable[..., Any]) -> FunctionVerifier: - import jpyinterpreter - - java_function = jpyinterpreter.as_typed_java(the_function) - untyped_java_function = jpyinterpreter.as_untyped_java(the_function) - - # Generator code use a different local variable helper than normal function; - # this is to make sure no verification error come up - jpyinterpreter._force_as_java_generator(the_function) - - return FunctionVerifier(the_function, java_function, untyped_java_function) - - -def pytest_addoption(parser): - """ - Allows adding command line options to pytest - """ - parser.addoption('--jacoco-agent', action='store', default='') - parser.addoption('--jacoco-output', action='store', default='target/jacoco.exec') - parser.addoption('--output-generated-classes', action='store', default='false') - - -def pytest_configure(config): - """ - Allows plugins and conftest files to perform initial configuration. - This hook is called for every plugin and initial conftest - file after command line options have been parsed. - """ - pass - - -def pytest_sessionstart(session): - """ - Called after the Session object has been created and - before performing collection and entering the run test loop. - """ - import jpyinterpreter - import pathlib - import sys - - locale.setlocale(locale.LC_ALL, 'C') - class_output_path = None - if session.config.getoption('--output-generated-classes') != 'false': - class_output_path = pathlib.Path('target', 'tox-generated-classes', 'python', - f'{sys.version_info[0]}.{sys.version_info[1]}') - - jacoco_agent = session.config.getoption('--jacoco-agent') - if jacoco_agent != '': - jacoco_output = session.config.getoption('--jacoco-output') - jpyinterpreter.init(f'-javaagent:{jacoco_agent}=destfile={jacoco_output}', - class_output_path=class_output_path) - else: - jpyinterpreter.init(class_output_path=class_output_path) - - -def pytest_sessionfinish(session, exitstatus): - """ - Called after whole test run finished, right before - returning the exit status to the system. - """ - pass - - -def pytest_unconfigure(config): - """ - called before test process is exited. - """ - pass \ No newline at end of file diff --git a/jpyinterpreter/tests/datetime/__init__.py b/jpyinterpreter/tests/datetime/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/jpyinterpreter/tests/datetime/test_date.py b/jpyinterpreter/tests/datetime/test_date.py deleted file mode 100644 index 03dbc7a7..00000000 --- a/jpyinterpreter/tests/datetime/test_date.py +++ /dev/null @@ -1,362 +0,0 @@ -from datetime import date, timedelta -from typing import Union - -from ..conftest import verifier_for - - -# Constructor -def test_constructor(): - def function(year: int, month: int, day: int) -> date: - return date(year, month, day) - - verifier = verifier_for(function) - - verifier.verify(2000, 1, 1, expected_result=date(2000, 1, 1)) - verifier.verify(2000, 1, 30, expected_result=date(2000, 1, 30)) - verifier.verify(2000, 2, 3, expected_result=date(2000, 2, 3)) - verifier.verify(2001, 1, 1, expected_result=date(2001, 1, 1)) - - verifier.verify(2000, 1, 0, expected_error=ValueError) - verifier.verify(2000, 1, 32, expected_error=ValueError) - verifier.verify(2000, 0, 1, expected_error=ValueError) - verifier.verify(2000, 13, 1, expected_error=ValueError) - - -# Instance attributes -def test_instance_attributes(): - def function(a: date) -> tuple: - return a.year, a.month, a.day - - verifier = verifier_for(function) - - verifier.verify(date(2000, 1, 1), expected_result=(2000, 1, 1)) - verifier.verify(date(2000, 1, 30), expected_result=(2000, 1, 30)) - verifier.verify(date(2000, 2, 3), expected_result=(2000, 2, 3)) - verifier.verify(date(2001, 1, 1), expected_result=(2001, 1, 1)) - - -# Operations -def test_comparisons(): - def less_than(a: date, b: date) -> bool: - return a < b - - def greater_than(a: date, b: date) -> bool: - return a > b - - def less_than_or_equal(a: date, b: date) -> bool: - return a <= b - - def greater_than_or_equal(a: date, b: date) -> bool: - return a >= b - - def equal(a: date, b: date) -> bool: - return a == b - - def not_equal(a: date, b: date) -> bool: - return a != b - - less_than_verifier = verifier_for(less_than) - greater_than_verifier = verifier_for(greater_than) - less_than_or_equal_verifier = verifier_for(less_than_or_equal) - greater_than_or_equal_verifier = verifier_for(greater_than_or_equal) - equal_verifier = verifier_for(equal) - not_equal_verifier = verifier_for(not_equal) - - sorted_list_of_dates = [ - date(2000, 1, 3), - date(2000, 1, 4), - date(2000, 2, 2), - date(2001, 1, 1) - ] - - for first_index in range(len(sorted_list_of_dates)): - for second_index in range(first_index, len(sorted_list_of_dates)): - smaller = sorted_list_of_dates[first_index] - bigger = sorted_list_of_dates[second_index] - - if first_index == second_index: - less_than_verifier.verify(smaller, bigger, expected_result=False) - greater_than_verifier.verify(smaller, bigger, expected_result=False) - less_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - greater_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - equal_verifier.verify(smaller, bigger, expected_result=True) - not_equal_verifier.verify(smaller, bigger, expected_result=False) - else: - less_than_verifier.verify(smaller, bigger, expected_result=True) - greater_than_verifier.verify(smaller, bigger, expected_result=False) - less_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - greater_than_or_equal_verifier.verify(smaller, bigger, expected_result=False) - equal_verifier.verify(smaller, bigger, expected_result=False) - not_equal_verifier.verify(smaller, bigger, expected_result=True) - - less_than_verifier.verify(bigger, smaller, expected_result=False) - greater_than_verifier.verify(bigger, smaller, expected_result=True) - less_than_or_equal_verifier.verify(bigger, smaller, expected_result=False) - greater_than_or_equal_verifier.verify(bigger, smaller, expected_result=True) - equal_verifier.verify(bigger, smaller, expected_result=False) - not_equal_verifier.verify(bigger, smaller, expected_result=True) - - -def test_subtract_date(): - def function(first_date: date, second_date: date) -> timedelta: - return first_date - second_date - - verifier = verifier_for(function) - - verifier.verify(date(2000, 1, 1), date(2000, 1, 1), expected_result=timedelta(days=0)) - - verifier.verify(date(2000, 1, 3), date(2000, 1, 1), expected_result=timedelta(days=2)) - verifier.verify(date(2000, 1, 1), date(2000, 1, 3), expected_result=timedelta(days=-2)) - - verifier.verify(date(2000, 2, 1), date(2000, 1, 1), expected_result=timedelta(days=31)) - verifier.verify(date(2000, 1, 1), date(2000, 2, 1), expected_result=timedelta(days=-31)) - - verifier.verify(date(2001, 1, 1), date(2000, 1, 1), expected_result=timedelta(days=366)) - verifier.verify(date(2000, 1, 1), date(2001, 1, 1), expected_result=timedelta(days=-366)) - - -def test_add_timedelta(): - def function(first_date: date, difference: timedelta) -> date: - return first_date + difference - - verifier = verifier_for(function) - - verifier.verify(date(2000, 1, 1), timedelta(days=0), expected_result=date(2000, 1, 1)) - - verifier.verify(date(2000, 1, 1), timedelta(days=2), expected_result=date(2000, 1, 3)) - verifier.verify(date(2000, 1, 3), timedelta(days=-2), expected_result=date(2000, 1, 1)) - - verifier.verify(date(2000, 1, 1), timedelta(days=31), expected_result=date(2000, 2, 1)) - verifier.verify(date(2000, 2, 1), timedelta(days=-31), expected_result=date(2000, 1, 1)) - - verifier.verify(date(2000, 1, 1), timedelta(days=366), expected_result=date(2001, 1, 1)) - verifier.verify(date(2001, 1, 1), timedelta(days=-366), expected_result=date(2000, 1, 1)) - - -def test_subtract_timedelta(): - def function(first_date: date, difference: timedelta) -> date: - return first_date - difference - - verifier = verifier_for(function) - - verifier.verify(date(2000, 1, 1), timedelta(days=0), expected_result=date(2000, 1, 1)) - - verifier.verify(date(2000, 1, 3), timedelta(days=2), expected_result=date(2000, 1, 1)) - verifier.verify(date(2000, 1, 1), timedelta(days=-2), expected_result=date(2000, 1, 3)) - - verifier.verify(date(2000, 2, 1), timedelta(days=31), expected_result=date(2000, 1, 1)) - verifier.verify(date(2000, 1, 1), timedelta(days=-31), expected_result=date(2000, 2, 1)) - - verifier.verify(date(2001, 1, 1), timedelta(days=366), expected_result=date(2000, 1, 1)) - verifier.verify(date(2000, 1, 1), timedelta(days=-366), expected_result=date(2001, 1, 1)) - - -def test_fromtimestamp(): - def function(timestamp: Union[int, float]) -> date: - return date.fromtimestamp(timestamp) - - verifier = verifier_for(function) - - # cannot use expected result; python timestamps use system timezone - verifier.verify(0) - verifier.verify(4000) - verifier.verify(200000) - - -def test_fromordinal(): - def function(ordinal: int) -> date: - return date.fromordinal(ordinal) - - verifier = verifier_for(function) - - verifier.verify(1, expected_result=date(1, 1, 1)) - verifier.verify(2, expected_result=date(1, 1, 2)) - verifier.verify(32, expected_result=date(1, 2, 1)) - verifier.verify(1000, expected_result=date(3, 9, 27)) - - -def test_fromisoformat(): - def function(date_string: str) -> date: - return date.fromisoformat(date_string) - - verifier = verifier_for(function) - - verifier.verify('2000-01-01', expected_result=date(2000, 1, 1)) - verifier.verify('1999-02-03', expected_result=date(1999, 2, 3)) - - -def test_fromisocalendar(): - def function(year: int, week: int, day: int) -> date: - return date.fromisocalendar(year, week, day) - - verifier = verifier_for(function) - - verifier.verify(2000, 1, 1, expected_result=date(2000, 1, 3)) - verifier.verify(1999, 2, 3, expected_result=date(1999, 1, 13)) - - -def test_replace(): - def replace_year(x: date, year: int) -> date: - return x.replace(year=year) - - def replace_month(x: date, month: int) -> date: - return x.replace(month=month) - - def replace_day(x: date, day: int) -> date: - return x.replace(day=day) - - def replace_year_month(x: date, year: int, month: int) -> date: - return x.replace(year=year, month=month) - - def replace_month_day(x: date, month: int, day: int) -> date: - return x.replace(month=month, day=day) - - def replace_year_day(x: date, year: int, day: int) -> date: - return x.replace(year=year, day=day) - - def replace_all(x: date, year: int, month: int, day: int) -> date: - return x.replace(year=year, month=month, day=day) - - replace_year_verifier = verifier_for(replace_year) - replace_month_verifier = verifier_for(replace_month) - replace_day_verifier = verifier_for(replace_day) - replace_year_month_verifier = verifier_for(replace_year_month) - replace_month_day_verifier = verifier_for(replace_month_day) - replace_year_day_verifier = verifier_for(replace_year_day) - replace_all_verifier = verifier_for(replace_all) - - replace_year_verifier.verify(date(2002, 12, 4), 3000, expected_result=date(3000, 12, 4)) - replace_month_verifier.verify(date(2002, 12, 4), 3, expected_result=date(2002, 3, 4)) - replace_day_verifier.verify(date(2002, 12, 4), 10, expected_result=date(2002, 12, 10)) - replace_year_month_verifier.verify(date(2002, 12, 4), 3000, 3, expected_result=date(3000, 3, 4)) - replace_month_day_verifier.verify(date(2002, 12, 4), 3, 10, expected_result=date(2002, 3, 10)) - replace_year_day_verifier.verify(date(2002, 12, 4), 3000, 10, expected_result=date(3000, 12, 10)) - replace_all_verifier.verify(date(2002, 12, 4), 3000, 3, 10, expected_result=date(3000, 3, 10)) - - -def test_timetuple(): - def function(x: date) -> tuple: - return x.timetuple() - - verifier = verifier_for(function) - - # TODO: enable type checking when named tuples are supported - verifier.verify(date(1, 1, 1), expected_result=(1, 1, 1, 0, 0, 0, 0, 1, -1), type_check=False) - verifier.verify(date(3, 9, 27), expected_result=(3, 9, 27, 0, 0, 0, 5, 270, -1), type_check=False) - - -def test_toordinal(): - def function(x: date) -> int: - return x.toordinal() - - verifier = verifier_for(function) - - verifier.verify(date(1, 1, 1), expected_result=1) - verifier.verify(date(1, 1, 2), expected_result=2) - verifier.verify(date(1, 2, 1), expected_result=32) - verifier.verify(date(3, 9, 27), expected_result=1000) - - -def test_weekday(): - def function(x: date) -> int: - return x.weekday() - - verifier = verifier_for(function) - - verifier.verify(date(2002, 12, 4), expected_result=2) - - -def test_isoweekday(): - def function(x: date) -> int: - return x.isoweekday() - - verifier = verifier_for(function) - - verifier.verify(date(2002, 12, 4), expected_result=3) - - -def test_isocalendar(): - def function(x: date) -> tuple: - return x.isocalendar() - - verifier = verifier_for(function) - - verifier.verify(date(2003, 12, 29), expected_result=(2004, 1, 1), type_check=False) - verifier.verify(date(2004, 1, 4), expected_result=(2004, 1, 7), type_check=False) - - -def test_isoformat(): - def function(x: date) -> str: - return x.isoformat() - - verifier = verifier_for(function) - - verifier.verify(date(2003, 12, 29), expected_result='2003-12-29') - verifier.verify(date(2004, 1, 4), expected_result='2004-01-04') - - -def test_str(): - def function(x: date) -> str: - return str(x) - - verifier = verifier_for(function) - - verifier.verify(date(2003, 12, 29), expected_result='2003-12-29') - verifier.verify(date(2004, 1, 4), expected_result='2004-01-04') - - -def test_ctime(): - def function(x: date) -> str: - return x.ctime() - - verifier = verifier_for(function) - - verifier.verify(date(2002, 12, 4), expected_result='Wed Dec 4 00:00:00 2002') - - -def test_strftime(): - def function(x: date, fmt: str) -> str: - return x.strftime(fmt) - - verifier = verifier_for(function) - - verifier.verify(date(1, 2, 3), '%a', - expected_result='Sat') - # Java C Locale uses the short form for the full variant of week days - # verifier.verify(date(1, 2, 3), '%A', - # expected_result='Saturday') - verifier.verify(date(1, 2, 3), '%W', - expected_result='05') - verifier.verify(date(1, 2, 3), '%d', - expected_result='03') - verifier.verify(date(1, 2, 3), '%b', - expected_result='Feb') - # Java C Locale uses the short form for the full variant of months - # verifier.verify(date(1, 2, 3), '%B', - # expected_result='February') - verifier.verify(date(1, 2, 3), '%m', - expected_result='02') - verifier.verify(date(1, 2, 3), '%y', - expected_result='01') - verifier.verify(date(1001, 2, 3), '%y', - expected_result='01') - # %Y have different results depending on the platform; - # Windows 0-pad it, Linux does not. - # verifier.verify(date(1, 2, 3), '%Y', - # expected_result='1') - verifier.verify(date(1, 2, 3), '%j', - expected_result='034') - verifier.verify(date(1, 2, 3), '%U', - expected_result='04') - verifier.verify(date(1, 2, 3), '%W', - expected_result='05') - # %Y have different results depending on the platform; - # Windows 0-pad it, Linux does not. - # verifier.verify(date(1, 2, 3), '%G', - # expected_result='1') - verifier.verify(date(1, 2, 3), '%u', - expected_result='6') - verifier.verify(date(1, 2, 3), '%%', - expected_result='%') - verifier.verify(date(1, 2, 3), '%V', - expected_result='05') diff --git a/jpyinterpreter/tests/datetime/test_datetime.py b/jpyinterpreter/tests/datetime/test_datetime.py deleted file mode 100644 index b02e1d43..00000000 --- a/jpyinterpreter/tests/datetime/test_datetime.py +++ /dev/null @@ -1,500 +0,0 @@ -from datetime import date, time, datetime, timedelta -from typing import Union - -from ..conftest import verifier_for - - -# Constructor -def test_constructor(): - def function(year: int, month: int, day: int) -> datetime: - return datetime(year, month, day) - - def function_with_time(year: int, month: int, day: int, - hour: int, minute: int, second: int, microsecond: int, tzinfo, fold: int) -> datetime: - return datetime(year, month, day, - hour=hour, minute=minute, second=second, microsecond=microsecond, tzinfo=tzinfo, fold=fold) - - verifier = verifier_for(function) - verifier_with_time = verifier_for(function_with_time) - - verifier.verify(2000, 1, 1, expected_result=datetime(2000, 1, 1)) - verifier.verify(2000, 1, 30, expected_result=datetime(2000, 1, 30)) - verifier.verify(2000, 2, 3, expected_result=datetime(2000, 2, 3)) - verifier.verify(2001, 1, 1, expected_result=datetime(2001, 1, 1)) - - verifier.verify(2000, 1, 0, expected_error=ValueError) - verifier.verify(2000, 1, 32, expected_error=ValueError) - verifier.verify(2000, 0, 1, expected_error=ValueError) - verifier.verify(2000, 13, 1, expected_error=ValueError) - - verifier_with_time.verify(2000, 1, 1, 1, 2, 3, 4, None, 0, expected_result=datetime(2000, 1, 1, 1, 2, 3, 4, None, - fold=0)) - verifier_with_time.verify(2000, 1, 30, 10, 20, 30, 40, None, 1, expected_result=datetime(2000, 1, 30, - 10, 20, 30, 40, - None, fold=1)) - - -# Instance attributes -def test_instance_attributes(): - def function(a: datetime) -> tuple: - return a.year, a.month, a.day, a.hour, a.minute, a.second, a.microsecond, a.tzinfo, a.fold - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1), expected_result=(2000, 1, 1, 0, 0, 0, 0, None, 0)) - verifier.verify(datetime(2000, 1, 30), expected_result=(2000, 1, 30, 0, 0, 0, 0, None, 0)) - verifier.verify(datetime(2000, 2, 3), expected_result=(2000, 2, 3, 0, 0, 0, 0, None, 0)) - verifier.verify(datetime(2001, 1, 1), expected_result=(2001, 1, 1, 0, 0, 0, 0, None, 0)) - - verifier.verify(datetime(2000, 1, 1, 1, 2, 3, 4, None, - fold=0), expected_result=(2000, 1, 1, 1, 2, 3, 4, None, 0)) - verifier.verify(datetime(2000, 1, 30, - 10, 20, 30, 40, - None, fold=1), expected_result=(2000, 1, 30, 10, 20, 30, 40, None, 1)) - - -# Operations -def test_comparisons(): - def less_than(a: datetime, b: datetime) -> bool: - return a < b - - def greater_than(a: datetime, b: datetime) -> bool: - return a > b - - def less_than_or_equal(a: datetime, b: datetime) -> bool: - return a <= b - - def greater_than_or_equal(a: datetime, b: datetime) -> bool: - return a >= b - - def equal(a: datetime, b: datetime) -> bool: - return a == b - - def not_equal(a: datetime, b: datetime) -> bool: - return a != b - - less_than_verifier = verifier_for(less_than) - greater_than_verifier = verifier_for(greater_than) - less_than_or_equal_verifier = verifier_for(less_than_or_equal) - greater_than_or_equal_verifier = verifier_for(greater_than_or_equal) - equal_verifier = verifier_for(equal) - not_equal_verifier = verifier_for(not_equal) - - sorted_list_of_datetimes = [ - datetime(2000, 1, 3), - datetime(2000, 1, 3, 0, 0, 45, 0, None, fold=0), - datetime(2000, 1, 3, 0, 0, 45, 1, None, fold=0), - datetime(2000, 1, 3, 0, 30, 0, 0, None, fold=0), - datetime(2000, 1, 3, 12, 0, 0, 0, None, fold=0), - datetime(2000, 1, 4), - datetime(2000, 1, 4, 0, 0, 45, 0, None, fold=0), - datetime(2000, 1, 4, 0, 30, 0, 0, None, fold=0), - datetime(2000, 1, 4, 12, 0, 0, 0, None, fold=0), - datetime(2000, 2, 2), - datetime(2000, 2, 2, 0, 0, 45, 0, None, fold=0), - datetime(2000, 2, 2, 0, 30, 0, 0, None, fold=0), - datetime(2000, 2, 2, 12, 0, 0, 0, None, fold=0), - datetime(2001, 1, 1), - datetime(2001, 1, 1, 0, 0, 45, 0, None, fold=0), - datetime(2001, 1, 1, 0, 30, 0, 0, None, fold=0), - datetime(2001, 1, 1, 12, 0, 0, 0, None, fold=0), - ] - - for first_index in range(len(sorted_list_of_datetimes)): - for second_index in range(first_index, len(sorted_list_of_datetimes)): - smaller = sorted_list_of_datetimes[first_index] - bigger = sorted_list_of_datetimes[second_index] - - if first_index == second_index: - less_than_verifier.verify(smaller, bigger, expected_result=False) - greater_than_verifier.verify(smaller, bigger, expected_result=False) - less_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - greater_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - equal_verifier.verify(smaller, bigger, expected_result=True) - not_equal_verifier.verify(smaller, bigger, expected_result=False) - else: - less_than_verifier.verify(smaller, bigger, expected_result=True) - greater_than_verifier.verify(smaller, bigger, expected_result=False) - less_than_or_equal_verifier.verify(smaller, bigger, expected_result=True) - greater_than_or_equal_verifier.verify(smaller, bigger, expected_result=False) - equal_verifier.verify(smaller, bigger, expected_result=False) - not_equal_verifier.verify(smaller, bigger, expected_result=True) - - less_than_verifier.verify(bigger, smaller, expected_result=False) - greater_than_verifier.verify(bigger, smaller, expected_result=True) - less_than_or_equal_verifier.verify(bigger, smaller, expected_result=False) - greater_than_or_equal_verifier.verify(bigger, smaller, expected_result=True) - equal_verifier.verify(bigger, smaller, expected_result=False) - not_equal_verifier.verify(bigger, smaller, expected_result=True) - - -def test_subtract_datetime(): - def function(first_date: datetime, second_date: datetime) -> timedelta: - return first_date - second_date - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1), datetime(2000, 1, 1), expected_result=timedelta(days=0)) - - verifier.verify(datetime(2000, 1, 3), datetime(2000, 1, 1), expected_result=timedelta(days=2)) - verifier.verify(datetime(2000, 1, 1), datetime(2000, 1, 3), expected_result=timedelta(days=-2)) - - verifier.verify(datetime(2000, 2, 1), datetime(2000, 1, 1), expected_result=timedelta(days=31)) - verifier.verify(datetime(2000, 1, 1), datetime(2000, 2, 1), expected_result=timedelta(days=-31)) - - verifier.verify(datetime(2001, 1, 1), datetime(2000, 1, 1), expected_result=timedelta(days=366)) - verifier.verify(datetime(2000, 1, 1), datetime(2001, 1, 1), expected_result=timedelta(days=-366)) - - verifier.verify(datetime(2000, 1, 1, 12, 0, 0), datetime(2000, 1, 1, 5, 30, 45), - expected_result=timedelta(seconds=23355)) - - -def test_add_timedelta(): - def function(first_date: datetime, difference: timedelta) -> datetime: - return first_date + difference - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1), timedelta(days=0), expected_result=datetime(2000, 1, 1)) - - verifier.verify(datetime(2000, 1, 1), timedelta(days=2), expected_result=datetime(2000, 1, 3)) - verifier.verify(datetime(2000, 1, 3), timedelta(days=-2), expected_result=datetime(2000, 1, 1)) - - verifier.verify(datetime(2000, 1, 1), timedelta(days=31), expected_result=datetime(2000, 2, 1)) - verifier.verify(datetime(2000, 2, 1), timedelta(days=-31), expected_result=datetime(2000, 1, 1)) - - verifier.verify(datetime(2000, 1, 1), timedelta(days=366), expected_result=datetime(2001, 1, 1)) - verifier.verify(datetime(2001, 1, 1), timedelta(days=-366), expected_result=datetime(2000, 1, 1)) - - verifier.verify(datetime(2000, 1, 1), timedelta(hours=12, minutes=30), expected_result=datetime(2000, 1, 1, 12, 30)) - - -def test_subtract_timedelta(): - def function(first_date: datetime, difference: timedelta) -> datetime: - return first_date - difference - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1), timedelta(days=0), expected_result=datetime(2000, 1, 1)) - - verifier.verify(datetime(2000, 1, 3), timedelta(days=2), expected_result=datetime(2000, 1, 1)) - verifier.verify(datetime(2000, 1, 1), timedelta(days=-2), expected_result=datetime(2000, 1, 3)) - - verifier.verify(datetime(2000, 2, 1), timedelta(days=31), expected_result=datetime(2000, 1, 1)) - verifier.verify(datetime(2000, 1, 1), timedelta(days=-31), expected_result=datetime(2000, 2, 1)) - - verifier.verify(datetime(2001, 1, 1), timedelta(days=366), expected_result=datetime(2000, 1, 1)) - verifier.verify(datetime(2000, 1, 1), timedelta(days=-366), expected_result=datetime(2001, 1, 1)) - verifier.verify(datetime(2000, 1, 1, 12, 30), timedelta(hours=12, minutes=30), expected_result=datetime(2000, 1, 1)) - - -def test_fromtimestamp(): - def function(timestamp: Union[int, float]) -> datetime: - return datetime.fromtimestamp(timestamp) - - verifier = verifier_for(function) - - # cannot use expected result; python timestamps use system timezone - verifier.verify(0) - verifier.verify(4000) - verifier.verify(200000) - - -def test_fromordinal(): - def function(ordinal: int) -> datetime: - return datetime.fromordinal(ordinal) - - verifier = verifier_for(function) - - verifier.verify(1, expected_result=datetime(1, 1, 1)) - verifier.verify(2, expected_result=datetime(1, 1, 2)) - verifier.verify(32, expected_result=datetime(1, 2, 1)) - verifier.verify(1000, expected_result=datetime(3, 9, 27)) - - -def test_fromisoformat(): - def function(date_string: str) -> datetime: - return datetime.fromisoformat(date_string) - - verifier = verifier_for(function) - - verifier.verify('2000-01-01', expected_result=datetime(2000, 1, 1)) - verifier.verify('1999-02-03', expected_result=datetime(1999, 2, 3)) - - -def test_fromisocalendar(): - def function(year: int, week: int, day: int) -> datetime: - return datetime.fromisocalendar(year, week, day) - - verifier = verifier_for(function) - - verifier.verify(2000, 1, 1, expected_result=datetime(2000, 1, 3)) - verifier.verify(1999, 2, 3, expected_result=datetime(1999, 1, 13)) - - -def test_date(): - def function(x: datetime) -> date: - return x.date() - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1, 1, 2, 3, 4, None, fold=0), expected_result=date(2000, 1, 1)) - verifier.verify(datetime(1999, 1, 13, 10, 20, 30, 40, None, fold=0), expected_result=date(1999, 1, 13)) - - -def test_time(): - def function(x: datetime) -> time: - return x.time() - - verifier = verifier_for(function) - - verifier.verify(datetime(2000, 1, 1, 1, 2, 3, 4, None, fold=0), expected_result=time(1, 2, 3, 4, None, fold=0)) - verifier.verify(datetime(1999, 1, 13, 10, 20, 30, 40, None, fold=0), - expected_result=time(10, 20, 30, 40, None, fold=0)) - - -def test_combine(): - def function(a: date, b: time) -> datetime: - return datetime.combine(a, b) - - verifier = verifier_for(function) - verifier.verify(date(2000, 1, 1), time(1, 2, 3, 4, None, fold=0), - expected_result=datetime(2000, 1, 1, 1, 2, 3, 4, None, fold=0)) - - -def test_replace(): - def replace_year(x: datetime, year: int) -> datetime: - return x.replace(year=year) - - def replace_month(x: datetime, month: int) -> datetime: - return x.replace(month=month) - - def replace_day(x: datetime, day: int) -> datetime: - return x.replace(day=day) - - def replace_year_month(x: datetime, year: int, month: int) -> datetime: - return x.replace(year=year, month=month) - - def replace_month_day(x: datetime, month: int, day: int) -> datetime: - return x.replace(month=month, day=day) - - def replace_year_day(x: datetime, year: int, day: int) -> datetime: - return x.replace(year=year, day=day) - - def replace_hour(x: datetime, hour: int) -> datetime: - return x.replace(hour=hour) - - def replace_minute(x: datetime, minute: int) -> datetime: - return x.replace(minute=minute) - - def replace_second(x: datetime, second: int) -> datetime: - return x.replace(second=second) - - def replace_micro(x: datetime, micro: int) -> datetime: - return x.replace(microsecond=micro) - - def replace_fold(x: datetime, fold: int) -> datetime: - return x.replace(fold=fold) - - def replace_all(x: datetime, year: int, month: int, day: int, - hour: int, minute: int, second: int, microsecond: int, tzinfo, fold: int) -> datetime: - return x.replace(year=year, month=month, day=day, - hour=hour, minute=minute, second=second, microsecond=microsecond, - tzinfo=tzinfo, fold=fold) - - replace_year_verifier = verifier_for(replace_year) - replace_month_verifier = verifier_for(replace_month) - replace_day_verifier = verifier_for(replace_day) - replace_year_month_verifier = verifier_for(replace_year_month) - replace_month_day_verifier = verifier_for(replace_month_day) - replace_year_day_verifier = verifier_for(replace_year_day) - replace_hour_verifier = verifier_for(replace_hour) - replace_minute_verifier = verifier_for(replace_minute) - replace_second_verifier = verifier_for(replace_second) - replace_micro_verifier = verifier_for(replace_micro) - replace_fold_verifier = verifier_for(replace_fold) - replace_all_verifier = verifier_for(replace_all) - - replace_year_verifier.verify(datetime(2002, 12, 4), 3000, expected_result=datetime(3000, 12, 4)) - replace_month_verifier.verify(datetime(2002, 12, 4), 3, expected_result=datetime(2002, 3, 4)) - replace_day_verifier.verify(datetime(2002, 12, 4), 10, expected_result=datetime(2002, 12, 10)) - replace_year_month_verifier.verify(datetime(2002, 12, 4), 3000, 3, expected_result=datetime(3000, 3, 4)) - replace_month_day_verifier.verify(datetime(2002, 12, 4), 3, 10, expected_result=datetime(2002, 3, 10)) - replace_year_day_verifier.verify(datetime(2002, 12, 4), 3000, 10, expected_result=datetime(3000, 12, 10)) - replace_hour_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), 10, - expected_result=datetime(2002, 12, 4, 10, 2, 3, 4, None, fold=0)) - replace_minute_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), 20, - expected_result=datetime(2002, 12, 4, 1, 20, 3, 4, None, fold=0)) - replace_second_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), 30, - expected_result=datetime(2002, 12, 4, 1, 2, 30, 4, None, fold=0)) - replace_micro_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), 40, - expected_result=datetime(2002, 12, 4, 1, 2, 3, 40, None, fold=0)) - replace_fold_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), 1, - expected_result=datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=1)) - replace_all_verifier.verify(datetime(2002, 12, 4, 1, 2, 3, 4, None, fold=0), - 3000, 2, 1, 10, 20, 30, 40, None, 1, - expected_result=datetime(3000, 2, 1, 10, 20, 30, 40, None, fold=1)) - - -def test_timetuple(): - def function(x: datetime) -> tuple: - return x.timetuple() - - verifier = verifier_for(function) - - # TODO: enable type checking when named tuples are supported - verifier.verify(datetime(1, 1, 1), expected_result=(1, 1, 1, 0, 0, 0, 0, 1, -1), type_check=False) - verifier.verify(datetime(3, 9, 27), expected_result=(3, 9, 27, 0, 0, 0, 5, 270, -1), type_check=False) - - -def test_toordinal(): - def function(x: datetime) -> int: - return x.toordinal() - - verifier = verifier_for(function) - - verifier.verify(datetime(1, 1, 1), expected_result=1) - verifier.verify(datetime(1, 1, 2), expected_result=2) - verifier.verify(datetime(1, 2, 1), expected_result=32) - verifier.verify(datetime(3, 9, 27), expected_result=1000) - - -def test_weekday(): - def function(x: datetime) -> int: - return x.weekday() - - verifier = verifier_for(function) - - verifier.verify(datetime(2002, 12, 4), expected_result=2) - - -def test_isoweekday(): - def function(x: datetime) -> int: - return x.isoweekday() - - verifier = verifier_for(function) - - verifier.verify(datetime(2002, 12, 4), expected_result=3) - - -def test_isocalendar(): - def function(x: datetime) -> tuple: - return x.isocalendar() - - verifier = verifier_for(function) - - verifier.verify(datetime(2003, 12, 29), expected_result=(2004, 1, 1), type_check=False) - verifier.verify(datetime(2004, 1, 4), expected_result=(2004, 1, 7), type_check=False) - - -def test_isoformat(): - def function(x: datetime) -> str: - return x.isoformat() - - verifier = verifier_for(function) - - verifier.verify(datetime(2003, 12, 29), expected_result='2003-12-29T00:00:00') - verifier.verify(datetime(2004, 1, 4), expected_result='2004-01-04T00:00:00') - - -def test_str(): - def function(x: datetime) -> str: - return str(x) - - verifier = verifier_for(function) - - verifier.verify(datetime(2003, 12, 29), expected_result='2003-12-29 00:00:00') - verifier.verify(datetime(2004, 1, 4), expected_result='2004-01-04 00:00:00') - - -def test_ctime(): - def function(x: datetime) -> str: - return x.ctime() - - verifier = verifier_for(function) - - verifier.verify(datetime(2002, 12, 4), expected_result='Wed Dec 4 00:00:00 2002') - - -def test_strftime(): - def function(x: datetime, fmt: str) -> str: - return x.strftime(fmt) - - verifier = verifier_for(function) - - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%a', - expected_result='Sat') - # Java C Locale uses the short form for the full variant of week days - # verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%A', - # expected_result='Saturday') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%W', - expected_result='05') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%d', - expected_result='03') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%b', - expected_result='Feb') - # Java C Locale uses the short form for the full variant of months - # verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%B', - # expected_result='February') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%m', - expected_result='02') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%y', - expected_result='01') - verifier.verify(datetime(1001, 2, 3, 4, 5, 6, 7), '%y', - expected_result='01') - # %Y have different results depending on the platform; - # Windows 0-pad it, Linux does not. - # verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%Y', - # expected_result='1') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%j', - expected_result='034') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%U', - expected_result='04') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%W', - expected_result='05') - # %Y have different results depending on the platform; - # Windows 0-pad it, Linux does not. - # verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%G', - # expected_result='1') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%u', - expected_result='6') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%%', - expected_result='%') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%V', - expected_result='05') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%H', - expected_result='04') - verifier.verify(datetime(12, 2, 3, 13, 5, 6, 7), '%I', - expected_result='01') - verifier.verify(datetime(13, 2, 3, 4, 5, 6, 7), '%p', - expected_result='AM') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%M', - expected_result='05') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%S', - expected_result='06') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%f', - expected_result='000007') - # %X is locale-specific, and Java/Python locale definitions can slightly differ - # ex: en_US = '4:05:06 AM' in Java, but '04:05:06 AM' in Python - # verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%X', - # expected_result='04:05:06 AM') - verifier.verify(datetime(1, 2, 3, 4, 5, 6, 7), '%%', - expected_result='%') - - -def test_strptime(): - def function(date_string: str, fmt: str) -> datetime: - return datetime.strptime(date_string, fmt) - - verifier = verifier_for(function) - - verifier.verify("21 Jun, 2018", "%d %b, %Y", - expected_result=datetime(2018, 6, 21)) - verifier.verify("12/11/2018 09:15:32", "%m/%d/%Y %H:%M:%S", - expected_result=datetime(2018, 12, 11, 9, 15, 32)) - verifier.verify("12/11/2018 09:15:32", "%d/%m/%Y %H:%M:%S", - expected_result=datetime(2018, 11, 12, 9, 15, 32)) - verifier.verify("09:15:32", "%H:%M:%S", - expected_result=datetime(1900, 1, 1, 9, 15, 32)) - verifier.verify("text", "%H:%M:%S", - expected_error=ValueError) diff --git a/jpyinterpreter/tests/datetime/test_time.py b/jpyinterpreter/tests/datetime/test_time.py deleted file mode 100644 index ada42f2b..00000000 --- a/jpyinterpreter/tests/datetime/test_time.py +++ /dev/null @@ -1,199 +0,0 @@ -from datetime import time - -from ..conftest import verifier_for - - -# Constructor -def test_constructor(): - def function(hour: int, minute: int, second: int, microsecond: int, tzinfo, fold: int) -> time: - return time(hour=hour, minute=minute, second=second, microsecond=microsecond, tzinfo=tzinfo, fold=fold) - - verifier = verifier_for(function) - - verifier.verify(1, 2, 3, 4, None, 0, expected_result=time(hour=1, minute=2, second=3, microsecond=4, - tzinfo=None, fold=0)) - verifier.verify(10, 20, 30, 40, None, 1, expected_result=time(hour=10, minute=20, second=30, microsecond=40, - tzinfo=None, fold=1)) - - verifier.verify(-1, 2, 3, 4, None, 0, expected_error=ValueError) - verifier.verify(24, 2, 3, 4, None, 0, expected_error=ValueError) - - verifier.verify(1, -1, 3, 4, None, 0, expected_error=ValueError) - verifier.verify(1, 60, 3, 4, None, 0, expected_error=ValueError) - - verifier.verify(1, 2, -1, 4, None, 0, expected_error=ValueError) - verifier.verify(1, 2, 60, 4, None, 0, expected_error=ValueError) - - verifier.verify(1, 2, 3, -1, None, 0, expected_error=ValueError) - verifier.verify(1, 2, 3, 1_000_000, None, 0, expected_error=ValueError) - - verifier.verify(1, 2, 3, 4, None, -1, expected_error=ValueError) - verifier.verify(1, 2, 3, 4, None, 2, expected_error=ValueError) - - -# Instance attributes -def test_instance_attributes(): - def function(a: time) -> tuple: - return a.hour, a.minute, a.second, a.microsecond, a.tzinfo, a.fold - - verifier = verifier_for(function) - - verifier.verify(time(hour=1, minute=2, second=3, microsecond=4, - tzinfo=None, fold=0), - expected_result=(1, 2, 3, 4, None, 0)) - - verifier.verify(time(hour=10, minute=20, second=30, microsecond=40, - tzinfo=None, fold=1), - expected_result=(10, 20, 30, 40, None, 1)) - - -# Operations -def test_comparisons(): - def equal(a: time, b: time) -> bool: - return a == b - - def not_equal(a: time, b: time) -> bool: - return a != b - - equal_verifier = verifier_for(equal) - not_equal_verifier = verifier_for(not_equal) - - sorted_list_of_times = [ - time(hour=1, minute=2, second=3, microsecond=4, - tzinfo=None, fold=0), - time(hour=10, minute=20, second=30, microsecond=40, - tzinfo=None, fold=1) - ] - - for first_index in range(len(sorted_list_of_times)): - for second_index in range(first_index, len(sorted_list_of_times)): - a = sorted_list_of_times[first_index] - b = sorted_list_of_times[second_index] - - if first_index == second_index: - equal_verifier.verify(a, b, expected_result=True) - not_equal_verifier.verify(a, b, expected_result=False) - else: - equal_verifier.verify(a, b, expected_result=False) - not_equal_verifier.verify(a, b, expected_result=True) - - equal_verifier.verify(b, a, expected_result=False) - not_equal_verifier.verify(b, a, expected_result=True) - - -def test_fromisoformat(): - def function(time_string: str) -> time: - return time.fromisoformat(time_string) - - verifier = verifier_for(function) - - verifier.verify('04:23:01', expected_result=time(4, 23, 1)) - verifier.verify('04:23:01.000384', expected_result=time(4, 23, 1, 384)) - # TODO: Timezone support - # verifier.verify('04:23:01+04:00', expected_result=time(4, 23, 1, - # tzinfo=timezone(timedelta(seconds=14400)))) - - -def test_replace(): - def replace_hour(x: time, hour: int) -> time: - return x.replace(hour=hour) - - def replace_minute(x: time, minute: int) -> time: - return x.replace(minute=minute) - - def replace_second(x: time, second: int) -> time: - return x.replace(second=second) - - def replace_micro(x: time, micro: int) -> time: - return x.replace(microsecond=micro) - - def replace_fold(x: time, fold: int) -> time: - return x.replace(fold=fold) - - def replace_all(x: time, hour: int, minute: int, second: int, microsecond: int, tzinfo, fold: int) -> time: - return x.replace(hour=hour, minute=minute, second=second, microsecond=microsecond, tzinfo=tzinfo, fold=fold) - - replace_hour_verifier = verifier_for(replace_hour) - replace_minute_verifier = verifier_for(replace_minute) - replace_second_verifier = verifier_for(replace_second) - replace_micro_verifier = verifier_for(replace_micro) - replace_fold_verifier = verifier_for(replace_fold) - replace_all_verifier = verifier_for(replace_all) - - replace_hour_verifier.verify(time(1, 2, 3, 4, None, fold=0), 10, - expected_result=time(10, 2, 3, 4, None, fold=0)) - replace_minute_verifier.verify(time(1, 2, 3, 4, None, fold=0), 20, - expected_result=time(1, 20, 3, 4, None, fold=0)) - replace_second_verifier.verify(time(1, 2, 3, 4, None, fold=0), 30, - expected_result=time(1, 2, 30, 4, None, fold=0)) - replace_micro_verifier.verify(time(1, 2, 3, 4, None, fold=0), 40, - expected_result=time(1, 2, 3, 40, None, fold=0)) - replace_fold_verifier.verify(time(1, 2, 3, 4, None, fold=0), 1, - expected_result=time(1, 2, 3, 4, None, fold=1)) - replace_all_verifier.verify(time(1, 2, 3, 4, None, fold=0), - 10, 20, 30, 40, None, 1, - expected_result=time(10, 20, 30, 40, None, fold=1)) - - -def test_isoformat(): - def function(x: time) -> str: - return x.isoformat() - - def function_with_timespec(x: time, timespec: str) -> str: - return x.isoformat(timespec) - - verifier = verifier_for(function) - timespec_verifier = verifier_for(function_with_timespec) - - verifier.verify(time(1, 2, 3, 4, None, fold=0), expected_result='01:02:03.000004') - verifier.verify(time(10, 20, 30, 40, None, fold=1), expected_result='10:20:30.000040') - - verifier.verify(time(1, 2, 3, 0, None, fold=0), expected_result='01:02:03') - verifier.verify(time(10, 20, 30, 0, None, fold=1), expected_result='10:20:30') - - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'auto', expected_result='10:20:30.000040') - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'hours', expected_result='10') - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'minutes', expected_result='10:20') - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'seconds', expected_result='10:20:30') - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'milliseconds', expected_result='10:20:30.000') - timespec_verifier.verify(time(10, 20, 30, 40, None, fold=0), 'microseconds', expected_result='10:20:30.000040') - - -def test_str(): - def function(x: time) -> str: - return str(x) - - verifier = verifier_for(function) - - verifier.verify(time(1, 2, 3, 0, None, fold=0), expected_result='01:02:03') - verifier.verify(time(10, 20, 30, 0, None, fold=1), expected_result='10:20:30') - verifier.verify(time(1, 2, 3, 4, None, fold=0), expected_result='01:02:03.000004') - - -def test_strftime(): - def function(x: time, fmt: str) -> str: - return x.strftime(fmt) - - verifier = verifier_for(function) - - verifier.verify(time(1, 2, 3, 4, None, fold=0), '%H', - expected_result='01') - verifier.verify(time(13, 2, 3, 4, None, fold=0), '%I', - expected_result='01') - verifier.verify(time(13, 2, 3, 4, None, fold=0), '%p', - expected_result='PM') - verifier.verify(time(1, 2, 3, 4, None, fold=0), '%M', - expected_result='02') - verifier.verify(time(1, 2, 3, 4, None, fold=0), '%S', - expected_result='03') - verifier.verify(time(1, 2, 3, 4, None, fold=0), '%f', - expected_result='000004') - - # %X is locale-specific, and Java/Python locale definitions can slightly differ - # ex: en_US = '1:02:03 AM' in Java, but '01:02:03 AM' in Python - # verifier.verify(time(1, 2, 3, 4, None, fold=0), '%X', - # expected_result='01:02:03 AM') - verifier.verify(time(1, 2, 3, 4, None, fold=0), '%%', - expected_result='%') - -# TODO: strftime, __format__, utcoffset, dst, tzname diff --git a/jpyinterpreter/tests/datetime/test_timedelta.py b/jpyinterpreter/tests/datetime/test_timedelta.py deleted file mode 100644 index c946f563..00000000 --- a/jpyinterpreter/tests/datetime/test_timedelta.py +++ /dev/null @@ -1,310 +0,0 @@ -from datetime import timedelta - -from ..conftest import verifier_for - - -# Constructor -def test_constructor(): - def positional_constructor(days: int, seconds: int, microseconds: int, - milliseconds: int, minutes: int, hours: int, weeks: int) -> timedelta: - return timedelta(days, seconds, microseconds, - milliseconds, minutes, hours, weeks) - - def keyword_constructor(days: int, seconds: int, microseconds: int, - milliseconds: int, minutes: int, hours: int, weeks: int) -> timedelta: - return timedelta(days=days, seconds=seconds, microseconds=microseconds, - milliseconds=milliseconds, minutes=minutes, hours=hours, weeks=weeks) - - def partial_constructor(seconds: int) -> timedelta: - return timedelta(seconds=seconds) - - positional_verifier = verifier_for(positional_constructor) - keyword_verifier = verifier_for(keyword_constructor) - partial_verifier = verifier_for(partial_constructor) - - positional_verifier.verify(50, 27, 10, 29000, 5, 8, 2, - expected_result=timedelta(days=64, seconds=29156, microseconds=10)) - keyword_verifier.verify(50, 27, 10, 29000, 5, 8, 2, - expected_result=timedelta(days=64, seconds=29156, microseconds=10)) - partial_verifier.verify(29156, expected_result=timedelta(seconds=29156)) - - -# Instance attributes -def test_instance_attributes(): - def function(a: timedelta) -> tuple: - return a.days, a.seconds, a.microseconds - - verifier = verifier_for(function) - - verifier.verify(timedelta(days=50, seconds=27, microseconds=10, milliseconds=29000, minutes=5, hours=8, weeks=2), - expected_result=(64, 29156, 10)) - - -# Operations -def test_add(): - def function(a: timedelta, b: timedelta) -> timedelta: - return a + b - - verifier = verifier_for(function) - - verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - timedelta(days=2, seconds=3, microseconds=4, milliseconds=5, minutes=6, hours=7, weeks=8), - expected_result= - timedelta(days=3, seconds=5, microseconds=7, milliseconds=9, minutes=11, hours=13, weeks=15)) - - -def test_subtract(): - def function(a: timedelta, b: timedelta) -> timedelta: - return a - b - - verifier = verifier_for(function) - - verifier.verify(timedelta(days=2, seconds=3, microseconds=4, milliseconds=5, minutes=6, hours=7, weeks=8), - timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - expected_result= - timedelta(days=1, seconds=1, microseconds=1, milliseconds=1, minutes=1, hours=1, weeks=1)) - - verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - timedelta(days=2, seconds=3, microseconds=4, milliseconds=5, minutes=6, hours=7, weeks=8), - expected_result= - timedelta(days=-1, seconds=-1, microseconds=-1, milliseconds=-1, minutes=-1, hours=-1, weeks=-1)) - - verifier.verify(timedelta(days=3, seconds=5, microseconds=7, milliseconds=9, minutes=11, hours=13, weeks=15), - timedelta(days=2, seconds=3, microseconds=4, milliseconds=5, minutes=6, hours=7, weeks=8), - expected_result= - timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7)) - - -def test_multiply(): - def left_int_function(a: timedelta, b: int) -> timedelta: - return a * b - - def right_int_function(a: int, b: timedelta) -> timedelta: - return a * b - - def left_float_function(a: timedelta, b: float) -> timedelta: - return a * b - - def right_float_function(a: float, b: timedelta) -> timedelta: - return a * b - - left_int_verifier = verifier_for(left_int_function) - right_int_verifier = verifier_for(right_int_function) - left_float_verifier = verifier_for(left_float_function) - right_float_verifier = verifier_for(right_float_function) - - left_int_verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), 3, - expected_result= - timedelta(days=3, seconds=6, microseconds=9, milliseconds=12, minutes=15, hours=18, weeks=21)) - left_int_verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), 0, - expected_result= - timedelta(0, 0, 0)) - left_int_verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7),-3, - expected_result= - timedelta(days=-3, seconds=-6, microseconds=-9, milliseconds=-12, minutes=-15, hours=-18, weeks=-21)) - - right_int_verifier.verify(3, timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - expected_result= - timedelta(days=3, seconds=6, microseconds=9, milliseconds=12, minutes=15, hours=18, weeks=21)) - right_int_verifier.verify(0, timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - expected_result= - timedelta(0, 0, 0)) - right_int_verifier.verify(-3, timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - expected_result= - timedelta(days=-3, seconds=-6, microseconds=-9, milliseconds=-12, minutes=-15, hours=-18, weeks=-21)) - - left_float_verifier.verify(timedelta(days=2, seconds=4, microseconds=6, milliseconds=8, minutes=10, hours=12, weeks=14), 1.5, - expected_result= - timedelta(days=3, seconds=6, microseconds=9, milliseconds=12, minutes=15, hours=18, weeks=21)) - left_float_verifier.verify(timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), 0.0, - expected_result= - timedelta(0, 0, 0)) - left_float_verifier.verify(timedelta(days=2, seconds=4, microseconds=6, milliseconds=8, minutes=10, hours=12, weeks=14), -1.5, - expected_result= - timedelta(days=-3, seconds=-6, microseconds=-9, milliseconds=-12, minutes=-15, hours=-18, weeks=-21)) - - right_float_verifier.verify(1.5, timedelta(days=2, seconds=4, microseconds=6, milliseconds=8, minutes=10, hours=12, weeks=14), - expected_result= - timedelta(days=3, seconds=6, microseconds=9, milliseconds=12, minutes=15, hours=18, weeks=21)) - right_float_verifier.verify(0.0, timedelta(days=1, seconds=2, microseconds=3, milliseconds=4, minutes=5, hours=6, weeks=7), - expected_result= - timedelta(0, 0, 0)) - right_float_verifier.verify(-1.5, timedelta(days=2, seconds=4, microseconds=6, milliseconds=8, minutes=10, hours=12, weeks=14), - expected_result= - timedelta(days=-3, seconds=-6, microseconds=-9, milliseconds=-12, minutes=-15, hours=-18, weeks=-21)) - - -def test_timedelta_division(): - def function(a: timedelta, b: timedelta) -> float: - return a / b - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), timedelta(seconds=0), expected_error=ZeroDivisionError) - verifier.verify(timedelta(seconds=10), timedelta(seconds=5), expected_result=2.0) - verifier.verify(timedelta(seconds=5), timedelta(seconds=10), expected_result=0.5) - - verifier.verify(timedelta(seconds=-10), timedelta(seconds=5), expected_result=-2.0) - verifier.verify(timedelta(seconds=10), timedelta(seconds=-5), expected_result=-2.0) - verifier.verify(timedelta(seconds=-10), timedelta(seconds=-5), expected_result=2.0) - - -def test_number_division(): - def int_function(a: timedelta, b: int) -> timedelta: - return a / b - - def float_function(a: timedelta, b: float) -> timedelta: - return a / b - - int_verifier = verifier_for(int_function) - float_verifier = verifier_for(float_function) - - int_verifier.verify(timedelta(seconds=10), 0, expected_error=ZeroDivisionError) - int_verifier.verify(timedelta(seconds=10), 2, expected_result=timedelta(seconds=5)) - int_verifier.verify(timedelta(seconds=5), 10, expected_result=timedelta(seconds=0.5)) - - int_verifier.verify(timedelta(seconds=-10), 2, expected_result=timedelta(seconds=-5)) - int_verifier.verify(timedelta(seconds=10), -2, expected_result=timedelta(seconds=-5)) - int_verifier.verify(timedelta(seconds=-10), -2, expected_result=timedelta(seconds=5)) - - float_verifier.verify(timedelta(seconds=10), 0.0, expected_error=ZeroDivisionError) - float_verifier.verify(timedelta(seconds=7.5), 1.5, expected_result=timedelta(seconds=5)) - float_verifier.verify(timedelta(seconds=5), 10.0, expected_result=timedelta(seconds=0.5)) - - float_verifier.verify(timedelta(seconds=-10), 2.0, expected_result=timedelta(seconds=-5)) - float_verifier.verify(timedelta(seconds=10), -2.0, expected_result=timedelta(seconds=-5)) - float_verifier.verify(timedelta(seconds=-10), -2.0, expected_result=timedelta(seconds=5)) - - -def test_timedelta_floor_division(): - def function(a: timedelta, b: timedelta) -> int: - return a // b - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), timedelta(seconds=0), expected_error=ZeroDivisionError) - verifier.verify(timedelta(seconds=10), timedelta(seconds=5), expected_result=2) - verifier.verify(timedelta(seconds=5), timedelta(seconds=10), expected_result=0) - - verifier.verify(timedelta(seconds=-10), timedelta(seconds=5), expected_result=-2) - verifier.verify(timedelta(seconds=10), timedelta(seconds=-5), expected_result=-2) - verifier.verify(timedelta(seconds=-10), timedelta(seconds=-5), expected_result=2) - - -def test_number_floor_division(): - def int_function(a: timedelta, b: int) -> timedelta: - return a // b - - int_verifier = verifier_for(int_function) - - int_verifier.verify(timedelta(seconds=10), 0, expected_error=ZeroDivisionError) - int_verifier.verify(timedelta(seconds=10), 2, expected_result=timedelta(seconds=5)) - int_verifier.verify(timedelta(seconds=5), 10, expected_result=timedelta(seconds=0.5)) - - int_verifier.verify(timedelta(seconds=-10), 2, expected_result=timedelta(seconds=-5)) - int_verifier.verify(timedelta(seconds=10), -2, expected_result=timedelta(seconds=-5)) - int_verifier.verify(timedelta(seconds=-10), -2, expected_result=timedelta(seconds=5)) - - -def test_mod(): - def function(a: timedelta, b: timedelta) -> timedelta: - return a % b - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), timedelta(seconds=0), expected_error=ZeroDivisionError) - verifier.verify(timedelta(seconds=10), timedelta(seconds=5), expected_result=timedelta(seconds=0)) - verifier.verify(timedelta(seconds=5), timedelta(seconds=10), expected_result=timedelta(seconds=5)) - verifier.verify(timedelta(seconds=15), timedelta(seconds=10), expected_result=timedelta(seconds=5)) - verifier.verify(timedelta(seconds=-15), timedelta(seconds=10), expected_result=timedelta(seconds=5)) - verifier.verify(timedelta(seconds=15), timedelta(seconds=-10), expected_result=timedelta(seconds=-5)) - verifier.verify(timedelta(seconds=-15), timedelta(seconds=-10), expected_result=timedelta(seconds=-5)) - - -def test_divmod(): - def function(a: timedelta, b: timedelta) -> tuple: - return divmod(a, b) - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), timedelta(seconds=0), expected_error=ZeroDivisionError) - verifier.verify(timedelta(seconds=10), timedelta(seconds=5), expected_result=(2, timedelta(seconds=0))) - verifier.verify(timedelta(seconds=5), timedelta(seconds=10), expected_result=(0, timedelta(seconds=5))) - verifier.verify(timedelta(seconds=15), timedelta(seconds=10), expected_result=(1, timedelta(seconds=5))) - verifier.verify(timedelta(seconds=-15), timedelta(seconds=10), expected_result=(-2, timedelta(seconds=5))) - verifier.verify(timedelta(seconds=-15), timedelta(seconds=-10), expected_result=(1, timedelta(seconds=-5))) - verifier.verify(timedelta(seconds=15), timedelta(seconds=-10), expected_result=(-2, timedelta(seconds=-5))) - - -def test_pos(): - def function(a: timedelta) -> timedelta: - return +a - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), expected_result=timedelta(seconds=10)) - verifier.verify(timedelta(seconds=0), expected_result=timedelta(seconds=0)) - verifier.verify(timedelta(seconds=-10), expected_result=timedelta(seconds=-10)) - - -def test_negate(): - def function(a: timedelta) -> timedelta: - return -a - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), expected_result=timedelta(seconds=-10)) - verifier.verify(timedelta(seconds=0), expected_result=timedelta(seconds=0)) - verifier.verify(timedelta(seconds=-10), expected_result=timedelta(seconds=10)) - - -def test_abs(): - def function(a: timedelta) -> timedelta: - return abs(a) - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), expected_result=timedelta(seconds=10)) - verifier.verify(timedelta(seconds=0), expected_result=timedelta(seconds=0)) - verifier.verify(timedelta(seconds=-10), expected_result=timedelta(seconds=10)) - - -def test_str(): - def function(a: timedelta) -> str: - return str(a) - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), expected_result='0:00:10') - verifier.verify(timedelta(seconds=0), expected_result='0:00:00') - verifier.verify(timedelta(seconds=-10), expected_result='-1 day, 23:59:50') - verifier.verify(timedelta(seconds=10, microseconds=123456), expected_result='0:00:10.123456') - verifier.verify(timedelta(weeks=1, hours=16, minutes=30), expected_result='7 days, 16:30:00') - - -def test_repr(): - def function(a: timedelta) -> str: - return repr(a) - - verifier = verifier_for(function) - - verifier.verify(timedelta(seconds=10), expected_result='datetime.timedelta(seconds=10)') - verifier.verify(timedelta(seconds=0), expected_result='datetime.timedelta(0)') - verifier.verify(timedelta(seconds=-10), expected_result='datetime.timedelta(days=-1, seconds=86390)') - verifier.verify(timedelta(seconds=10, microseconds=123456), - expected_result='datetime.timedelta(seconds=10, microseconds=123456)') - verifier.verify(timedelta(weeks=1, hours=16, minutes=30), expected_result='datetime.timedelta(days=7, seconds=59400)') - - -def test_between_0_and_30_minutes(): - def function(a: timedelta) -> bool: - return timedelta(minutes=0) <= a <= timedelta(minutes=30) - - verifier = verifier_for(function) - - verifier.verify(timedelta(minutes=0), expected_result=True) - verifier.verify(timedelta(minutes=15), expected_result=True) - verifier.verify(timedelta(minutes=30), expected_result=True) - - verifier.verify(timedelta(minutes=-15), expected_result=False) - verifier.verify(timedelta(minutes=45), expected_result=False) diff --git a/jpyinterpreter/tests/test_builtins.py b/jpyinterpreter/tests/test_builtins.py deleted file mode 100644 index d89b962b..00000000 --- a/jpyinterpreter/tests/test_builtins.py +++ /dev/null @@ -1,795 +0,0 @@ -import jpyinterpreter -import pytest -import sys -from typing import SupportsAbs, Iterable, Callable, Sequence, Union, Iterator, Sized, Reversible, SupportsIndex - -from jpype import JImplementationFor - -from .conftest import verifier_for - - -# 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()}\n' - f'{exception.stacktrace()}') - 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 test_abs(): - def my_function(x: SupportsAbs) -> object: - return abs(x) - - class MyClassWithAbs: - def __abs__(self) -> int: - return 10 - - verifier = verifier_for(my_function) - - verifier.verify(1, expected_result=1) - verifier.verify(-1, expected_result=1) - verifier.verify(1.0, expected_result=1.0) - verifier.verify(-1.0, expected_result=1.0) - verifier.verify(MyClassWithAbs(), expected_result=10) - - -def test_any(): - def my_function(x: Iterable) -> bool: - return any(x) - - class MyClassWithoutBool: - pass - - class MyClassWithBool: - is_true: bool - - def __init__(self, is_true: bool): - self.is_true = is_true - - def __bool__(self) -> bool: - return self.is_true - - verifier = verifier_for(my_function) - verifier.verify([], expected_result=False) - verifier.verify([False, 0, 0.0, MyClassWithBool(False), None], expected_result=False) - verifier.verify([False, MyClassWithBool(True)], expected_result=True) - verifier.verify([MyClassWithoutBool()], expected_result=True) - verifier.verify([1], expected_result=True) - verifier.verify([1.0], expected_result=True) - verifier.verify([True], expected_result=True) - - -def test_all(): - def my_function(x: Iterable) -> bool: - return all(x) - - class MyClassWithoutBool: - pass - - class MyClassWithBool: - is_true: bool - - def __init__(self, is_true: bool): - self.is_true = is_true - - def __bool__(self) -> bool: - return self.is_true - - verifier = verifier_for(my_function) - verifier.verify([], expected_result=True) - verifier.verify([True, 1, 1.0, MyClassWithBool(True)], expected_result=True) - verifier.verify([False, MyClassWithBool(True)], expected_result=False) - verifier.verify([MyClassWithoutBool()], expected_result=True) - verifier.verify([0], expected_result=False) - verifier.verify([0.0], expected_result=False) - verifier.verify([False], expected_result=False) - verifier.verify([None], expected_result=False) - - -def test_ascii(): - def my_function(x: object) -> str: - return ascii(x) - - verifier = verifier_for(my_function) - verifier.verify(10, expected_result='10') - verifier.verify('text', expected_result="'text'") - verifier.verify('text\nwith\nlines', expected_result="'text\\nwith\\nlines'") - - -def test_bin(): - def my_function(x: int) -> str: - return bin(x) - - verifier = verifier_for(my_function) - verifier.verify(10, expected_result=bin(10)) - verifier.verify(-10, expected_result=bin(-10)) - - -def test_bool(): - def my_function(x: object) -> bool: - return bool(x) - - verifier = verifier_for(my_function) - verifier.verify(0, expected_result=False) - verifier.verify(1, expected_result=True) - verifier.verify(-1, expected_result=True) - verifier.verify('', expected_result=False) - verifier.verify('test', expected_result=True) - verifier.verify(True, expected_result=True) - verifier.verify(False, expected_result=False) - - -def test_callable(): - def my_function(x: Callable) -> bool: - return callable(x) - - verifier = verifier_for(my_function) - verifier.verify(10, expected_result=False) - verifier.verify(my_function, expected_result=True) - verifier.verify(int, expected_result=True) - - -def test_chr(): - def my_function(x: int) -> str: - return chr(x) - - verifier = verifier_for(my_function) - verifier.verify(30, expected_result=chr(30)) - verifier.verify(2400, expected_result=chr(2400)) - - -def test_delattr(): - def my_function(x: object) -> bool: - delattr(x, 'my_attr') - return hasattr(x, 'my_attr') - - class TestObject: - pass - - a = TestObject() - a.my_attr = 'test' - - verifier = verifier_for(my_function) - verifier.verify(a, expected_result=False) - verifier.verify(TestObject(), expected_error=AttributeError) - - -def test_divmod(): - def my_function(x: any, y: any) -> tuple: - return divmod(x, y) - - verifier = verifier_for(my_function) - verifier.verify(16, 5) - verifier.verify(-16, 5) - verifier.verify(16, -5) - verifier.verify(-16, -5) - - verifier.verify(16, 5.0) - verifier.verify(-16, 5.0) - verifier.verify(16, -5.0) - verifier.verify(-16, -5.0) - - verifier.verify(16.0, 5.0) - verifier.verify(-16.0, 5.0) - verifier.verify(16.0, -5.0) - verifier.verify(-16.0, -5.0) - - -def test_dict(): - def my_function(x: object) -> dict: - out = dict() - out['key'] = x - return out - - verifier = verifier_for(my_function) - verifier.verify('value', expected_result={'key': 'value'}) - verifier.verify(10, expected_result={'key': 10}) - - -def test_enumerate(): - def my_function_without_start(x: Sequence) -> tuple: - return tuple(enumerate(x)) - - def my_function_with_start(x: Sequence, start: int) -> tuple: - return tuple(enumerate(x, start)) - - verifier = verifier_for(my_function_without_start) - with_start_verifier = verifier_for(my_function_with_start) - verifier.verify(['a', 'b', 'c'], expected_result=((0, 'a'), (1, 'b'), (2, 'c'))) - with_start_verifier.verify(['a', 'b', 'c'], 5, expected_result=((5, 'a'), (6, 'b'), (7, 'c'))) - - -def test_filter(): - def my_function(function: Callable[[any], bool], iterable: Iterable) -> tuple: - return tuple(filter(function, iterable)) - - verifier = verifier_for(my_function) - - verifier.verify(None, [0, 1, False, 2], expected_result=(1, 2)) - verifier.verify(lambda x: x == 2 or x is False, [0, 1, False, 2], expected_result=(False, 2)) - - -def test_float(): - import math - - def my_function(x: any) -> float: - return float(x) - - verifier = verifier_for(my_function) - verifier.verify(10, expected_result=10.0) - verifier.verify('1.0', expected_result=1.0) - verifier.verify_property('nan', predicate=math.isnan) - verifier.verify_property('NaN', predicate=math.isnan) - verifier.verify_property('-nan', predicate=math.isnan) - verifier.verify_property('-NaN', predicate=math.isnan) - verifier.verify('inf', expected_result=float('inf')) - verifier.verify('INF', expected_result=float('inf')) - verifier.verify('-inf', expected_result=float('-inf')) - verifier.verify('infinity', expected_result=float('inf')) - verifier.verify('-infinity', expected_result=float('-inf')) - - -def test_format(): - def my_function(x: object) -> str: - return format(x) - - def my_function_with_spec(x: object, spec: str) -> str: - return format(x, spec) - - verifier = verifier_for(my_function) - with_spec_verifier = verifier_for(my_function_with_spec) - - verifier.verify(10, expected_result='10') - with_spec_verifier.verify(10, '', expected_result='10') - - -def test_getattr(): - def my_function(x: any, name: str) -> any: - return getattr(x, name) - - def my_function_with_default(x: any, name: str, default: any) -> any: - return getattr(x, name, default) - - class TestObject: - pass - - verifier = verifier_for(my_function) - with_default_verifier = verifier_for(my_function_with_default) - - a = TestObject() - a.test = 'value' - - verifier.verify(a, 'test', expected_result='value') - with_default_verifier.verify(a, 'missing', 10, expected_result=10) - - -global_variable = 10 - - -def test_globals(): - def my_function() -> any: - global global_variable - x = global_variable - return globals() - - verifier = verifier_for(my_function) - # The globals directory in Java only stores used globals - verifier.verify_property(predicate=lambda out: out['global_variable'] == 10) - - -def test_hasattr(): - def my_function(x: any, name: str) -> bool: - return hasattr(x, name) - - class TestObject: - pass - - verifier = verifier_for(my_function) - a = TestObject() - a.test = 'value' - verifier.verify(a, 'test', expected_result=True) - verifier.verify(a, 'other', expected_result=False) - - -def test_hash(): - def my_function(x: any) -> int: - return hash(x) - - verifier = verifier_for(my_function) - verifier.verify(1) - verifier.verify(1.0) - verifier.verify(True) - - -def test_id(): - def my_function(x: object) -> int: - return id(x) - - a = object() - verifier = verifier_for(my_function) - verifier.verify(a, clone_arguments=False) - - -def test_int(): - def my_function(x: any) -> int: - return int(x) - - verifier = verifier_for(my_function) - verifier.verify(1.5, expected_result=1) - verifier.verify(1.0, expected_result=1) - verifier.verify('2', expected_result=2) - - -def test_isinstance(): - def my_function(x: object, y: Union[type, tuple]) -> bool: - return isinstance(x, y) - - verifier = verifier_for(my_function) - verifier.verify(1, int, expected_result=True) - verifier.verify(True, int, expected_result=True) - verifier.verify(1.0, int, expected_result=False) - verifier.verify(int, int, expected_result=False) - verifier.verify(1.0, (int, float), expected_result=True) - - -def test_issubclass(): - def my_function(x: any, y: Union[type, tuple]) -> bool: - return issubclass(x, y) - - verifier = verifier_for(my_function) - verifier.verify(1, int, expected_error=TypeError) - verifier.verify(int, int, expected_result=True) - verifier.verify(bool, int, expected_result=True) - verifier.verify(float, int, expected_result=False) - verifier.verify(float, (int, float), expected_result=True) - - -def test_iter(): - def my_function(x: Iterable) -> Iterator: - return iter(x) - - def predicate(iterator): - for i in range(4): - assert next(iterator) == i + 1 - - try: - next(iterator) - return False - except StopIteration: - return True - - verifier = verifier_for(my_function) - verifier.verify_property([1, 2, 3, 4], predicate=predicate) - - -def test_len(): - def my_function(x: Sized) -> int: - return len(x) - - verifier = verifier_for(my_function) - verifier.verify([1, 2, 3], expected_result=3) - verifier.verify((4, 5), expected_result=2) - verifier.verify({6}, expected_result=1) - verifier.verify({ - 'a': 1, - 'b': 2, - 'c': 3 - }, expected_result=3) - - -def test_list(): - def my_function(x: Iterable) -> list: - return list(x) - - def my_function_no_args() -> list: - return list() - - verifier = verifier_for(my_function) - no_args_verifier = verifier_for(my_function_no_args) - - verifier.verify((1, 2, 3), expected_result=[1, 2, 3]) - no_args_verifier.verify(expected_result=[]) - - -def test_locals(): - def my_function(): - return locals() - - # Not using verifier since locals is not implemented in Java since it would involve reading frame variable slots - # to get the current value of local variables - java_function = jpyinterpreter.as_java(my_function) - with pytest.raises(ValueError) as excinfo: - java_function() - - assert 'builtin locals() is not supported when executed in Java bytecode' in str(excinfo.value) - - -def test_map(): - def my_function(function: Callable, iterable: Iterable) -> tuple: - return tuple(map(function, iterable)) - - def my_function_two_args(function: Callable, iterable1: Iterable, iterable2: Iterable) -> tuple: - return tuple(map(function, iterable1, iterable2)) - - verifier = verifier_for(my_function) - two_args_verifier = verifier_for(my_function_two_args) - - verifier.verify(lambda x: x + 1, [1, 2, 3], expected_result=(2, 3, 4)) - two_args_verifier.verify(lambda x, y: x + y, [1, 2, 3], [3, 4, 5], expected_result=(4, 6, 8)) - two_args_verifier.verify(lambda x, y: x + y, [1, 2], [3, 4, 5], expected_result=(4, 6)) - two_args_verifier.verify(lambda x, y: x + y, [1, 2, 3], [3, 4], expected_result=(4, 6)) - - -def test_min(): - def my_function(x: Iterable) -> any: - return min(x) - - def my_function_two_args(x: any, y: any) -> any: - return min(x, y) - - verifier = verifier_for(my_function) - two_args_verifier = verifier_for(my_function_two_args) - - verifier.verify([1, 2, 3], expected_result=1) - verifier.verify([3, 2, 1], expected_result=1) - two_args_verifier.verify(1, 2, expected_result=1) - two_args_verifier.verify(2, 1, expected_result=1) - - -def test_max(): - def my_function(x: Iterable) -> any: - return max(x) - - def my_function_two_args(x: any, y: any) -> any: - return max(x, y) - - verifier = verifier_for(my_function) - two_args_verifier = verifier_for(my_function_two_args) - - verifier.verify([1, 2, 3], expected_result=3) - verifier.verify([3, 2, 1], expected_result=3) - two_args_verifier.verify(1, 2, expected_result=2) - two_args_verifier.verify(2, 1, expected_result=2) - - -def test_next(): - def my_function(x: Iterable) -> any: - i = iter(x) - return next(i) - - verifier = verifier_for(my_function) - verifier.verify([1, 2, 3], expected_result=1) - - -def test_object(): - def my_function(x: any) -> bool: - # cannot really do anything with a plain object - return isinstance(x, object) - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=True) - verifier.verify('a', expected_result=True) - verifier.verify(int, expected_result=True) - - -def test_oct(): - def my_function(x: int) -> str: - return oct(x) - - verifier = verifier_for(my_function) - verifier.verify(15) - verifier.verify(-15) - - -def test_ord(): - def my_function(x: str) -> int: - return ord(x) - - verifier = verifier_for(my_function) - verifier.verify('a') - verifier.verify('\n') - - -def test_pow(): - def my_function(x: int, y: int) -> any: - return pow(x, y) - - def my_function_with_mod(x: int, y: int, z: int) -> any: - return pow(x, y, z) - - verifier = verifier_for(my_function) - with_mod_verifier = verifier_for(my_function_with_mod) - - verifier.verify(2, 3, expected_result=8) - verifier.verify(2, -3, expected_result=0.125) - - with_mod_verifier.verify(2, 3, 3, expected_result=2) - with_mod_verifier.verify(2, -1, 3, expected_result=2) - - -def test_range(): - def my_function(x: int) -> tuple: - return tuple(range(x)) - - def my_function_with_start(start: int, stop: int) -> tuple: - return tuple(range(start, stop)) - - def my_function_with_start_and_step(start: int, stop: int, step: int) -> tuple: - return tuple(range(start, stop, step)) - - verifier = verifier_for(my_function) - with_start_verifier = verifier_for(my_function_with_start) - with_start_and_step_verifier = verifier_for(my_function_with_start_and_step) - - verifier.verify(5, expected_result=(0, 1, 2, 3, 4)) - - with_start_verifier.verify(5, 10, expected_result=(5, 6, 7, 8, 9)) - - with_start_and_step_verifier.verify(5, 10, 2, expected_result=(5, 7, 9)) - with_start_and_step_verifier.verify(10, 5, -2, expected_result=(10, 8, 6)) - - -def test_repr(): - def my_function(x: any) -> str: - return repr(x) - - verifier = verifier_for(my_function) - - verifier.verify(10, expected_result='10') - verifier.verify('a\nstring\nwith\nnew lines', expected_result="'a\\nstring\\nwith\\nnew lines'") - verifier.verify([1, '2', 3], expected_result="[1, '2', 3]") - - -def test_reversed(): - def my_function(x: Reversible) -> tuple: - return tuple(reversed(x)) - - class TestClass: - def __reversed__(self): - yield from range(10, 1, -1) - - verifier = verifier_for(my_function) - - verifier.verify([1, 2, 3], expected_result=(3, 2, 1)) - verifier.verify((1, 2, 3), expected_result=(3, 2, 1)) - verifier.verify(TestClass(), expected_result=(10, 9, 8, 7, 6, 5, 4, 3, 2)) - - -def test_round(): - def my_function(x: any) -> int: - return round(x) - - def my_function_with_precision(x: any, y: int) -> any: - return round(x, y) - - verifier = verifier_for(my_function) - with_precision_verifier = verifier_for(my_function_with_precision) - - verifier.verify(15, expected_result=15) - verifier.verify(1.5, expected_result=2) - verifier.verify(1.2, expected_result=1) - verifier.verify(1.7, expected_result=2) - verifier.verify(2.5, expected_result=2) - - with_precision_verifier.verify(15, 0, expected_result=15) - with_precision_verifier.verify(15, 2, expected_result=15) - with_precision_verifier.verify(15, -1, expected_result=20) - with_precision_verifier.verify(25, -1, expected_result=20) - - with_precision_verifier.verify(1.076, 2, expected_result=1.08) - with_precision_verifier.verify(1.024, 2, expected_result=1.02) - with_precision_verifier.verify(1.045, 2, expected_result=1.04) - with_precision_verifier.verify(1.055, 2, expected_result=1.05) - - with_precision_verifier.verify(176.0, -1, expected_result=180.0) - with_precision_verifier.verify(124.0, -1, expected_result=120.0) - with_precision_verifier.verify(145.0, -1, expected_result=140.0) - with_precision_verifier.verify(155.0, -1, expected_result=160.0) - - -def test_set(): - def my_function() -> set: - return set() - - def my_function_with_arg(x: Iterable) -> set: - return set(x) - - verifier = verifier_for(my_function) - with_arg_verifier = verifier_for(my_function_with_arg) - - verifier.verify(expected_result=set()) - with_arg_verifier.verify([1, 2, 2, 3], expected_result={1, 2, 3}) - - -def test_setattr(): - def my_function(x: any, name: str, value: any) -> any: - setattr(x, name, value) - return getattr(x, name) - - class TestObject: - pass - - verifier = verifier_for(my_function) - - a = TestObject() - a.test = 'value 1' - - verifier.verify(a, 'test', 'value 2', expected_result='value 2') - - -def test_slice(): - def my_function(start: Union[int, SupportsIndex], stop: Union[int, SupportsIndex]): - return slice(start, stop) - - def my_function_with_step(start: Union[int, SupportsIndex], stop: Union[int, SupportsIndex], step: int): - return slice(start, stop, step) - - class MyClassWithIndex: - def __init__(self, index): - self.index = index - - def __index__(self): - return self.index - - def __eq__(self, other): - return self.index == other.index - - verifier = verifier_for(my_function) - with_step_verifier = verifier_for(my_function_with_step) - - verifier.verify(1, 5, expected_result=slice(1, 5)) - verifier.verify(1, MyClassWithIndex(3), expected_result=slice(1, MyClassWithIndex(3))) - with_step_verifier.verify(1, 5, 2, expected_result=slice(1, 5, 2)) - - -def test_sorted(): - def my_function(x: Sequence) -> Sequence: - return sorted(x) - - def my_function_with_key(x: Sequence, y: Callable[[any], any]) -> Sequence: - return sorted(x, key=y) - - def my_function_with_reverse(x: Sequence, y: bool) -> Sequence: - return sorted(x, reverse=y) - - def my_function_with_key_and_reverse(x: Sequence, y: Callable[[any], any], z: bool) -> Sequence: - return sorted(x, key=y, reverse=z) - - verifier = verifier_for(my_function) - with_key_verifier = verifier_for(my_function_with_key) - with_reverse_verifier = verifier_for(my_function_with_reverse) - with_key_and_reverse_verifier = verifier_for(my_function_with_key_and_reverse) - - verifier.verify([2, 1, 3, 4], expected_result=[1, 2, 3, 4]) - with_key_verifier.verify([2, 1, 3, 4], lambda x: -x, expected_result=[4, 3, 2, 1]) - with_reverse_verifier.verify([2, 1, 3, 4], True, expected_result=[4, 3, 2, 1]) - with_key_and_reverse_verifier.verify([2, 1, 3, 4], lambda x: -x, True, expected_result=[1, 2, 3, 4]) - - -def test_str(): - def my_function(x: any) -> str: - return str(x) - - class TestObject: - def __str__(self): - return 'A TestObject Instance' - - verifier = verifier_for(my_function) - verifier.verify(10, expected_result='10') - verifier.verify(1.0, expected_result='1.0') - verifier.verify('text', expected_result='text') - verifier.verify([1, '2', 3], expected_result="[1, '2', 3]") - verifier.verify(TestObject(), expected_result='A TestObject Instance') - - -def test_sum(): - def my_function(x: Iterable) -> any: - return sum(x) - - def my_function_with_start(x: Iterable, y: any) -> any: - return sum(x, y) - - verifier = verifier_for(my_function) - with_start_verifier = verifier_for(my_function_with_start) - - verifier.verify([], expected_result=0) - verifier.verify([1, 3, 5], expected_result=9) - verifier.verify([1.0, 3.0, 5.0], expected_result=9.0) - with_start_verifier.verify([], 1.5, expected_result=1.5) - with_start_verifier.verify([1, 3, 5], 1.5, expected_result=10.5) - with_start_verifier.verify([1.0, 3.0, 5.0], 1.5, expected_result=10.5) - - -def test_tuple(): - def my_function() -> tuple: - return tuple() - - def my_function_with_arg(x: Iterable) -> tuple: - return tuple(x) - - verifier = verifier_for(my_function) - with_arg_verifier = verifier_for(my_function_with_arg) - - verifier.verify(expected_result=tuple()) - with_arg_verifier.verify([1, 2, 2, 3], expected_result=(1, 2, 2, 3)) - - -def test_type(): - def my_function(x: any) -> type: - return type(x) - - class MyObject: - pass - - verifier = verifier_for(my_function) - - verifier.verify(10, expected_result=int) - verifier.verify('text', expected_result=str) - verifier.verify([1, 2], expected_result=list) - verifier.verify(MyObject(), expected_result=MyObject) - verifier.verify(MyObject, expected_result=type) - - -def test_zip(): - def my_function(iterables: Iterable) -> tuple: - return tuple(zip(*iterables)) - - def my_function_strict(iterables: Iterable, is_strict: bool) -> tuple: - return tuple(zip(*iterables, strict=is_strict)) - - verifier = verifier_for(my_function) - strict_verifier = verifier_for(my_function_strict) - - verifier.verify([], expected_result=tuple()) - verifier.verify(((1, 2, 3),), expected_result=((1,), (2,), (3,))) - verifier.verify(((1, 2, 3), (4, 5, 6)), expected_result=((1, 4), (2, 5), (3, 6))) - verifier.verify(((1, 2, 3), (4, 5)), expected_result=((1, 4), (2, 5))) - verifier.verify(((1, 2), (4, 5, 6)), expected_result=((1, 4), (2, 5))) - - if sys.version_info >= (3, 10): - strict_verifier.verify(((1, 2, 3),), True, expected_result=((1,), (2,), (3,))) - strict_verifier.verify(((1, 2, 3), (4, 5, 6)), True, expected_result=((1, 4), (2, 5), (3, 6))) - strict_verifier.verify(((1, 2, 3), (4, 5)), True, expected_error=ValueError) - strict_verifier.verify(((1, 2), (4, 5, 6)), True, expected_error=ValueError) - - -def test_builtin_exceptions(): - import builtins - - for key in dir(builtins): - value = getattr(builtins, key) - if isinstance(value, type) and issubclass(value, BaseException): - exception_class = value - - if issubclass(exception_class, OSError) or issubclass(exception_class, SyntaxError) or \ - issubclass(exception_class, UnicodeError) or exception_class.__name__.endswith('Group'): - # Exception groups are 3.11+, and take two arguments - # these errors take a different number of arguments, and thus the below code will not work - continue - - def my_function() -> exception_class: - return exception_class("my argument") - - verifier = verifier_for(my_function) - verifier.verify_property(predicate= - lambda error: isinstance(error, exception_class) and len(error.args) == 1 and - 'my argument' in error.args[0]) diff --git a/jpyinterpreter/tests/test_bytearray.py b/jpyinterpreter/tests/test_bytearray.py deleted file mode 100644 index c516c6a1..00000000 --- a/jpyinterpreter/tests/test_bytearray.py +++ /dev/null @@ -1,1353 +0,0 @@ -from typing import Union -from .conftest import verifier_for - - -######################################## -# Sequence methods -######################################## - -def test_membership(): - def membership(tested: bytearray, x: bytearray) -> bool: - return x in tested - - def not_membership(tested: bytearray, x: bytearray) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), expected_result=True) - not_membership_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), expected_result=False) - - membership_verifier.verify(bytearray(b'hello world'), bytearray(b'test'), expected_result=False) - not_membership_verifier.verify(bytearray(b'hello world'), bytearray(b'test'), expected_result=True) - - membership_verifier.verify(bytearray(b'hello world'), bytearray(b''), expected_result=True) - not_membership_verifier.verify(bytearray(b'hello world'), bytearray(b''), expected_result=False) - - -def test_concat(): - def concat(x: bytearray, y: bytearray) -> tuple: - out = x + y - return out, out is x, out is y - - concat_verifier = verifier_for(concat) - - concat_verifier.verify(bytearray(b'hello '), bytearray(b'world'), expected_result=(bytearray(b'hello world'), False, False)) - concat_verifier.verify(bytearray(b''), bytearray(b'hello world'), expected_result=(bytearray(b'hello world'), False, False)) - concat_verifier.verify(bytearray(b'hello world'), bytearray(b''), expected_result=(bytearray(b'hello world'), False, False)) - concat_verifier.verify(bytearray(b'world '), bytearray(b'hello'), expected_result=(bytearray(b'world hello'), False, False)) - - -def test_repeat(): - def left_repeat(x: bytearray, y: int) -> tuple: - out = x * y - return out, out is x, out is y - - def right_repeat(x: int, y: bytearray) -> tuple: - out = x * y - return out, out is x, out is y - - left_repeat_verifier = verifier_for(left_repeat) - right_repeat_verifier = verifier_for(right_repeat) - - left_repeat_verifier.verify(bytearray(b'hi'), 1, expected_result=(bytearray(b'hi'), False, False)) - left_repeat_verifier.verify(bytearray(b'abc'), 2, expected_result=(bytearray(b'abcabc'), False, False)) - left_repeat_verifier.verify(bytearray(b'a'), 4, expected_result=(bytearray(b'aaaa'), False, False)) - left_repeat_verifier.verify(bytearray(b'test'), 0, expected_result=(bytearray(b''), False, False)) - left_repeat_verifier.verify(bytearray(b'test'), -1, expected_result=(bytearray(b''), False, False)) - left_repeat_verifier.verify(bytearray(b'test'), -2, expected_result=(bytearray(b''), False, False)) - - right_repeat_verifier.verify(1, bytearray(b'hi'), expected_result=(bytearray(b'hi'), False, False)) - right_repeat_verifier.verify(2, bytearray(b'abc'), expected_result=(bytearray(b'abcabc'), False, False)) - right_repeat_verifier.verify(4, bytearray(b'a'), expected_result=(bytearray(b'aaaa'), False, False)) - right_repeat_verifier.verify(0, bytearray(b'test'), expected_result=(bytearray(b''), False, False)) - right_repeat_verifier.verify(-1, bytearray(b'test'), expected_result=(bytearray(b''), False, False)) - right_repeat_verifier.verify(-2, bytearray(b'test'), expected_result=(bytearray(b''), False, False)) - - -def test_get_item(): - def get_item(tested: bytearray, index: int) -> int: - return tested[index] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify(bytearray(b'abc'), 1, expected_result=ord(bytearray(b'b'))) - get_item_verifier.verify(bytearray(b'abc'), -1, expected_result=ord(bytearray(b'c'))) - get_item_verifier.verify(bytearray(b'abcd'), -1, expected_result=ord(bytearray(b'd'))) - get_item_verifier.verify(bytearray(b'abcd'), -2, expected_result=ord(bytearray(b'c'))) - get_item_verifier.verify(bytearray(b'abcd'), 0, expected_result=ord(bytearray(b'a'))) - get_item_verifier.verify(bytearray(b'abc'), 3, expected_error=IndexError) - get_item_verifier.verify(bytearray(b'abc'), -4, expected_error=IndexError) - - -def test_get_slice(): - def get_slice(tested: bytearray, start: Union[int, None], end: Union[int, None]) -> bytearray: - return tested[start:end] - - get_slice_verifier = verifier_for(get_slice) - - get_slice_verifier.verify(bytearray(b'abcde'), 1, 3, expected_result=bytearray(b'bc')) - get_slice_verifier.verify(bytearray(b'abcde'), -3, -1, expected_result=bytearray(b'cd')) - - get_slice_verifier.verify(bytearray(b'abcde'), 0, -2, expected_result=bytearray(b'abc')) - get_slice_verifier.verify(bytearray(b'abcde'), -3, 4, expected_result=bytearray(b'cd')) - - get_slice_verifier.verify(bytearray(b'abcde'), 3, 1, expected_result=bytearray(b'')) - get_slice_verifier.verify(bytearray(b'abcde'), -1, -3, expected_result=bytearray(b'')) - - get_slice_verifier.verify(bytearray(b'abcde'), 100, 1000, expected_result=bytearray(b'')) - get_slice_verifier.verify(bytearray(b'abcde'), 0, 1000, expected_result=bytearray(b'abcde')) - - get_slice_verifier.verify(bytearray(b'abcde'), 1, None, expected_result=bytearray(b'bcde')) - get_slice_verifier.verify(bytearray(b'abcde'), None, 2, expected_result=bytearray(b'ab')) - get_slice_verifier.verify(bytearray(b'abcde'), None, None, expected_result=bytearray(b'abcde')) - - -def test_get_slice_with_step(): - def get_slice_with_step(tested: bytearray, start: Union[int, None], end: Union[int, None], - step: Union[int, None]) -> bytearray: - return tested[start:end:step] - - get_slice_verifier = verifier_for(get_slice_with_step) - - get_slice_verifier.verify(bytearray(b'abcde'), 0, None, 2, expected_result=bytearray(b'ace')) - get_slice_verifier.verify(bytearray(b'abcde'), 1, None, 2, expected_result=bytearray(b'bd')) - get_slice_verifier.verify(bytearray(b'abcde'), 0, 5, 2, expected_result=bytearray(b'ace')) - get_slice_verifier.verify(bytearray(b'abcde'), 1, 5, 2, expected_result=bytearray(b'bd')) - get_slice_verifier.verify(bytearray(b'abcde'), 0, -1, 2, expected_result=bytearray(b'ac')) - get_slice_verifier.verify(bytearray(b'abcde'), 1, -1, 2, expected_result=bytearray(b'bd')) - - get_slice_verifier.verify(bytearray(b'abcde'), 4, None, -2, expected_result=bytearray(b'eca')) - get_slice_verifier.verify(bytearray(b'abcde'), 3, None, -2, expected_result=bytearray(b'db')) - get_slice_verifier.verify(bytearray(b'abcde'), -1, -6, -2, expected_result=bytearray(b'eca')) - get_slice_verifier.verify(bytearray(b'abcde'), -2, -6, -2, expected_result=bytearray(b'db')) - get_slice_verifier.verify(bytearray(b'abcde'), 4, 0, -2, expected_result=bytearray(b'ec')) - get_slice_verifier.verify(bytearray(b'abcde'), 3, 0, -2, expected_result=bytearray(b'db')) - - get_slice_verifier.verify(bytearray(b'abcde'), 0, None, None, expected_result=bytearray(b'abcde')) - get_slice_verifier.verify(bytearray(b'abcde'), 0, 3, None, expected_result=bytearray(b'abc')) - - get_slice_verifier.verify(bytearray(b'abcde'), 3, 1, -1, expected_result=bytearray(b'dc')) - get_slice_verifier.verify(bytearray(b'abcde'), -1, -3, -1, expected_result=bytearray(b'ed')) - get_slice_verifier.verify(bytearray(b'abcde'), 3, 1, 1, expected_result=bytearray(b'')) - get_slice_verifier.verify(bytearray(b'abcde'), -1, -3, 1, expected_result=bytearray(b'')) - - -def test_len(): - def length(tested: bytearray) -> int: - return len(tested) - - len_verifier = verifier_for(length) - - len_verifier.verify(bytearray(b''), expected_result=0) - len_verifier.verify(bytearray(b'a'), expected_result=1) - len_verifier.verify(bytearray(b'ab'), expected_result=2) - len_verifier.verify(bytearray(b'cba'), expected_result=3) - - -def test_index(): - def index(tested: bytearray, item: bytearray) -> int: - return tested.index(item) - - def index_start(tested: bytearray, item: bytearray, start: int) -> int: - return tested.index(item, start) - - def index_start_end(tested: bytearray, item: bytearray, start: int, end: int) -> int: - return tested.index(item, start, end) - - index_verifier = verifier_for(index) - index_start_verifier = verifier_for(index_start) - index_start_end_verifier = verifier_for(index_start_end) - - index_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), expected_result=0) - index_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), expected_result=1) - index_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), expected_error=ValueError) - - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, expected_result=3) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 5, expected_error=ValueError) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, expected_result=1) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, expected_result=2) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, expected_error=ValueError) - - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -3, expected_result=3) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, expected_result=4) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, expected_result=5) - index_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, expected_error=ValueError) - - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, 2, expected_error=ValueError) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, 2, expected_result=1) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, 2, expected_error=ValueError) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, 2, expected_error=ValueError) - - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -2, -1, expected_error=ValueError) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, -1, expected_result=4) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, -1, expected_error=ValueError) - index_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, -1, expected_error=ValueError) - - -def test_count(): - def count(tested: bytearray, item: bytearray) -> int: - return tested.count(item) - - count_verifier = verifier_for(count) - - count_verifier.verify(bytearray(b'abc'), bytearray(b'a'), expected_result=1) - count_verifier.verify(bytearray(b'abc'), bytearray(b'b'), expected_result=1) - count_verifier.verify(bytearray(b'abc'), bytearray(b'c'), expected_result=1) - count_verifier.verify(bytearray(b'abc'), bytearray(b'd'), expected_result=0) - - count_verifier.verify(bytearray(b'abca'), bytearray(b'a'), expected_result=2) - count_verifier.verify(bytearray(b'aaca'), bytearray(b'a'), expected_result=3) - count_verifier.verify(bytearray(b''), bytearray(b'a'), expected_result=0) - - -######################################## -# Mutable Sequence operations -######################################## - - -def test_set_item(): - def set_item(tested: bytearray, index: int, value: int) -> bytearray: - tested[index] = value - return tested - - set_item_verifier = verifier_for(set_item) - set_item_verifier.verify(bytearray(b'abcde'), 0, ord('g'), expected_result=bytearray(b'gbcde')) - set_item_verifier.verify(bytearray(b'abcde'), 2, ord('C'), expected_result=bytearray(b'abCde')) - set_item_verifier.verify(bytearray(b'abcde'), -1, ord('F'), expected_result=bytearray(b'abcdF')) - set_item_verifier.verify(bytearray(b'abcde'), -10, ord('a'), expected_error=IndexError) - set_item_verifier.verify(bytearray(b'abcde'), 10, ord('a'), expected_error=IndexError) - -def test_set_slice(): - def set_slice(tested: bytearray, start: Union[int, None], stop: Union[int, None], value: bytes) -> bytearray: - tested[start:stop] = value - return tested - - set_slice_verifier = verifier_for(set_slice) - set_slice_verifier.verify(bytearray(b'abcde'), 1, 3, b'', expected_result=bytearray(b'ade')) - set_slice_verifier.verify(bytearray(b'abcde'), 2, 2, b'H', expected_result=bytearray(b'abHcde')) - set_slice_verifier.verify(bytearray(b'abcde'), 1, 3, b'H', expected_result=bytearray(b'aHde')) - set_slice_verifier.verify(bytearray(b'abcde'), 1, 3, b'HI', expected_result=bytearray(b'aHIde')) - set_slice_verifier.verify(bytearray(b'abcde'), 1, 3, b'HIJ', expected_result=bytearray(b'aHIJde')) - - set_slice_verifier.verify(bytearray(b'abcde'), -4, -2, b'HI', expected_result=bytearray(b'aHIde')) - set_slice_verifier.verify(bytearray(b'abcde'), 1, -2, b'HI', expected_result=bytearray(b'aHIde')) - - set_slice_verifier.verify(bytearray(b'abcde'), 5, 5, b'F', expected_result=bytearray(b'abcdeF')) - - set_slice_verifier.verify(bytearray(b'abcde'), 1, None, b'', expected_result=bytearray(b'a')) - set_slice_verifier.verify(bytearray(b'abcde'), 1, None, b'HI', expected_result=bytearray(b'aHI')) - - -def test_delete_slice(): - def delete_slice(tested: bytearray, start: Union[int, None], stop: Union[int, None]) -> bytearray: - del tested[start:stop] - return tested - - delete_slice_verifier = verifier_for(delete_slice) - delete_slice_verifier.verify(bytearray(b'abcde'), 1, 3, expected_result=bytearray(b'ade')) - delete_slice_verifier.verify(bytearray(b'abcde'), 3, 5, expected_result=bytearray(b'abc')) - delete_slice_verifier.verify(bytearray(b'abcde'), 1, None, expected_result=bytearray(b'a')) - delete_slice_verifier.verify(bytearray(b'abcde'), None, 3, expected_result=bytearray(b'de')) - delete_slice_verifier.verify(bytearray(b'abcde'), None, None, expected_result=bytearray(b'')) - - -def test_set_slice_with_step(): - def set_slice_with_step(tested: bytearray, start: Union[int, None], stop: Union[int, None], step: Union[int, None], - value: bytes) -> bytearray: - tested[start:stop:step] = value - return tested - - set_slice_with_step_verifier = verifier_for(set_slice_with_step) - - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 3, 0, -1, b'BCD', expected_result=bytearray(b'aDCBe')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 1, 4, 2, b'BD', expected_result=bytearray(b'aBcDe')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 1, -1, 2, b'BD', expected_result=bytearray(b'aBcDe')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 0, None, 2, b'ACE', - expected_result=bytearray(b'AbCdE')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), None, 4, 2, b'AC', - expected_result=bytearray(b'AbCde')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), None, None, 2, b'ACE', - expected_result=bytearray(b'AbCdE')) - - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 3, 0, -1, b'', expected_result=bytearray(b'ae')) - set_slice_with_step_verifier.verify(bytearray(b'abcde'), 3, 0, -1, b'BCDE', expected_error=ValueError) - - -def test_delete_slice_with_step(): - def delete_slice_with_step(tested: bytearray, start: Union[int, None], stop: Union[int, None], - step: Union[int, None]) -> bytearray: - del tested[start:stop:step] - return tested - - delete_slice_with_step_verifier = verifier_for(delete_slice_with_step) - - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), 3, 0, -1, expected_result=bytearray(b'ae')) - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), 1, 4, 2, expected_result=bytearray(b'ace')) - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), 1, -1, 2, expected_result=bytearray(b'ace')) - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), 0, None, 2, - expected_result=bytearray(b'bd')) - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), None, 4, 2, - expected_result=bytearray(b'bde')) - delete_slice_with_step_verifier.verify(bytearray(b'abcde'), None, None, 2, - expected_result=bytearray(b'bd')) - - -def test_append(): - def append(tested: bytearray, item: int) -> bytearray: - tested.append(item) - return tested - - append_verifier = verifier_for(append) - - append_verifier.verify(bytearray(b''), ord('a'), expected_result=bytearray(b'a')) - append_verifier.verify(bytearray(b'a'), ord('b'), expected_result=bytearray(b'ab')) - append_verifier.verify(bytearray(b'ab'), ord('c'), expected_result=bytearray(b'abc')) - append_verifier.verify(bytearray(b'abc'), ord('c'), expected_result=bytearray(b'abcc')) - - -def test_clear(): - def clear(tested: bytearray) -> bytearray: - tested.clear() - return tested - - clear_verifier = verifier_for(clear) - - clear_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - clear_verifier.verify(bytearray(b'a'), expected_result=bytearray(b'')) - clear_verifier.verify(bytearray(b'ab'), expected_result=bytearray(b'')) - clear_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'')) - - -def test_copy(): - def copy(tested: bytearray) -> tuple: - out = tested.copy() - return out, out is tested - - copy_verifier = verifier_for(copy) - - copy_verifier.verify(bytearray(b''), expected_result=(bytearray(b''), False)) - copy_verifier.verify(bytearray(b'a'), expected_result=(bytearray(b'a'), False)) - copy_verifier.verify(bytearray(b'ab'), expected_result=(bytearray(b'ab'), False)) - copy_verifier.verify(bytearray(b'abc'), expected_result=(bytearray(b'abc'), False)) - - -def test_extend(): - def extend(tested: bytearray, item: bytearray) -> bytearray: - tested.extend(item) - return tested - - extend_verifier = verifier_for(extend) - - extend_verifier.verify(bytearray(b''), bytearray(b'a'), expected_result=bytearray(b'a')) - extend_verifier.verify(bytearray(b'a'), bytearray(b'b'), expected_result=bytearray(b'ab')) - extend_verifier.verify(bytearray(b'ab'), bytearray(b'c'), expected_result=bytearray(b'abc')) - extend_verifier.verify(bytearray(b'abc'), bytearray(b''), expected_result=bytearray(b'abc')) - extend_verifier.verify(bytearray(b'abc'), bytearray(b'de'), expected_result=bytearray(b'abcde')) - - -def test_inplace_add(): - def extend(tested: bytearray, item: bytearray) -> bytearray: - tested += item - return tested - - extend_verifier = verifier_for(extend) - - extend_verifier.verify(bytearray(b''), bytearray(b'a'), expected_result=bytearray(b'a')) - extend_verifier.verify(bytearray(b'a'), bytearray(b'b'), expected_result=bytearray(b'ab')) - extend_verifier.verify(bytearray(b'ab'), bytearray(b'c'), expected_result=bytearray(b'abc')) - extend_verifier.verify(bytearray(b'abc'), bytearray(b''), expected_result=bytearray(b'abc')) - extend_verifier.verify(bytearray(b'abc'), bytearray(b'de'), expected_result=bytearray(b'abcde')) - - -def test_inplace_multiply(): - def multiply(tested: bytearray, item: int) -> bytearray: - tested *= item - return tested - - multiply_verifier = verifier_for(multiply) - - multiply_verifier.verify(bytearray(b'abc'), 1, expected_result=bytearray(b'abc')) - multiply_verifier.verify(bytearray(b'ab'), 2, expected_result=bytearray(b'abab')) - multiply_verifier.verify(bytearray(b'ab'), 3, expected_result=bytearray(b'ababab')) - multiply_verifier.verify(bytearray(b'abc'), 0, expected_result=bytearray(b'')) - multiply_verifier.verify(bytearray(b'abc'), -1, expected_result=bytearray(b'')) - - - -def test_insert(): - def insert(tested: bytearray, index: int, item: int) -> bytearray: - tested.insert(index, item) - return tested - - insert_verifier = verifier_for(insert) - - insert_verifier.verify(bytearray(b''), 0, ord('a'), expected_result=bytearray(b'a')) - insert_verifier.verify(bytearray(b'a'), 0, ord('b'), expected_result=bytearray(b'ba')) - insert_verifier.verify(bytearray(b'a'), 1, ord('b'), expected_result=bytearray(b'ab')) - insert_verifier.verify(bytearray(b'ab'), 0, ord('c'), expected_result=bytearray(b'cab')) - insert_verifier.verify(bytearray(b'ab'), 1, ord('c'), expected_result=bytearray(b'acb')) - insert_verifier.verify(bytearray(b'ab'), 2, ord('c'), expected_result=bytearray(b'abc')) - insert_verifier.verify(bytearray(b'abc'), -1, ord('d'), expected_result=bytearray(b'abdc')) - insert_verifier.verify(bytearray(b'abc'), -2, ord('d'), expected_result=bytearray(b'adbc')) - insert_verifier.verify(bytearray(b'abc'), 3, ord('d'), expected_result=bytearray(b'abcd')) - insert_verifier.verify(bytearray(b'abc'), 4, ord('d'), expected_result=bytearray(b'abcd')) - insert_verifier.verify(bytearray(b'abc'), -4, ord('d'), expected_result=bytearray(b'dabc')) - insert_verifier.verify(bytearray(b'abc'), -5, ord('d'), expected_result=bytearray(b'dabc')) - - -def test_pop(): - def pop(tested: bytearray) -> tuple: - item = tested.pop() - return item, tested - - pop_verifier = verifier_for(pop) - - pop_verifier.verify(bytearray(b'abc'), expected_result=(ord('c'), bytearray(b'ab'))) - pop_verifier.verify(bytearray(b'ab'), expected_result=(ord('b'), bytearray(b'a'))) - pop_verifier.verify(bytearray(b'a'), expected_result=(ord('a'), bytearray(b''))) - - pop_verifier.verify(bytearray(b'abe'), expected_result=(ord('e'), bytearray(b'ab'))) - - pop_verifier.verify(bytearray(b''), expected_error=IndexError) - - -def test_pop_at_index(): - def pop_at_index(tested: bytearray, index: int) -> tuple: - item = tested.pop(index) - return item, tested - - pop_at_index_verifier = verifier_for(pop_at_index) - - pop_at_index_verifier.verify(bytearray(b'abc'), -1, expected_result=(ord('c'), bytearray(b'ab'))) - pop_at_index_verifier.verify(bytearray(b'ab'), -1, expected_result=(ord('b'), bytearray(b'a'))) - pop_at_index_verifier.verify(bytearray(b'a'), -1, expected_result=(ord('a'), bytearray(b''))) - - pop_at_index_verifier.verify(bytearray(b'abc'), 1, expected_result=(ord('b'), bytearray(b'ac'))) - pop_at_index_verifier.verify(bytearray(b'abc'), 0, expected_result=(ord('a'), bytearray(b'bc'))) - pop_at_index_verifier.verify(bytearray(b'abc'), 2, expected_result=(ord('c'), bytearray(b'ab'))) - pop_at_index_verifier.verify(bytearray(b'abc'), -2, expected_result=(ord('b'), bytearray(b'ac'))) - - pop_at_index_verifier.verify(bytearray(b'abc'), -4, expected_error=IndexError) - pop_at_index_verifier.verify(bytearray(b'abc'), 4, expected_error=IndexError) - pop_at_index_verifier.verify(bytearray(b''), 0, expected_error=IndexError) - - -def test_remove(): - def remove(tested: bytearray, item: int) -> bytearray: - tested.remove(item) - return tested - - remove_verifier = verifier_for(remove) - - remove_verifier.verify(bytearray(b'abc'), ord('a'), expected_result=bytearray(b'bc')) - remove_verifier.verify(bytearray(b'abc'), ord('b'), expected_result=bytearray(b'ac')) - remove_verifier.verify(bytearray(b'abc'), ord('c'), expected_result=bytearray(b'ab')) - - remove_verifier.verify(bytearray(b'abe'), ord('b'), expected_result=bytearray(b'ae')) - - remove_verifier.verify(bytearray(b'abc'), ord('d'), expected_error=ValueError) - remove_verifier.verify(bytearray(b''), ord('a'), expected_error=ValueError) - - -def test_reverse(): - def reverse(tested: bytearray) -> bytearray: - tested.reverse() - return tested - - reverse_verifier = verifier_for(reverse) - - reverse_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'cba')) - reverse_verifier.verify(bytearray(b'cba'), expected_result=bytearray(b'abc')) - reverse_verifier.verify(bytearray(b'ab'), expected_result=bytearray(b'ba')) - reverse_verifier.verify(bytearray(b'ba'), expected_result=bytearray(b'ab')) - reverse_verifier.verify(bytearray(b'a'), expected_result=bytearray(b'a')) - reverse_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - - -######################################## -# Bytearray operations -######################################## -def test_interpolation(): - def interpolation(tested: bytearray, values: object) -> bytearray: # noqa - # IDE thinks bytearray __mod__ returns bytes, but in reality it returns bytearray - return tested % values # noqa - - interpolation_verifier = verifier_for(interpolation) - - interpolation_verifier.verify(bytearray(b'%d'), 100, expected_result=bytearray(b'100')) - interpolation_verifier.verify(bytearray(b'%d'), 0b1111, expected_result=bytearray(b'15')) - interpolation_verifier.verify(bytearray(b'%s'), bytearray(b'foo'), expected_result=bytearray(b'foo')) - interpolation_verifier.verify(bytearray(b'%s %s'), (bytearray(b'foo'), bytearray(b'bar')), - expected_result=bytearray(b'foo bar')) - interpolation_verifier.verify(bytearray(b'%(foo)s'), {b'foo': bytearray(b'10'), b'bar': bytearray(b'20')}, - expected_result=bytearray(b'10')) - - interpolation_verifier.verify(bytearray(b'%d'), 101, expected_result=bytearray(b'101')) - interpolation_verifier.verify(bytearray(b'%i'), 101, expected_result=bytearray(b'101')) - - interpolation_verifier.verify(bytearray(b'%o'), 27, expected_result=bytearray(b'33')) - interpolation_verifier.verify(bytearray(b'%#o'), 27, expected_result=bytearray(b'0o33')) - - interpolation_verifier.verify(bytearray(b'%x'), 27, expected_result=bytearray(b'1b')) - interpolation_verifier.verify(bytearray(b'%X'), 27, expected_result=bytearray(b'1B')) - interpolation_verifier.verify(bytearray(b'%#x'), 27, expected_result=bytearray(b'0x1b')) - interpolation_verifier.verify(bytearray(b'%#X'), 27, expected_result=bytearray(b'0X1B')) - - interpolation_verifier.verify(bytearray(b'%03d'), 1, expected_result=bytearray(b'001')) - interpolation_verifier.verify(bytearray(b'%-5d'), 1, expected_result=bytearray(b'1 ')) - interpolation_verifier.verify(bytearray(b'%0-5d'), 1, expected_result=bytearray(b'1 ')) - - interpolation_verifier.verify(bytearray(b'%d'), 1, expected_result=bytearray(b'1')) - interpolation_verifier.verify(bytearray(b'%d'), -1, expected_result=bytearray(b'-1')) - interpolation_verifier.verify(bytearray(b'% d'), 1, expected_result=bytearray(b' 1')) - interpolation_verifier.verify(bytearray(b'% d'), -1, expected_result=bytearray(b'-1')) - interpolation_verifier.verify(bytearray(b'%+d'), 1, expected_result=bytearray(b'+1')) - interpolation_verifier.verify(bytearray(b'%+d'), -1, expected_result=bytearray(b'-1')) - - interpolation_verifier.verify(bytearray(b'%f'), 3.14, expected_result=bytearray(b'3.140000')) - interpolation_verifier.verify(bytearray(b'%F'), 3.14, expected_result=bytearray(b'3.140000')) - interpolation_verifier.verify(bytearray(b'%.1f'), 3.14, expected_result=bytearray(b'3.1')) - interpolation_verifier.verify(bytearray(b'%.2f'), 3.14, expected_result=bytearray(b'3.14')) - interpolation_verifier.verify(bytearray(b'%.3f'), 3.14, expected_result=bytearray(b'3.140')) - - interpolation_verifier.verify(bytearray(b'%g'), 1234567890, expected_result=bytearray(b'1.23457e+09')) - interpolation_verifier.verify(bytearray(b'%G'), 1234567890, expected_result=bytearray(b'1.23457E+09')) - interpolation_verifier.verify(bytearray(b'%e'), 1234567890, expected_result=bytearray(b'1.234568e+09')) - interpolation_verifier.verify(bytearray(b'%E'), 1234567890, expected_result=bytearray(b'1.234568E+09')) - - interpolation_verifier.verify(bytearray(b'ABC %c'), 10, expected_result=bytearray(b'ABC \n')) - interpolation_verifier.verify(bytearray(b'ABC %c'), 67, expected_result=bytearray(b'ABC C')) - interpolation_verifier.verify(bytearray(b'ABC %c'), 68, expected_result=bytearray(b'ABC D')) - interpolation_verifier.verify(bytearray(b'ABC %c'), bytearray(b'D'), expected_result=bytearray(b'ABC D')) - interpolation_verifier.verify(bytearray(b'ABC %s'), bytearray(b'test'), expected_result=bytearray(b'ABC test')) - interpolation_verifier.verify(bytearray(b'ABC %r'), bytearray(b'test'), - expected_result=bytearray(b'ABC bytearray(b\'test\')')) - - interpolation_verifier.verify(bytearray(b'Give it %d%%!'), 100, expected_result=bytearray(b'Give it 100%!')) - interpolation_verifier.verify(bytearray(b'Give it %(all-you-got)d%%!'), {b'all-you-got': 100}, - expected_result=bytearray(b'Give it 100%!')) - - -######################################## -# Bytearray methods -######################################## - - -def test_capitalize(): - def capitalize(tested: bytearray) -> bytearray: - return tested.capitalize() - - capitalize_verifier = verifier_for(capitalize) - - capitalize_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - capitalize_verifier.verify(bytearray(b'test'), expected_result=bytearray(b'Test')) - capitalize_verifier.verify(bytearray(b'TEST'), expected_result=bytearray(b'Test')) - capitalize_verifier.verify(bytearray(b'hello world'), expected_result=bytearray(b'Hello world')) - capitalize_verifier.verify(bytearray(b'Hello World'), expected_result=bytearray(b'Hello world')) - capitalize_verifier.verify(bytearray(b'HELLO WORLD'), expected_result=bytearray(b'Hello world')) - capitalize_verifier.verify(bytearray('π'.encode()), expected_result=bytearray('π'.encode())) - - -def test_center(): - def center(tested: bytearray, width: int) -> bytearray: - return tested.center(width) - - def center_with_fill(tested: bytearray, width: int, fill: bytearray) -> bytearray: - return tested.center(width, fill) - - center_verifier = verifier_for(center) - center_with_fill_verifier = verifier_for(center_with_fill) - - center_verifier.verify(bytearray(b'test'), 10, expected_result=bytearray(b' test ')) - center_verifier.verify(bytearray(b'test'), 9, expected_result=bytearray(b' test ')) - center_verifier.verify(bytearray(b'test'), 4, expected_result=bytearray(b'test')) - center_verifier.verify(bytearray(b'test'), 2, expected_result=bytearray(b'test')) - - center_with_fill_verifier.verify(bytearray(b'test'), 10, bytearray(b'#'), expected_result=bytearray(b'###test###')) - center_with_fill_verifier.verify(bytearray(b'test'), 9, bytearray(b'#'), expected_result=bytearray(b'###test##')) - center_with_fill_verifier.verify(bytearray(b'test'), 4, bytearray(b'#'), expected_result=bytearray(b'test')) - center_with_fill_verifier.verify(bytearray(b'test'), 2, bytearray(b'#'), expected_result=bytearray(b'test')) - - -def test_count_byte(): - def count(tested: bytearray, item: int) -> int: - return tested.count(item) - - def count_start(tested: bytearray, item: int, start: int) -> int: - return tested.count(item, start) - - def count_start_end(tested: bytearray, item: int, start: int, end: int) -> int: - return tested.count(item, start, end) - - count_verifier = verifier_for(count) - count_from_start_verifier = verifier_for(count_start) - count_between_verifier = verifier_for(count_start_end) - - count_verifier.verify(bytearray(b'abc'), ord(bytearray(b'a')), expected_result=1) - count_verifier.verify(bytearray(b'abc'), ord(bytearray(b'b')), expected_result=1) - count_verifier.verify(bytearray(b'abc'), ord(bytearray(b'c')), expected_result=1) - count_verifier.verify(bytearray(b'abc'), ord(bytearray(b'd')), expected_result=0) - - count_verifier.verify(bytearray(b'abca'), ord(bytearray(b'a')), expected_result=2) - count_verifier.verify(bytearray(b'aaca'), ord(bytearray(b'a')), expected_result=3) - count_verifier.verify(bytearray(b''), ord(bytearray(b'a')), expected_result=0) - - count_from_start_verifier.verify(bytearray(b'abc'), ord(bytearray(b'a')), 1, expected_result=0) - count_from_start_verifier.verify(bytearray(b'abc'), ord(bytearray(b'b')), 1, expected_result=1) - count_from_start_verifier.verify(bytearray(b'abc'), ord(bytearray(b'c')), 1, expected_result=1) - count_from_start_verifier.verify(bytearray(b'abc'), ord(bytearray(b'd')), 1, expected_result=0) - - count_from_start_verifier.verify(bytearray(b'abca'), ord(bytearray(b'a')), 1, expected_result=1) - count_from_start_verifier.verify(bytearray(b'aaca'), ord(bytearray(b'a')), 1, expected_result=2) - count_from_start_verifier.verify(bytearray(b''), ord(bytearray(b'a')), 1, expected_result=0) - - count_between_verifier.verify(bytearray(b'abc'), ord(bytearray(b'a')), 1, 2, expected_result=0) - count_between_verifier.verify(bytearray(b'abc'), ord(bytearray(b'b')), 1, 2, expected_result=1) - count_between_verifier.verify(bytearray(b'abc'), ord(bytearray(b'c')), 1, 2, expected_result=0) - count_between_verifier.verify(bytearray(b'abc'), ord(bytearray(b'd')), 1, 2, expected_result=0) - - count_between_verifier.verify(bytearray(b'abca'), ord(bytearray(b'a')), 1, 2, expected_result=0) - count_between_verifier.verify(bytearray(b'abca'), ord(bytearray(b'a')), 1, 4, expected_result=1) - count_between_verifier.verify(bytearray(b'abca'), ord(bytearray(b'a')), 0, 2, expected_result=1) - count_between_verifier.verify(bytearray(b'aaca'), ord(bytearray(b'a')), 1, 2, expected_result=1) - count_between_verifier.verify(bytearray(b''), ord(bytearray(b'a')), 1, 2, expected_result=0) - - -def test_endswith(): - def endswith(tested: bytearray, suffix: bytearray) -> bool: - return tested.endswith(suffix) - - def endswith_start(tested: bytearray, suffix: bytearray, start: int) -> bool: - return tested.endswith(suffix, start) - - def endswith_between(tested: bytearray, suffix: bytearray, start: int, end: int) -> bool: - return tested.endswith(suffix, start, end) - - endswith_verifier = verifier_for(endswith) - endswith_start_verifier = verifier_for(endswith_start) - endswith_between_verifier = verifier_for(endswith_between) - - endswith_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), expected_result=True) - endswith_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), expected_result=False) - endswith_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), expected_result=False) - endswith_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), expected_result=True) - - endswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 6, expected_result=True) - endswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), 6, expected_result=False) - endswith_start_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), 6, expected_result=False) - endswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), 6, expected_result=False) - - endswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 6, 11, expected_result=True) - endswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 7, 11, expected_result=False) - endswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), 0, 5, expected_result=True) - endswith_between_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), 0, 5, expected_result=False) - endswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), 5, 11, expected_result=False) - - -def test_expandtabs(): - def expandtabs(tested: bytearray) -> bytearray: - return tested.expandtabs() - - def expandtabs_with_tabsize(tested: bytearray, tabsize: int) -> bytearray: - return tested.expandtabs(tabsize) - - expandtabs_verifier = verifier_for(expandtabs) - expandtabs_with_tabsize_verifier = verifier_for(expandtabs_with_tabsize) - - expandtabs_verifier.verify(bytearray(b'01\t012\t0123\t01234'), expected_result=bytearray(b'01 012 0123 01234')) - expandtabs_with_tabsize_verifier.verify(bytearray(b'01\t012\t0123\t01234'), 8, expected_result=bytearray(b'01 012 0123 01234')) - expandtabs_with_tabsize_verifier.verify(bytearray(b'01\t012\t0123\t01234'), 4, expected_result=bytearray(b'01 012 0123 01234')) - - -def test_find(): - def find(tested: bytearray, item: bytearray) -> int: - return tested.find(item) - - def find_start_verifier(tested: bytearray, item: bytearray, start: int) -> int: - return tested.find(item, start) - - def find_start_end_verifier(tested: bytearray, item: bytearray, start: int, end: int) -> int: - return tested.find(item, start, end) - - find_verifier = verifier_for(find) - find_start_verifier = verifier_for(find_start_verifier) - find_start_end_verifier = verifier_for(find_start_end_verifier) - - find_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), expected_result=0) - find_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), expected_result=1) - find_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), expected_result=-1) - - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, expected_result=3) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 5, expected_result=-1) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, expected_result=1) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, expected_result=2) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, expected_result=-1) - - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -3, expected_result=3) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, expected_result=4) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, expected_result=5) - find_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, expected_result=-1) - - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, 2, expected_result=-1) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, 2, expected_result=1) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, 2, expected_result=-1) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, 2, expected_result=-1) - - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -2, -1, expected_result=-1) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, -1, expected_result=4) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, -1, expected_result=-1) - find_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, -1, expected_result=-1) - - -def test_isalnum(): - def isalnum(tested: bytearray) -> bool: - return tested.isalnum() - - isalnum_verifier = verifier_for(isalnum) - - isalnum_verifier.verify(bytearray(b''), expected_result=False) - isalnum_verifier.verify(bytearray(b'abc'), expected_result=True) - isalnum_verifier.verify(bytearray(b'ABC'), expected_result=True) - isalnum_verifier.verify(bytearray(b'123'), expected_result=True) - isalnum_verifier.verify(bytearray(b'ABC123'), expected_result=True) - isalnum_verifier.verify(bytearray(b'+'), expected_result=False) - isalnum_verifier.verify(bytearray(b'[]'), expected_result=False) - isalnum_verifier.verify(bytearray(b'-'), expected_result=False) - isalnum_verifier.verify(bytearray(b'%'), expected_result=False) - isalnum_verifier.verify(bytearray(b'\n'), expected_result=False) - isalnum_verifier.verify(bytearray(b'\t'), expected_result=False) - isalnum_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_isalpha(): - def isalpha(tested: bytearray) -> bool: - return tested.isalpha() - - isalpha_verifier = verifier_for(isalpha) - - isalpha_verifier.verify(bytearray(b''), expected_result=False) - isalpha_verifier.verify(bytearray(b'abc'), expected_result=True) - isalpha_verifier.verify(bytearray(b'ABC'), expected_result=True) - isalpha_verifier.verify(bytearray(b'123'), expected_result=False) - isalpha_verifier.verify(bytearray(b'ABC123'), expected_result=False) - isalpha_verifier.verify(bytearray(b'+'), expected_result=False) - isalpha_verifier.verify(bytearray(b'[]'), expected_result=False) - isalpha_verifier.verify(bytearray(b'-'), expected_result=False) - isalpha_verifier.verify(bytearray(b'%'), expected_result=False) - isalpha_verifier.verify(bytearray(b'\n'), expected_result=False) - isalpha_verifier.verify(bytearray(b'\t'), expected_result=False) - isalpha_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_isascii(): - def isascii(tested: bytearray) -> bool: - return tested.isascii() - - isascii_verifier = verifier_for(isascii) - - isascii_verifier.verify(bytearray(b''), expected_result=True) - isascii_verifier.verify(bytearray(b'abc'), expected_result=True) - isascii_verifier.verify(bytearray(b'ABC'), expected_result=True) - isascii_verifier.verify(bytearray(b'123'), expected_result=True) - isascii_verifier.verify(bytearray(b'ABC123'), expected_result=True) - isascii_verifier.verify(bytearray(b'+'), expected_result=True) - isascii_verifier.verify(bytearray(b'[]'), expected_result=True) - isascii_verifier.verify(bytearray(b'-'), expected_result=True) - isascii_verifier.verify(bytearray(b'%'), expected_result=True) - isascii_verifier.verify(bytearray(b'\n'), expected_result=True) - isascii_verifier.verify(bytearray(b'\t'), expected_result=True) - isascii_verifier.verify(bytearray(b' '), expected_result=True) - - -def test_isdigit(): - def isdigit(tested: bytearray) -> bool: - return tested.isdigit() - - isdigit_verifier = verifier_for(isdigit) - - isdigit_verifier.verify(bytearray(b''), expected_result=False) - isdigit_verifier.verify(bytearray(b'abc'), expected_result=False) - isdigit_verifier.verify(bytearray(b'ABC'), expected_result=False) - isdigit_verifier.verify(bytearray(b'123'), expected_result=True) - isdigit_verifier.verify(bytearray(b'ABC123'), expected_result=False) - isdigit_verifier.verify(bytearray(b'+'), expected_result=False) - isdigit_verifier.verify(bytearray(b'[]'), expected_result=False) - isdigit_verifier.verify(bytearray(b'-'), expected_result=False) - isdigit_verifier.verify(bytearray(b'%'), expected_result=False) - isdigit_verifier.verify(bytearray(b'\n'), expected_result=False) - isdigit_verifier.verify(bytearray(b'\t'), expected_result=False) - isdigit_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_islower(): - def islower(tested: bytearray) -> bool: - return tested.islower() - - islower_verifier = verifier_for(islower) - - islower_verifier.verify(bytearray(b''), expected_result=False) - islower_verifier.verify(bytearray(b'abc'), expected_result=True) - islower_verifier.verify(bytearray(b'ABC'), expected_result=False) - islower_verifier.verify(bytearray(b'123'), expected_result=False) - islower_verifier.verify(bytearray(b'ABC123'), expected_result=False) - islower_verifier.verify(bytearray(b'+'), expected_result=False) - islower_verifier.verify(bytearray(b'[]'), expected_result=False) - islower_verifier.verify(bytearray(b'-'), expected_result=False) - islower_verifier.verify(bytearray(b'%'), expected_result=False) - islower_verifier.verify(bytearray(b'\n'), expected_result=False) - islower_verifier.verify(bytearray(b'\t'), expected_result=False) - islower_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_isspace(): - def isspace(tested: bytearray) -> bool: - return tested.isspace() - - isspace_verifier = verifier_for(isspace) - - isspace_verifier.verify(bytearray(b''), expected_result=False) - isspace_verifier.verify(bytearray(b'abc'), expected_result=False) - isspace_verifier.verify(bytearray(b'ABC'), expected_result=False) - isspace_verifier.verify(bytearray(b'123'), expected_result=False) - isspace_verifier.verify(bytearray(b'ABC123'), expected_result=False) - isspace_verifier.verify(bytearray(b'+'), expected_result=False) - isspace_verifier.verify(bytearray(b'[]'), expected_result=False) - isspace_verifier.verify(bytearray(b'-'), expected_result=False) - isspace_verifier.verify(bytearray(b'%'), expected_result=False) - isspace_verifier.verify(bytearray(b'\n'), expected_result=True) - isspace_verifier.verify(bytearray(b'\t'), expected_result=True) - isspace_verifier.verify(bytearray(b' '), expected_result=True) - - -def test_istitle(): - def istitle(tested: bytearray) -> bool: - return tested.istitle() - - istitle_verifier = verifier_for(istitle) - - istitle_verifier.verify(bytearray(b''), expected_result=False) - - istitle_verifier.verify(bytearray(b'Abc'), expected_result=True) - istitle_verifier.verify(bytearray(b'The Title'), expected_result=True) - istitle_verifier.verify(bytearray(b'The title'), expected_result=False) - - istitle_verifier.verify(bytearray(b'abc'), expected_result=False) - istitle_verifier.verify(bytearray(b'ABC'), expected_result=False) - istitle_verifier.verify(bytearray(b'123'), expected_result=False) - istitle_verifier.verify(bytearray(b'ABC123'), expected_result=False) - istitle_verifier.verify(bytearray(b'+'), expected_result=False) - istitle_verifier.verify(bytearray(b'[]'), expected_result=False) - istitle_verifier.verify(bytearray(b'-'), expected_result=False) - istitle_verifier.verify(bytearray(b'%'), expected_result=False) - istitle_verifier.verify(bytearray(b'\n'), expected_result=False) - istitle_verifier.verify(bytearray(b'\t'), expected_result=False) - istitle_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_isupper(): - def isupper(tested: bytearray) -> bool: - return tested.isupper() - - isupper_verifier = verifier_for(isupper) - - isupper_verifier.verify(bytearray(b''), expected_result=False) - isupper_verifier.verify(bytearray(b'abc'), expected_result=False) - isupper_verifier.verify(bytearray(b'ABC'), expected_result=True) - isupper_verifier.verify(bytearray(b'123'), expected_result=False) - isupper_verifier.verify(bytearray(b'ABC123'), expected_result=True) - isupper_verifier.verify(bytearray(b'+'), expected_result=False) - isupper_verifier.verify(bytearray(b'[]'), expected_result=False) - isupper_verifier.verify(bytearray(b'-'), expected_result=False) - isupper_verifier.verify(bytearray(b'%'), expected_result=False) - isupper_verifier.verify(bytearray(b'\n'), expected_result=False) - isupper_verifier.verify(bytearray(b'\t'), expected_result=False) - isupper_verifier.verify(bytearray(b' '), expected_result=False) - - -def test_join(): - def join(tested: bytearray, iterable: list) -> bytearray: - return tested.join(iterable) - - join_verifier = verifier_for(join) - - join_verifier.verify(bytearray(b', '), [], expected_result=bytearray(b'')) - join_verifier.verify(bytearray(b', '), [bytearray(b'a')], expected_result=bytearray(b'a')) - join_verifier.verify(bytearray(b', '), [bytearray(b'a'), bytearray(b'b')], expected_result=bytearray(b'a, b')) - join_verifier.verify(bytearray(b' '), [bytearray(b'hello'), bytearray(b'world'), bytearray(b'again')], expected_result=bytearray(b'hello world again')) - join_verifier.verify(bytearray(b'\n'), [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], expected_result=bytearray(b'1\n2\n3')) - join_verifier.verify(bytearray(b', '), [1, 2], expected_error=TypeError) - - -def test_ljust(): - def ljust(tested: bytearray, width: int) -> bytearray: - return tested.ljust(width) - - def ljust_with_fill(tested: bytearray, width: int, fill: bytearray) -> bytearray: - return tested.ljust(width, fill) - - ljust_verifier = verifier_for(ljust) - ljust_with_fill_verifier = verifier_for(ljust_with_fill) - - ljust_verifier.verify(bytearray(b'test'), 10, expected_result=bytearray(b'test ')) - ljust_verifier.verify(bytearray(b'test'), 9, expected_result=bytearray(b'test ')) - ljust_verifier.verify(bytearray(b'test'), 4, expected_result=bytearray(b'test')) - ljust_verifier.verify(bytearray(b'test'), 2, expected_result=bytearray(b'test')) - - ljust_with_fill_verifier.verify(bytearray(b'test'), 10, bytearray(b'#'), expected_result=bytearray(b'test######')) - ljust_with_fill_verifier.verify(bytearray(b'test'), 9, bytearray(b'#'), expected_result=bytearray(b'test#####')) - ljust_with_fill_verifier.verify(bytearray(b'test'), 4, bytearray(b'#'), expected_result=bytearray(b'test')) - ljust_with_fill_verifier.verify(bytearray(b'test'), 2, bytearray(b'#'), expected_result=bytearray(b'test')) - - -def test_lower(): - def lower(tested: bytearray) -> bytearray: - return tested.lower() - - lower_verifier = verifier_for(lower) - - lower_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - lower_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'abc')) - lower_verifier.verify(bytearray(b'ABC'), expected_result=bytearray(b'abc')) - lower_verifier.verify(bytearray(b'123'), expected_result=bytearray(b'123')) - lower_verifier.verify(bytearray(b'ABC123'), expected_result=bytearray(b'abc123')) - lower_verifier.verify(bytearray(b'+'), expected_result=bytearray(b'+')) - lower_verifier.verify(bytearray(b'[]'), expected_result=bytearray(b'[]')) - lower_verifier.verify(bytearray(b'-'), expected_result=bytearray(b'-')) - lower_verifier.verify(bytearray(b'%'), expected_result=bytearray(b'%')) - lower_verifier.verify(bytearray('π'.encode()), expected_result=bytearray('π'.encode())) - lower_verifier.verify(bytearray(b'\n'), expected_result=bytearray(b'\n')) - lower_verifier.verify(bytearray(b'\t'), expected_result=bytearray(b'\t')) - lower_verifier.verify(bytearray(b' '), expected_result=bytearray(b' ')) - - -def test_lstrip(): - def lstrip(tested: bytearray) -> bytearray: - return tested.lstrip() - - def lstrip_with_chars(tested: bytearray, chars: bytearray) -> bytearray: - return tested.lstrip(chars) - - lstrip_verifier = verifier_for(lstrip) - lstrip_with_chars_verifier = verifier_for(lstrip_with_chars) - - lstrip_verifier.verify(bytearray(b' spacious '), expected_result=bytearray(b'spacious ')) - lstrip_with_chars_verifier.verify(bytearray(b'www.example.com'), bytearray(b'cmowz.'), expected_result=bytearray(b'example.com')) - - -def test_partition(): - def partition(tested: bytearray, sep: bytearray) -> tuple: - return tested.partition(sep) - - partition_verifier = verifier_for(partition) - - partition_verifier.verify(bytearray(b'before+after+extra'), bytearray(b'+'), expected_result=(bytearray(b'before'), bytearray(b'+'), bytearray(b'after+extra'))) - partition_verifier.verify(bytearray(b'before and after and extra'), bytearray(b'+'), expected_result=(bytearray(b'before and after and extra'), - bytearray(b''), bytearray(b''))) - partition_verifier.verify(bytearray(b'before and after and extra'), bytearray(b' and '), - expected_result=(bytearray(b'before'), bytearray(b' and '), bytearray(b'after and extra'))) - partition_verifier.verify(bytearray(b'before+after+extra'), bytearray(b' and '), expected_result=(bytearray(b'before+after+extra'), bytearray(b''), bytearray(b''))) - - -def test_removeprefix(): - def removeprefix(tested: bytearray, prefix: bytearray) -> bytearray: - return tested.removeprefix(prefix) - - removeprefix_verifier = verifier_for(removeprefix) - - removeprefix_verifier.verify(bytearray(b'TestHook'), bytearray(b'Test'), expected_result=bytearray(b'Hook')) - removeprefix_verifier.verify(bytearray(b'BaseTestCase'), bytearray(b'Test'), expected_result=bytearray(b'BaseTestCase')) - removeprefix_verifier.verify(bytearray(b'BaseCaseTest'), bytearray(b'Test'), expected_result=bytearray(b'BaseCaseTest')) - removeprefix_verifier.verify(bytearray(b'BaseCase'), bytearray(b'Test'), expected_result=bytearray(b'BaseCase')) - - -def test_removesuffix(): - def removesuffix(tested: bytearray, prefix: bytearray) -> bytearray: - return tested.removesuffix(prefix) - - removesuffix_verifier = verifier_for(removesuffix) - - removesuffix_verifier.verify(bytearray(b'MiscTests'), bytearray(b'Tests'), expected_result=bytearray(b'Misc')) - removesuffix_verifier.verify(bytearray(b'TmpTestsDirMixin'), bytearray(b'Tests'), expected_result=bytearray(b'TmpTestsDirMixin')) - removesuffix_verifier.verify(bytearray(b'TestsTmpDirMixin'), bytearray(b'Tests'), expected_result=bytearray(b'TestsTmpDirMixin')) - removesuffix_verifier.verify(bytearray(b'TmpDirMixin'), bytearray(b'Tests'), expected_result=bytearray(b'TmpDirMixin')) - - -def test_replace(): - def replace(tested: bytearray, substring: bytearray, replacement: bytearray) -> bytearray: - return tested.replace(substring, replacement) - - def replace_with_count(tested: bytearray, substring: bytearray, replacement: bytearray, count: int) -> bytearray: - return tested.replace(substring, replacement, count) - - - replace_verifier = verifier_for(replace) - replace_with_count_verifier = verifier_for(replace_with_count) - - replace_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), - expected_result=bytearray(b'all dogs, including the dog Aldogo, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), 0, - expected_result=bytearray(b'all cats, including the cat Alcato, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), 1, - expected_result=bytearray(b'all dogs, including the cat Alcato, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), 2, - expected_result=bytearray(b'all dogs, including the dog Alcato, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), 3, - expected_result=bytearray(b'all dogs, including the dog Aldogo, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), 4, - expected_result=bytearray(b'all dogs, including the dog Aldogo, are animals')) - replace_with_count_verifier.verify(bytearray(b'all cats, including the cat Alcato, are animals'), bytearray(b'cat'), bytearray(b'dog'), -1, - expected_result=bytearray(b'all dogs, including the dog Aldogo, are animals')) - - -def test_rfind(): - def rfind(tested: bytearray, item: bytearray) -> int: - return tested.rfind(item) - - def rfind_start_verifier(tested: bytearray, item: bytearray, start: int) -> int: - return tested.rfind(item, start) - - def rfind_start_end_verifier(tested: bytearray, item: bytearray, start: int, end: int) -> int: - return tested.rfind(item, start, end) - - rfind_verifier = verifier_for(rfind) - rfind_start_verifier = verifier_for(rfind_start_verifier) - rfind_start_end_verifier = verifier_for(rfind_start_end_verifier) - - rfind_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), expected_result=3) - rfind_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), expected_result=4) - rfind_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), expected_result=-1) - - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, expected_result=3) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 5, expected_result=-1) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, expected_result=4) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, expected_result=5) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, expected_result=-1) - - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -3, expected_result=3) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, expected_result=4) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, expected_result=5) - rfind_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, expected_result=-1) - - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, 2, expected_result=-1) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, 2, expected_result=1) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, 2, expected_result=-1) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, 2, expected_result=-1) - - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -2, -1, expected_result=-1) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, -1, expected_result=4) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, -1, expected_result=-1) - rfind_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, -1, expected_result=-1) - - -def test_rindex(): - def rindex(tested: bytearray, item: bytearray) -> int: - return tested.rindex(item) - - def rindex_start_verifier(tested: bytearray, item: bytearray, start: int) -> int: - return tested.rindex(item, start) - - def rindex_start_end_verifier(tested: bytearray, item: bytearray, start: int, end: int) -> int: - return tested.rindex(item, start, end) - - rindex_verifier = verifier_for(rindex) - rindex_start_verifier = verifier_for(rindex_start_verifier) - rindex_start_end_verifier = verifier_for(rindex_start_end_verifier) - - rindex_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), expected_result=3) - rindex_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), expected_result=4) - rindex_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), expected_error=ValueError) - - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, expected_result=3) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 5, expected_error=ValueError) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, expected_result=4) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, expected_result=5) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, expected_error=ValueError) - - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -3, expected_result=3) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, expected_result=4) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, expected_result=5) - rindex_start_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, expected_error=ValueError) - - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), 1, 2, expected_result=1) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), 1, 2, expected_error=ValueError) - - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'a'), -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'b'), -2, -1, expected_result=4) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'c'), -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify(bytearray(b'abcabc'), bytearray(b'd'), -2, -1, expected_error=ValueError) - -def test_rjust(): - def rjust(tested: bytearray, width: int) -> bytearray: - return tested.rjust(width) - - def rjust_with_fill(tested: bytearray, width: int, fill: bytearray) -> bytearray: - return tested.rjust(width, fill) - - rjust_verifier = verifier_for(rjust) - rjust_with_fill_verifier = verifier_for(rjust_with_fill) - - rjust_verifier.verify(bytearray(b'test'), 10, expected_result=bytearray(b' test')) - rjust_verifier.verify(bytearray(b'test'), 9, expected_result=bytearray(b' test')) - rjust_verifier.verify(bytearray(b'test'), 4, expected_result=bytearray(b'test')) - rjust_verifier.verify(bytearray(b'test'), 2, expected_result=bytearray(b'test')) - - rjust_with_fill_verifier.verify(bytearray(b'test'), 10, bytearray(b'#'), expected_result=bytearray(b'######test')) - rjust_with_fill_verifier.verify(bytearray(b'test'), 9, bytearray(b'#'), expected_result=bytearray(b'#####test')) - rjust_with_fill_verifier.verify(bytearray(b'test'), 4, bytearray(b'#'), expected_result=bytearray(b'test')) - rjust_with_fill_verifier.verify(bytearray(b'test'), 2, bytearray(b'#'), expected_result=bytearray(b'test')) - - -def test_rpartition(): - def rpartition(tested: bytearray, sep: bytearray) -> tuple: - return tested.rpartition(sep) - - rpartition_verifier = verifier_for(rpartition) - - rpartition_verifier.verify(bytearray(b'before+after+extra'), bytearray(b'+'), expected_result=(bytearray(b'before+after'), bytearray(b'+'), bytearray(b'extra'))) - rpartition_verifier.verify(bytearray(b'before and after and extra'), bytearray(b'+'), expected_result=(bytearray(b''), bytearray(b''), - bytearray(b'before and after and extra'))) - rpartition_verifier.verify(bytearray(b'before and after and extra'), bytearray(b' and '), - expected_result=(bytearray(b'before and after'), bytearray(b' and '), bytearray(b'extra'))) - rpartition_verifier.verify(bytearray(b'before+after+extra'), bytearray(b' and '), expected_result=(bytearray(b''), bytearray(b''), bytearray(b'before+after+extra'))) - - -def test_rsplit(): - def rsplit(tested: bytearray) -> list: - return tested.rsplit() - - def rsplit_with_sep(tested: bytearray, sep: bytearray) -> list: - return tested.rsplit(sep) - - def rsplit_with_sep_and_count(tested: bytearray, sep: bytearray, count: int) -> list: - return tested.rsplit(sep, count) - - rsplit_verifier = verifier_for(rsplit) - rsplit_with_sep_verifier = verifier_for(rsplit_with_sep) - rsplit_with_sep_and_count_verifier = verifier_for(rsplit_with_sep_and_count) - - rsplit_verifier.verify(bytearray(b'123'), expected_result=[bytearray(b'123')]) - rsplit_verifier.verify(bytearray(b'1 2 3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - rsplit_verifier.verify(bytearray(b' 1 2 3 '), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - rsplit_verifier.verify(bytearray(b'1\n2\n3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - rsplit_verifier.verify(bytearray(b'1\t2\t3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - - rsplit_with_sep_verifier.verify(bytearray(b'1,2,3'), bytearray(b','), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - rsplit_with_sep_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - rsplit_with_sep_verifier.verify(bytearray(b',1,2,,3,'), bytearray(b','), expected_result=[bytearray(b''), bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - - rsplit_with_sep_and_count_verifier.verify(bytearray(b'1,2,3'), bytearray(b','), 1, expected_result=[bytearray(b'1,2'), bytearray(b'3')]) - rsplit_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 1, expected_result=[bytearray(b'1,2,,3'), bytearray(b'')]) - rsplit_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 2, expected_result=[bytearray(b'1,2,'), bytearray(b'3'), bytearray(b'')]) - rsplit_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 3, expected_result=[bytearray(b'1,2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - rsplit_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 4, expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - - -def test_rstrip(): - def rstrip(tested: bytearray) -> bytearray: - return tested.rstrip() - - def rstrip_with_chars(tested: bytearray, chars: bytearray) -> bytearray: - return tested.rstrip(chars) - - rstrip_verifier = verifier_for(rstrip) - rstrip_with_chars_verifier = verifier_for(rstrip_with_chars) - - rstrip_verifier.verify(bytearray(b' spacious '), expected_result=bytearray(b' spacious')) - rstrip_with_chars_verifier.verify(bytearray(b'www.example.com'), bytearray(b'cmowz.'), expected_result=bytearray(b'www.example')) - - - -def test_split(): - def split(tested: bytearray) -> list: - return tested.split() - - def split_with_sep(tested: bytearray, sep: bytearray) -> list: - return tested.split(sep) - - def split_with_sep_and_count(tested: bytearray, sep: bytearray, count: int) -> list: - return tested.split(sep, count) - - split_verifier = verifier_for(split) - split_with_sep_verifier = verifier_for(split_with_sep) - split_with_sep_and_count_verifier = verifier_for(split_with_sep_and_count) - - split_verifier.verify(bytearray(b'123'), expected_result=[bytearray(b'123')]) - split_verifier.verify(bytearray(b'1 2 3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - split_verifier.verify(bytearray(b' 1 2 3 '), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - split_verifier.verify(bytearray(b'1\n2\n3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - split_verifier.verify(bytearray(b'1\t2\t3'), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - - split_with_sep_verifier.verify(bytearray(b'1,2,3'), bytearray(b','), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b'3')]) - split_with_sep_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - split_with_sep_verifier.verify(bytearray(b',1,2,,3,'), bytearray(b','), expected_result=[bytearray(b''), bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - - split_with_sep_and_count_verifier.verify(bytearray(b'1,2,3'), bytearray(b','), 1, expected_result=[bytearray(b'1'), bytearray(b'2,3')]) - split_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 1, expected_result=[bytearray(b'1'), bytearray(b'2,,3,')]) - split_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 2, expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b',3,')]) - split_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 3, expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3,')]) - split_with_sep_and_count_verifier.verify(bytearray(b'1,2,,3,'), bytearray(b','), 4, expected_result=[bytearray(b'1'), bytearray(b'2'), bytearray(b''), bytearray(b'3'), bytearray(b'')]) - - -def test_splitlines(): - def splitlines(tested: bytearray) -> list: - return tested.splitlines() - - def splitlines_keep_ends(tested: bytearray, keep_ends: bool) -> list: - return tested.splitlines(keep_ends) - - splitlines_verifier = verifier_for(splitlines) - splitlines_keep_ends_verifier = verifier_for(splitlines_keep_ends) - - splitlines_verifier.verify(bytearray(b'ab c\n\nde fg\rkl\r\n'), expected_result=[bytearray(b'ab c'), bytearray(b''), bytearray(b'de fg'), bytearray(b'kl')]) - splitlines_verifier.verify(bytearray(b''), expected_result=[]) - splitlines_verifier.verify(bytearray(b'One line\n'), expected_result=[bytearray(b'One line')]) - - splitlines_keep_ends_verifier.verify(bytearray(b'ab c\n\nde fg\rkl\r\n'), False, expected_result=[bytearray(b'ab c'), bytearray(b''), bytearray(b'de fg'), bytearray(b'kl')]) - splitlines_keep_ends_verifier.verify(bytearray(b'ab c\n\nde fg\rkl\r\n'), True, - expected_result=[bytearray(b'ab c\n'), bytearray(b'\n'), bytearray(b'de fg\r'), bytearray(b'kl\r\n')]) - splitlines_keep_ends_verifier.verify(bytearray(b''), True, expected_result=[]) - splitlines_keep_ends_verifier.verify(bytearray(b''), False, expected_result=[]) - splitlines_keep_ends_verifier.verify(bytearray(b'One line\n'), True, expected_result=[bytearray(b'One line\n')]) - splitlines_keep_ends_verifier.verify(bytearray(b'One line\n'), False, expected_result=[bytearray(b'One line')]) - - -def test_startswith(): - def startswith(tested: bytearray, suffix: bytearray) -> bool: - return tested.startswith(suffix) - - def startswith_start(tested: bytearray, suffix: bytearray, start: int) -> bool: - return tested.startswith(suffix, start) - - def startswith_between(tested: bytearray, suffix: bytearray, start: int, end: int) -> bool: - return tested.startswith(suffix, start, end) - - startswith_verifier = verifier_for(startswith) - startswith_start_verifier = verifier_for(startswith_start) - startswith_between_verifier = verifier_for(startswith_between) - - startswith_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), expected_result=True) - startswith_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), expected_result=False) - startswith_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), expected_result=False) - startswith_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), expected_result=True) - - startswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 6, expected_result=True) - startswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), 6, expected_result=False) - startswith_start_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), 6, expected_result=False) - startswith_start_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), 6, expected_result=False) - - startswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 6, 11, expected_result=True) - startswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'world'), 7, 11, expected_result=False) - startswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'hello'), 0, 5, expected_result=True) - startswith_between_verifier.verify(bytearray(b'hello'), bytearray(b'hello world'), 0, 5, expected_result=False) - startswith_between_verifier.verify(bytearray(b'hello world'), bytearray(b'hello world'), 5, 11, expected_result=False) - - -def test_strip(): - def strip(tested: bytearray) -> bytearray: - return tested.strip() - - def strip_with_chars(tested: bytearray, chars: bytearray) -> bytearray: - return tested.strip(chars) - - strip_verifier = verifier_for(strip) - strip_with_chars_verifier = verifier_for(strip_with_chars) - - strip_verifier.verify(bytearray(b' spacious '), expected_result=bytearray(b'spacious')) - strip_with_chars_verifier.verify(bytearray(b'www.example.com'), bytearray(b'cmowz.'), expected_result=bytearray(b'example')) - - -def test_swapcase(): - def swapcase(tested: bytearray) -> bytearray: - return tested.swapcase() - - swapcase_verifier = verifier_for(swapcase) - - swapcase_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - swapcase_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'ABC')) - swapcase_verifier.verify(bytearray(b'ABC'), expected_result=bytearray(b'abc')) - swapcase_verifier.verify(bytearray(b'123'), expected_result=bytearray(b'123')) - swapcase_verifier.verify(bytearray(b'ABC123'), expected_result=bytearray(b'abc123')) - swapcase_verifier.verify(bytearray(b'+'), expected_result=bytearray(b'+')) - swapcase_verifier.verify(bytearray(b'[]'), expected_result=bytearray(b'[]')) - swapcase_verifier.verify(bytearray(b'-'), expected_result=bytearray(b'-')) - swapcase_verifier.verify(bytearray(b'%'), expected_result=bytearray(b'%')) - swapcase_verifier.verify(bytearray('π'.encode()), expected_result=bytearray('π'.encode())) - swapcase_verifier.verify(bytearray(b'\n'), expected_result=bytearray(b'\n')) - swapcase_verifier.verify(bytearray(b'\t'), expected_result=bytearray(b'\t')) - swapcase_verifier.verify(bytearray(b' '), expected_result=bytearray(b' ')) - - -def test_title(): - def title(tested: bytearray) -> bytearray: - return tested.title() - - title_verifier = verifier_for(title) - - title_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - title_verifier.verify(bytearray(b'Hello world'), expected_result=bytearray(b'Hello World')) - title_verifier.verify(bytearray(b"they're bill's friends from the UK"), - expected_result=bytearray(b"They'Re Bill'S Friends From The Uk")) - - title_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'Abc')) - title_verifier.verify(bytearray(b'ABC'), expected_result=bytearray(b'Abc')) - title_verifier.verify(bytearray(b'123'), expected_result=bytearray(b'123')) - title_verifier.verify(bytearray(b'ABC123'), expected_result=bytearray(b'Abc123')) - title_verifier.verify(bytearray(b'+'), expected_result=bytearray(b'+')) - title_verifier.verify(bytearray(b'[]'), expected_result=bytearray(b'[]')) - title_verifier.verify(bytearray(b'-'), expected_result=bytearray(b'-')) - title_verifier.verify(bytearray(b'%'), expected_result=bytearray(b'%')) - title_verifier.verify(bytearray('π'.encode()), expected_result=bytearray('π'.encode())) - title_verifier.verify(bytearray(b'\n'), expected_result=bytearray(b'\n')) - title_verifier.verify(bytearray(b'\t'), expected_result=bytearray(b'\t')) - title_verifier.verify(bytearray(b' '), expected_result=bytearray(b' ')) - - -def test_translate(): - def translate(tested: bytearray, mapping: bytearray) -> bytearray: - return tested.translate(mapping) - - translate_verifier = verifier_for(translate) - - mapping = bytearray(b'').join([bytes([(i + 1) % 256]) for i in range(256)]) - - translate_verifier.verify(bytearray(b'hello world'), - mapping, expected_result=bytearray(b'ifmmp!xpsme')) - - -def test_upper(): - def upper(tested: bytearray) -> bytearray: - return tested.upper() - - upper_verifier = verifier_for(upper) - - upper_verifier.verify(bytearray(b''), expected_result=bytearray(b'')) - upper_verifier.verify(bytearray(b'abc'), expected_result=bytearray(b'ABC')) - upper_verifier.verify(bytearray(b'ABC'), expected_result=bytearray(b'ABC')) - upper_verifier.verify(bytearray(b'123'), expected_result=bytearray(b'123')) - upper_verifier.verify(bytearray(b'ABC123'), expected_result=bytearray(b'ABC123')) - upper_verifier.verify(bytearray(b'+'), expected_result=bytearray(b'+')) - upper_verifier.verify(bytearray(b'[]'), expected_result=bytearray(b'[]')) - upper_verifier.verify(bytearray(b'-'), expected_result=bytearray(b'-')) - upper_verifier.verify(bytearray(b'%'), expected_result=bytearray(b'%')) - upper_verifier.verify(bytearray(b'\n'), expected_result=bytearray(b'\n')) - upper_verifier.verify(bytearray(b'\t'), expected_result=bytearray(b'\t')) - upper_verifier.verify(bytearray(b' '), expected_result=bytearray(b' ')) - - -def test_zfill(): - def zfill(tested: bytearray, padding: int) -> bytearray: - return tested.zfill(padding) - - zfill_verifier = verifier_for(zfill) - - zfill_verifier.verify(bytearray(b'42'), 5, expected_result=bytearray(b'00042')) - zfill_verifier.verify(bytearray(b'-42'), 5, expected_result=bytearray(b'-0042')) - zfill_verifier.verify(bytearray(b'+42'), 5, expected_result=bytearray(b'+0042')) - zfill_verifier.verify(bytearray(b'42'), 1, expected_result=bytearray(b'42')) - zfill_verifier.verify(bytearray(b'-42'), 1, expected_result=bytearray(b'-42')) - zfill_verifier.verify(bytearray(b'+42'), 1, expected_result=bytearray(b'+42')) - zfill_verifier.verify(bytearray(b'abc'), 10, expected_result=bytearray(b'0000000abc')) diff --git a/jpyinterpreter/tests/test_bytes.py b/jpyinterpreter/tests/test_bytes.py deleted file mode 100644 index 1664f53e..00000000 --- a/jpyinterpreter/tests/test_bytes.py +++ /dev/null @@ -1,1086 +0,0 @@ -from typing import Union -from .conftest import verifier_for - - -######################################## -# Sequence methods -######################################## - -def test_membership(): - def membership(tested: bytes, x: bytes) -> bool: - return x in tested - - def not_membership(tested: bytes, x: bytes) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify(b'hello world', b'world', expected_result=True) - not_membership_verifier.verify(b'hello world', b'world', expected_result=False) - - membership_verifier.verify(b'hello world', b'test', expected_result=False) - not_membership_verifier.verify(b'hello world', b'test', expected_result=True) - - membership_verifier.verify(b'hello world', b'', expected_result=True) - not_membership_verifier.verify(b'hello world', b'', expected_result=False) - - -def test_concat(): - def concat(x: bytes, y: bytes) -> tuple: - out = x + y - return out, out is x, out is y - - concat_verifier = verifier_for(concat) - - concat_verifier.verify(b'hello ', b'world', expected_result=(b'hello world', False, False)) - concat_verifier.verify(b'', b'hello world', expected_result=(b'hello world', False, True)) - concat_verifier.verify(b'hello world', b'', expected_result=(b'hello world', True, False)) - concat_verifier.verify(b'world ', b'hello', expected_result=(b'world hello', False, False)) - - -def test_repeat(): - def left_repeat(x: bytes, y: int) -> tuple: - out = x * y - return out, out is x, out is y - - def right_repeat(x: int, y: bytes) -> tuple: - out = x * y - return out, out is x, out is y - - left_repeat_verifier = verifier_for(left_repeat) - right_repeat_verifier = verifier_for(right_repeat) - - left_repeat_verifier.verify(b'hi', 1, expected_result=(b'hi', True, False)) - left_repeat_verifier.verify(b'abc', 2, expected_result=(b'abcabc', False, False)) - left_repeat_verifier.verify(b'a', 4, expected_result=(b'aaaa', False, False)) - left_repeat_verifier.verify(b'test', 0, expected_result=(b'', False, False)) - left_repeat_verifier.verify(b'test', -1, expected_result=(b'', False, False)) - left_repeat_verifier.verify(b'test', -2, expected_result=(b'', False, False)) - - right_repeat_verifier.verify(1, b'hi', expected_result=(b'hi', False, True)) - right_repeat_verifier.verify(2, b'abc', expected_result=(b'abcabc', False, False)) - right_repeat_verifier.verify(4, b'a', expected_result=(b'aaaa', False, False)) - right_repeat_verifier.verify(0, b'test', expected_result=(b'', False, False)) - right_repeat_verifier.verify(-1, b'test', expected_result=(b'', False, False)) - right_repeat_verifier.verify(-2, b'test', expected_result=(b'', False, False)) - - -def test_get_item(): - def get_item(tested: bytes, index: int) -> int: - return tested[index] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify(b'abc', 1, expected_result=ord(b'b')) - get_item_verifier.verify(b'abc', -1, expected_result=ord(b'c')) - get_item_verifier.verify(b'abcd', -1, expected_result=ord(b'd')) - get_item_verifier.verify(b'abcd', -2, expected_result=ord(b'c')) - get_item_verifier.verify(b'abcd', 0, expected_result=ord(b'a')) - get_item_verifier.verify(b'abc', 3, expected_error=IndexError) - get_item_verifier.verify(b'abc', -4, expected_error=IndexError) - - -def test_get_slice(): - def get_slice(tested: bytes, start: Union[int, None], end: Union[int, None]) -> bytes: - return tested[start:end] - - get_slice_verifier = verifier_for(get_slice) - - get_slice_verifier.verify(b'abcde', 1, 3, expected_result=b'bc') - get_slice_verifier.verify(b'abcde', -3, -1, expected_result=b'cd') - - get_slice_verifier.verify(b'abcde', 0, -2, expected_result=b'abc') - get_slice_verifier.verify(b'abcde', -3, 4, expected_result=b'cd') - - get_slice_verifier.verify(b'abcde', 3, 1, expected_result=b'') - get_slice_verifier.verify(b'abcde', -1, -3, expected_result=b'') - - get_slice_verifier.verify(b'abcde', 100, 1000, expected_result=b'') - get_slice_verifier.verify(b'abcde', 0, 1000, expected_result=b'abcde') - - get_slice_verifier.verify(b'abcde', 1, None, expected_result=b'bcde') - get_slice_verifier.verify(b'abcde', None, 2, expected_result=b'ab') - get_slice_verifier.verify(b'abcde', None, None, expected_result=b'abcde') - - -def test_get_slice_with_step(): - def get_slice_with_step(tested: bytes, start: Union[int, None], end: Union[int, None], - step: Union[int, None]) -> bytes: - return tested[start:end:step] - - get_slice_verifier = verifier_for(get_slice_with_step) - - get_slice_verifier.verify(b'abcde', 0, None, 2, expected_result=b'ace') - get_slice_verifier.verify(b'abcde', 1, None, 2, expected_result=b'bd') - get_slice_verifier.verify(b'abcde', 0, 5, 2, expected_result=b'ace') - get_slice_verifier.verify(b'abcde', 1, 5, 2, expected_result=b'bd') - get_slice_verifier.verify(b'abcde', 0, -1, 2, expected_result=b'ac') - get_slice_verifier.verify(b'abcde', 1, -1, 2, expected_result=b'bd') - - get_slice_verifier.verify(b'abcde', 4, None, -2, expected_result=b'eca') - get_slice_verifier.verify(b'abcde', 3, None, -2, expected_result=b'db') - get_slice_verifier.verify(b'abcde', -1, -6, -2, expected_result=b'eca') - get_slice_verifier.verify(b'abcde', -2, -6, -2, expected_result=b'db') - get_slice_verifier.verify(b'abcde', 4, 0, -2, expected_result=b'ec') - get_slice_verifier.verify(b'abcde', 3, 0, -2, expected_result=b'db') - - get_slice_verifier.verify(b'abcde', 0, None, None, expected_result=b'abcde') - get_slice_verifier.verify(b'abcde', 0, 3, None, expected_result=b'abc') - - get_slice_verifier.verify(b'abcde', 3, 1, -1, expected_result=b'dc') - get_slice_verifier.verify(b'abcde', -1, -3, -1, expected_result=b'ed') - get_slice_verifier.verify(b'abcde', 3, 1, 1, expected_result=b'') - get_slice_verifier.verify(b'abcde', -1, -3, 1, expected_result=b'') - - -def test_len(): - def length(tested: bytes) -> int: - return len(tested) - - len_verifier = verifier_for(length) - - len_verifier.verify(b'', expected_result=0) - len_verifier.verify(b'a', expected_result=1) - len_verifier.verify(b'ab', expected_result=2) - len_verifier.verify(b'cba', expected_result=3) - - -def test_index(): - def index(tested: bytes, item: bytes) -> int: - return tested.index(item) - - def index_start(tested: bytes, item: bytes, start: int) -> int: - return tested.index(item, start) - - def index_start_end(tested: bytes, item: bytes, start: int, end: int) -> int: - return tested.index(item, start, end) - - index_verifier = verifier_for(index) - index_start_verifier = verifier_for(index_start) - index_start_end_verifier = verifier_for(index_start_end) - - index_verifier.verify(b'abcabc', b'a', expected_result=0) - index_verifier.verify(b'abcabc', b'b', expected_result=1) - index_verifier.verify(b'abcabc', b'd', expected_error=ValueError) - - index_start_verifier.verify(b'abcabc', b'a', 1, expected_result=3) - index_start_verifier.verify(b'abcabc', b'a', 5, expected_error=ValueError) - index_start_verifier.verify(b'abcabc', b'b', 1, expected_result=1) - index_start_verifier.verify(b'abcabc', b'c', 1, expected_result=2) - index_start_verifier.verify(b'abcabc', b'd', 1, expected_error=ValueError) - - index_start_verifier.verify(b'abcabc', b'a', -3, expected_result=3) - index_start_verifier.verify(b'abcabc', b'b', -2, expected_result=4) - index_start_verifier.verify(b'abcabc', b'c', -2, expected_result=5) - index_start_verifier.verify(b'abcabc', b'd', -2, expected_error=ValueError) - - index_start_end_verifier.verify(b'abcabc', b'a', 1, 2, expected_error=ValueError) - index_start_end_verifier.verify(b'abcabc', b'b', 1, 2, expected_result=1) - index_start_end_verifier.verify(b'abcabc', b'c', 1, 2, expected_error=ValueError) - index_start_end_verifier.verify(b'abcabc', b'd', 1, 2, expected_error=ValueError) - - index_start_end_verifier.verify(b'abcabc', b'a', -2, -1, expected_error=ValueError) - index_start_end_verifier.verify(b'abcabc', b'b', -2, -1, expected_result=4) - index_start_end_verifier.verify(b'abcabc', b'c', -2, -1, expected_error=ValueError) - index_start_end_verifier.verify(b'abcabc', b'd', -2, -1, expected_error=ValueError) - - -def test_count(): - def count(tested: bytes, item: bytes) -> int: - return tested.count(item) - - count_verifier = verifier_for(count) - - count_verifier.verify(b'abc', b'a', expected_result=1) - count_verifier.verify(b'abc', b'b', expected_result=1) - count_verifier.verify(b'abc', b'c', expected_result=1) - count_verifier.verify(b'abc', b'd', expected_result=0) - - count_verifier.verify(b'abca', b'a', expected_result=2) - count_verifier.verify(b'aaca', b'a', expected_result=3) - count_verifier.verify(b'', b'a', expected_result=0) - - -######################################## -# Bytes operations -######################################## -def test_interpolation(): - def interpolation(tested: bytes, values: object) -> bytes: - return tested % values - - interpolation_verifier = verifier_for(interpolation) - - interpolation_verifier.verify(b'%d', 100, expected_result=b'100') - interpolation_verifier.verify(b'%d', 0b1111, expected_result=b'15') - interpolation_verifier.verify(b'%s', b'foo', expected_result=b'foo') - interpolation_verifier.verify(b'%s %s', (b'foo', b'bar'), expected_result=b'foo bar') - interpolation_verifier.verify(b'%(foo)s', {b'foo': b'10', b'bar': b'20'}, expected_result=b'10') - - interpolation_verifier.verify(b'%d', 101, expected_result=b'101') - interpolation_verifier.verify(b'%i', 101, expected_result=b'101') - - interpolation_verifier.verify(b'%o', 27, expected_result=b'33') - interpolation_verifier.verify(b'%#o', 27, expected_result=b'0o33') - - interpolation_verifier.verify(b'%x', 27, expected_result=b'1b') - interpolation_verifier.verify(b'%X', 27, expected_result=b'1B') - interpolation_verifier.verify(b'%#x', 27, expected_result=b'0x1b') - interpolation_verifier.verify(b'%#X', 27, expected_result=b'0X1B') - - interpolation_verifier.verify(b'%03d', 1, expected_result=b'001') - interpolation_verifier.verify(b'%-5d', 1, expected_result=b'1 ') - interpolation_verifier.verify(b'%0-5d', 1, expected_result=b'1 ') - - interpolation_verifier.verify(b'%d', 1, expected_result=b'1') - interpolation_verifier.verify(b'%d', -1, expected_result=b'-1') - interpolation_verifier.verify(b'% d', 1, expected_result=b' 1') - interpolation_verifier.verify(b'% d', -1, expected_result=b'-1') - interpolation_verifier.verify(b'%+d', 1, expected_result=b'+1') - interpolation_verifier.verify(b'%+d', -1, expected_result=b'-1') - - interpolation_verifier.verify(b'%f', 3.14, expected_result=b'3.140000') - interpolation_verifier.verify(b'%F', 3.14, expected_result=b'3.140000') - interpolation_verifier.verify(b'%.1f', 3.14, expected_result=b'3.1') - interpolation_verifier.verify(b'%.2f', 3.14, expected_result=b'3.14') - interpolation_verifier.verify(b'%.3f', 3.14, expected_result=b'3.140') - - interpolation_verifier.verify(b'%g', 1234567890, expected_result=b'1.23457e+09') - interpolation_verifier.verify(b'%G', 1234567890, expected_result=b'1.23457E+09') - interpolation_verifier.verify(b'%e', 1234567890, expected_result=b'1.234568e+09') - interpolation_verifier.verify(b'%E', 1234567890, expected_result=b'1.234568E+09') - - interpolation_verifier.verify(b'ABC %c', 10, expected_result=b'ABC \n') - interpolation_verifier.verify(b'ABC %c', 67, expected_result=b'ABC C') - interpolation_verifier.verify(b'ABC %c', 68, expected_result=b'ABC D') - interpolation_verifier.verify(b'ABC %c', b'D', expected_result=b'ABC D') - interpolation_verifier.verify(b'ABC %s', b'test', expected_result=b'ABC test') - interpolation_verifier.verify(b'ABC %r', b'test', expected_result=b'ABC b\'test\'') - - interpolation_verifier.verify(b'Give it %d%%!', 100, expected_result=b'Give it 100%!') - interpolation_verifier.verify(b'Give it %(all-you-got)d%%!', {b'all-you-got': 100}, - expected_result=b'Give it 100%!') - - -######################################## -# Bytes methods -######################################## - - -def test_capitalize(): - def capitalize(tested: bytes) -> bytes: - return tested.capitalize() - - capitalize_verifier = verifier_for(capitalize) - - capitalize_verifier.verify(b'', expected_result=b'') - capitalize_verifier.verify(b'test', expected_result=b'Test') - capitalize_verifier.verify(b'TEST', expected_result=b'Test') - capitalize_verifier.verify(b'hello world', expected_result=b'Hello world') - capitalize_verifier.verify(b'Hello World', expected_result=b'Hello world') - capitalize_verifier.verify(b'HELLO WORLD', expected_result=b'Hello world') - capitalize_verifier.verify('π'.encode(), expected_result='π'.encode()) - - -def test_center(): - def center(tested: bytes, width: int) -> bytes: - return tested.center(width) - - def center_with_fill(tested: bytes, width: int, fill: bytes) -> bytes: - return tested.center(width, fill) - - center_verifier = verifier_for(center) - center_with_fill_verifier = verifier_for(center_with_fill) - - center_verifier.verify(b'test', 10, expected_result=b' test ') - center_verifier.verify(b'test', 9, expected_result=b' test ') - center_verifier.verify(b'test', 4, expected_result=b'test') - center_verifier.verify(b'test', 2, expected_result=b'test') - - center_with_fill_verifier.verify(b'test', 10, b'#', expected_result=b'###test###') - center_with_fill_verifier.verify(b'test', 9, b'#', expected_result=b'###test##') - center_with_fill_verifier.verify(b'test', 4, b'#', expected_result=b'test') - center_with_fill_verifier.verify(b'test', 2, b'#', expected_result=b'test') - - -def test_count_byte(): - def count(tested: bytes, item: int) -> int: - return tested.count(item) - - def count_start(tested: bytes, item: int, start: int) -> int: - return tested.count(item, start) - - def count_start_end(tested: bytes, item: int, start: int, end: int) -> int: - return tested.count(item, start, end) - - count_verifier = verifier_for(count) - count_from_start_verifier = verifier_for(count_start) - count_between_verifier = verifier_for(count_start_end) - - count_verifier.verify(b'abc', ord(b'a'), expected_result=1) - count_verifier.verify(b'abc', ord(b'b'), expected_result=1) - count_verifier.verify(b'abc', ord(b'c'), expected_result=1) - count_verifier.verify(b'abc', ord(b'd'), expected_result=0) - - count_verifier.verify(b'abca', ord(b'a'), expected_result=2) - count_verifier.verify(b'aaca', ord(b'a'), expected_result=3) - count_verifier.verify(b'', ord(b'a'), expected_result=0) - - count_from_start_verifier.verify(b'abc', ord(b'a'), 1, expected_result=0) - count_from_start_verifier.verify(b'abc', ord(b'b'), 1, expected_result=1) - count_from_start_verifier.verify(b'abc', ord(b'c'), 1, expected_result=1) - count_from_start_verifier.verify(b'abc', ord(b'd'), 1, expected_result=0) - - count_from_start_verifier.verify(b'abca', ord(b'a'), 1, expected_result=1) - count_from_start_verifier.verify(b'aaca', ord(b'a'), 1, expected_result=2) - count_from_start_verifier.verify(b'', ord(b'a'), 1, expected_result=0) - - count_between_verifier.verify(b'abc', ord(b'a'), 1, 2, expected_result=0) - count_between_verifier.verify(b'abc', ord(b'b'), 1, 2, expected_result=1) - count_between_verifier.verify(b'abc', ord(b'c'), 1, 2, expected_result=0) - count_between_verifier.verify(b'abc', ord(b'd'), 1, 2, expected_result=0) - - count_between_verifier.verify(b'abca', ord(b'a'), 1, 2, expected_result=0) - count_between_verifier.verify(b'abca', ord(b'a'), 1, 4, expected_result=1) - count_between_verifier.verify(b'abca', ord(b'a'), 0, 2, expected_result=1) - count_between_verifier.verify(b'aaca', ord(b'a'), 1, 2, expected_result=1) - count_between_verifier.verify(b'', ord(b'a'), 1, 2, expected_result=0) - - -def test_endswith(): - def endswith(tested: bytes, suffix: bytes) -> bool: - return tested.endswith(suffix) - - def endswith_start(tested: bytes, suffix: bytes, start: int) -> bool: - return tested.endswith(suffix, start) - - def endswith_between(tested: bytes, suffix: bytes, start: int, end: int) -> bool: - return tested.endswith(suffix, start, end) - - endswith_verifier = verifier_for(endswith) - endswith_start_verifier = verifier_for(endswith_start) - endswith_between_verifier = verifier_for(endswith_between) - - endswith_verifier.verify(b'hello world', b'world', expected_result=True) - endswith_verifier.verify(b'hello world', b'hello', expected_result=False) - endswith_verifier.verify(b'hello', b'hello world', expected_result=False) - endswith_verifier.verify(b'hello world', b'hello world', expected_result=True) - - endswith_start_verifier.verify(b'hello world', b'world', 6, expected_result=True) - endswith_start_verifier.verify(b'hello world', b'hello', 6, expected_result=False) - endswith_start_verifier.verify(b'hello', b'hello world', 6, expected_result=False) - endswith_start_verifier.verify(b'hello world', b'hello world', 6, expected_result=False) - - endswith_between_verifier.verify(b'hello world', b'world', 6, 11, expected_result=True) - endswith_between_verifier.verify(b'hello world', b'world', 7, 11, expected_result=False) - endswith_between_verifier.verify(b'hello world', b'hello', 0, 5, expected_result=True) - endswith_between_verifier.verify(b'hello', b'hello world', 0, 5, expected_result=False) - endswith_between_verifier.verify(b'hello world', b'hello world', 5, 11, expected_result=False) - - -def test_expandtabs(): - def expandtabs(tested: bytes) -> bytes: - return tested.expandtabs() - - def expandtabs_with_tabsize(tested: bytes, tabsize: int) -> bytes: - return tested.expandtabs(tabsize) - - expandtabs_verifier = verifier_for(expandtabs) - expandtabs_with_tabsize_verifier = verifier_for(expandtabs_with_tabsize) - - expandtabs_verifier.verify(b'01\t012\t0123\t01234', expected_result=b'01 012 0123 01234') - expandtabs_with_tabsize_verifier.verify(b'01\t012\t0123\t01234', 8, expected_result=b'01 012 0123 01234') - expandtabs_with_tabsize_verifier.verify(b'01\t012\t0123\t01234', 4, expected_result=b'01 012 0123 01234') - - -def test_find(): - def find(tested: bytes, item: bytes) -> int: - return tested.find(item) - - def find_start_verifier(tested: bytes, item: bytes, start: int) -> int: - return tested.find(item, start) - - def find_start_end_verifier(tested: bytes, item: bytes, start: int, end: int) -> int: - return tested.find(item, start, end) - - find_verifier = verifier_for(find) - find_start_verifier = verifier_for(find_start_verifier) - find_start_end_verifier = verifier_for(find_start_end_verifier) - - find_verifier.verify(b'abcabc', b'a', expected_result=0) - find_verifier.verify(b'abcabc', b'b', expected_result=1) - find_verifier.verify(b'abcabc', b'd', expected_result=-1) - - find_start_verifier.verify(b'abcabc', b'a', 1, expected_result=3) - find_start_verifier.verify(b'abcabc', b'a', 5, expected_result=-1) - find_start_verifier.verify(b'abcabc', b'b', 1, expected_result=1) - find_start_verifier.verify(b'abcabc', b'c', 1, expected_result=2) - find_start_verifier.verify(b'abcabc', b'd', 1, expected_result=-1) - - find_start_verifier.verify(b'abcabc', b'a', -3, expected_result=3) - find_start_verifier.verify(b'abcabc', b'b', -2, expected_result=4) - find_start_verifier.verify(b'abcabc', b'c', -2, expected_result=5) - find_start_verifier.verify(b'abcabc', b'd', -2, expected_result=-1) - - find_start_end_verifier.verify(b'abcabc', b'a', 1, 2, expected_result=-1) - find_start_end_verifier.verify(b'abcabc', b'b', 1, 2, expected_result=1) - find_start_end_verifier.verify(b'abcabc', b'c', 1, 2, expected_result=-1) - find_start_end_verifier.verify(b'abcabc', b'd', 1, 2, expected_result=-1) - - find_start_end_verifier.verify(b'abcabc', b'a', -2, -1, expected_result=-1) - find_start_end_verifier.verify(b'abcabc', b'b', -2, -1, expected_result=4) - find_start_end_verifier.verify(b'abcabc', b'c', -2, -1, expected_result=-1) - find_start_end_verifier.verify(b'abcabc', b'd', -2, -1, expected_result=-1) - - -def test_isalnum(): - def isalnum(tested: bytes) -> bool: - return tested.isalnum() - - isalnum_verifier = verifier_for(isalnum) - - isalnum_verifier.verify(b'', expected_result=False) - isalnum_verifier.verify(b'abc', expected_result=True) - isalnum_verifier.verify(b'ABC', expected_result=True) - isalnum_verifier.verify(b'123', expected_result=True) - isalnum_verifier.verify(b'ABC123', expected_result=True) - isalnum_verifier.verify(b'+', expected_result=False) - isalnum_verifier.verify(b'[]', expected_result=False) - isalnum_verifier.verify(b'-', expected_result=False) - isalnum_verifier.verify(b'%', expected_result=False) - isalnum_verifier.verify(b'\n', expected_result=False) - isalnum_verifier.verify(b'\t', expected_result=False) - isalnum_verifier.verify(b' ', expected_result=False) - - -def test_isalpha(): - def isalpha(tested: bytes) -> bool: - return tested.isalpha() - - isalpha_verifier = verifier_for(isalpha) - - isalpha_verifier.verify(b'', expected_result=False) - isalpha_verifier.verify(b'abc', expected_result=True) - isalpha_verifier.verify(b'ABC', expected_result=True) - isalpha_verifier.verify(b'123', expected_result=False) - isalpha_verifier.verify(b'ABC123', expected_result=False) - isalpha_verifier.verify(b'+', expected_result=False) - isalpha_verifier.verify(b'[]', expected_result=False) - isalpha_verifier.verify(b'-', expected_result=False) - isalpha_verifier.verify(b'%', expected_result=False) - isalpha_verifier.verify(b'\n', expected_result=False) - isalpha_verifier.verify(b'\t', expected_result=False) - isalpha_verifier.verify(b' ', expected_result=False) - - -def test_isascii(): - def isascii(tested: bytes) -> bool: - return tested.isascii() - - isascii_verifier = verifier_for(isascii) - - isascii_verifier.verify(b'', expected_result=True) - isascii_verifier.verify(b'abc', expected_result=True) - isascii_verifier.verify(b'ABC', expected_result=True) - isascii_verifier.verify(b'123', expected_result=True) - isascii_verifier.verify(b'ABC123', expected_result=True) - isascii_verifier.verify(b'+', expected_result=True) - isascii_verifier.verify(b'[]', expected_result=True) - isascii_verifier.verify(b'-', expected_result=True) - isascii_verifier.verify(b'%', expected_result=True) - isascii_verifier.verify(b'\n', expected_result=True) - isascii_verifier.verify(b'\t', expected_result=True) - isascii_verifier.verify(b' ', expected_result=True) - - -def test_isdigit(): - def isdigit(tested: bytes) -> bool: - return tested.isdigit() - - isdigit_verifier = verifier_for(isdigit) - - isdigit_verifier.verify(b'', expected_result=False) - isdigit_verifier.verify(b'abc', expected_result=False) - isdigit_verifier.verify(b'ABC', expected_result=False) - isdigit_verifier.verify(b'123', expected_result=True) - isdigit_verifier.verify(b'ABC123', expected_result=False) - isdigit_verifier.verify(b'+', expected_result=False) - isdigit_verifier.verify(b'[]', expected_result=False) - isdigit_verifier.verify(b'-', expected_result=False) - isdigit_verifier.verify(b'%', expected_result=False) - isdigit_verifier.verify(b'\n', expected_result=False) - isdigit_verifier.verify(b'\t', expected_result=False) - isdigit_verifier.verify(b' ', expected_result=False) - - -def test_islower(): - def islower(tested: bytes) -> bool: - return tested.islower() - - islower_verifier = verifier_for(islower) - - islower_verifier.verify(b'', expected_result=False) - islower_verifier.verify(b'abc', expected_result=True) - islower_verifier.verify(b'ABC', expected_result=False) - islower_verifier.verify(b'123', expected_result=False) - islower_verifier.verify(b'ABC123', expected_result=False) - islower_verifier.verify(b'+', expected_result=False) - islower_verifier.verify(b'[]', expected_result=False) - islower_verifier.verify(b'-', expected_result=False) - islower_verifier.verify(b'%', expected_result=False) - islower_verifier.verify(b'\n', expected_result=False) - islower_verifier.verify(b'\t', expected_result=False) - islower_verifier.verify(b' ', expected_result=False) - - -def test_isspace(): - def isspace(tested: bytes) -> bool: - return tested.isspace() - - isspace_verifier = verifier_for(isspace) - - isspace_verifier.verify(b'', expected_result=False) - isspace_verifier.verify(b'abc', expected_result=False) - isspace_verifier.verify(b'ABC', expected_result=False) - isspace_verifier.verify(b'123', expected_result=False) - isspace_verifier.verify(b'ABC123', expected_result=False) - isspace_verifier.verify(b'+', expected_result=False) - isspace_verifier.verify(b'[]', expected_result=False) - isspace_verifier.verify(b'-', expected_result=False) - isspace_verifier.verify(b'%', expected_result=False) - isspace_verifier.verify(b'\n', expected_result=True) - isspace_verifier.verify(b'\t', expected_result=True) - isspace_verifier.verify(b' ', expected_result=True) - - -def test_istitle(): - def istitle(tested: bytes) -> bool: - return tested.istitle() - - istitle_verifier = verifier_for(istitle) - - istitle_verifier.verify(b'', expected_result=False) - - istitle_verifier.verify(b'Abc', expected_result=True) - istitle_verifier.verify(b'The Title', expected_result=True) - istitle_verifier.verify(b'The title', expected_result=False) - - istitle_verifier.verify(b'abc', expected_result=False) - istitle_verifier.verify(b'ABC', expected_result=False) - istitle_verifier.verify(b'123', expected_result=False) - istitle_verifier.verify(b'ABC123', expected_result=False) - istitle_verifier.verify(b'+', expected_result=False) - istitle_verifier.verify(b'[]', expected_result=False) - istitle_verifier.verify(b'-', expected_result=False) - istitle_verifier.verify(b'%', expected_result=False) - istitle_verifier.verify(b'\n', expected_result=False) - istitle_verifier.verify(b'\t', expected_result=False) - istitle_verifier.verify(b' ', expected_result=False) - - -def test_isupper(): - def isupper(tested: bytes) -> bool: - return tested.isupper() - - isupper_verifier = verifier_for(isupper) - - isupper_verifier.verify(b'', expected_result=False) - isupper_verifier.verify(b'abc', expected_result=False) - isupper_verifier.verify(b'ABC', expected_result=True) - isupper_verifier.verify(b'123', expected_result=False) - isupper_verifier.verify(b'ABC123', expected_result=True) - isupper_verifier.verify(b'+', expected_result=False) - isupper_verifier.verify(b'[]', expected_result=False) - isupper_verifier.verify(b'-', expected_result=False) - isupper_verifier.verify(b'%', expected_result=False) - isupper_verifier.verify(b'\n', expected_result=False) - isupper_verifier.verify(b'\t', expected_result=False) - isupper_verifier.verify(b' ', expected_result=False) - - -def test_join(): - def join(tested: bytes, iterable: list) -> bytes: - return tested.join(iterable) - - join_verifier = verifier_for(join) - - join_verifier.verify(b', ', [], expected_result=b'') - join_verifier.verify(b', ', [b'a'], expected_result=b'a') - join_verifier.verify(b', ', [b'a', b'b'], expected_result=b'a, b') - join_verifier.verify(b' ', [b'hello', b'world', b'again'], expected_result=b'hello world again') - join_verifier.verify(b'\n', [b'1', b'2', b'3'], expected_result=b'1\n2\n3') - join_verifier.verify(b', ', [1, 2], expected_error=TypeError) - - -def test_ljust(): - def ljust(tested: bytes, width: int) -> bytes: - return tested.ljust(width) - - def ljust_with_fill(tested: bytes, width: int, fill: bytes) -> bytes: - return tested.ljust(width, fill) - - ljust_verifier = verifier_for(ljust) - ljust_with_fill_verifier = verifier_for(ljust_with_fill) - - ljust_verifier.verify(b'test', 10, expected_result=b'test ') - ljust_verifier.verify(b'test', 9, expected_result=b'test ') - ljust_verifier.verify(b'test', 4, expected_result=b'test') - ljust_verifier.verify(b'test', 2, expected_result=b'test') - - ljust_with_fill_verifier.verify(b'test', 10, b'#', expected_result=b'test######') - ljust_with_fill_verifier.verify(b'test', 9, b'#', expected_result=b'test#####') - ljust_with_fill_verifier.verify(b'test', 4, b'#', expected_result=b'test') - ljust_with_fill_verifier.verify(b'test', 2, b'#', expected_result=b'test') - - -def test_lower(): - def lower(tested: bytes) -> bytes: - return tested.lower() - - lower_verifier = verifier_for(lower) - - lower_verifier.verify(b'', expected_result=b'') - lower_verifier.verify(b'abc', expected_result=b'abc') - lower_verifier.verify(b'ABC', expected_result=b'abc') - lower_verifier.verify(b'123', expected_result=b'123') - lower_verifier.verify(b'ABC123', expected_result=b'abc123') - lower_verifier.verify(b'+', expected_result=b'+') - lower_verifier.verify(b'[]', expected_result=b'[]') - lower_verifier.verify(b'-', expected_result=b'-') - lower_verifier.verify(b'%', expected_result=b'%') - lower_verifier.verify('π'.encode(), expected_result='π'.encode()) - lower_verifier.verify(b'\n', expected_result=b'\n') - lower_verifier.verify(b'\t', expected_result=b'\t') - lower_verifier.verify(b' ', expected_result=b' ') - - -def test_lstrip(): - def lstrip(tested: bytes) -> bytes: - return tested.lstrip() - - def lstrip_with_chars(tested: bytes, chars: bytes) -> bytes: - return tested.lstrip(chars) - - lstrip_verifier = verifier_for(lstrip) - lstrip_with_chars_verifier = verifier_for(lstrip_with_chars) - - lstrip_verifier.verify(b' spacious ', expected_result=b'spacious ') - lstrip_with_chars_verifier.verify(b'www.example.com', b'cmowz.', expected_result=b'example.com') - - -def test_partition(): - def partition(tested: bytes, sep: bytes) -> tuple: - return tested.partition(sep) - - partition_verifier = verifier_for(partition) - - partition_verifier.verify(b'before+after+extra', b'+', expected_result=(b'before', b'+', b'after+extra')) - partition_verifier.verify(b'before and after and extra', b'+', expected_result=(b'before and after and extra', - b'', b'')) - partition_verifier.verify(b'before and after and extra', b' and ', - expected_result=(b'before', b' and ', b'after and extra')) - partition_verifier.verify(b'before+after+extra', b' and ', expected_result=(b'before+after+extra', b'', b'')) - - -def test_removeprefix(): - def removeprefix(tested: bytes, prefix: bytes) -> bytes: - return tested.removeprefix(prefix) - - removeprefix_verifier = verifier_for(removeprefix) - - removeprefix_verifier.verify(b'TestHook', b'Test', expected_result=b'Hook') - removeprefix_verifier.verify(b'BaseTestCase', b'Test', expected_result=b'BaseTestCase') - removeprefix_verifier.verify(b'BaseCaseTest', b'Test', expected_result=b'BaseCaseTest') - removeprefix_verifier.verify(b'BaseCase', b'Test', expected_result=b'BaseCase') - - -def test_removesuffix(): - def removesuffix(tested: bytes, prefix: bytes) -> bytes: - return tested.removesuffix(prefix) - - removesuffix_verifier = verifier_for(removesuffix) - - removesuffix_verifier.verify(b'MiscTests', b'Tests', expected_result=b'Misc') - removesuffix_verifier.verify(b'TmpTestsDirMixin', b'Tests', expected_result=b'TmpTestsDirMixin') - removesuffix_verifier.verify(b'TestsTmpDirMixin', b'Tests', expected_result=b'TestsTmpDirMixin') - removesuffix_verifier.verify(b'TmpDirMixin', b'Tests', expected_result=b'TmpDirMixin') - - -def test_replace(): - def replace(tested: bytes, substring: bytes, replacement: bytes) -> bytes: - return tested.replace(substring, replacement) - - def replace_with_count(tested: bytes, substring: bytes, replacement: bytes, count: int) -> bytes: - return tested.replace(substring, replacement, count) - - - replace_verifier = verifier_for(replace) - replace_with_count_verifier = verifier_for(replace_with_count) - - replace_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', - expected_result=b'all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', 0, - expected_result=b'all cats, including the cat Alcato, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', 1, - expected_result=b'all dogs, including the cat Alcato, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', 2, - expected_result=b'all dogs, including the dog Alcato, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', 3, - expected_result=b'all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', 4, - expected_result=b'all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify(b'all cats, including the cat Alcato, are animals', b'cat', b'dog', -1, - expected_result=b'all dogs, including the dog Aldogo, are animals') - - -def test_rfind(): - def rfind(tested: bytes, item: bytes) -> int: - return tested.rfind(item) - - def rfind_start_verifier(tested: bytes, item: bytes, start: int) -> int: - return tested.rfind(item, start) - - def rfind_start_end_verifier(tested: bytes, item: bytes, start: int, end: int) -> int: - return tested.rfind(item, start, end) - - rfind_verifier = verifier_for(rfind) - rfind_start_verifier = verifier_for(rfind_start_verifier) - rfind_start_end_verifier = verifier_for(rfind_start_end_verifier) - - rfind_verifier.verify(b'abcabc', b'a', expected_result=3) - rfind_verifier.verify(b'abcabc', b'b', expected_result=4) - rfind_verifier.verify(b'abcabc', b'd', expected_result=-1) - - rfind_start_verifier.verify(b'abcabc', b'a', 1, expected_result=3) - rfind_start_verifier.verify(b'abcabc', b'a', 5, expected_result=-1) - rfind_start_verifier.verify(b'abcabc', b'b', 1, expected_result=4) - rfind_start_verifier.verify(b'abcabc', b'c', 1, expected_result=5) - rfind_start_verifier.verify(b'abcabc', b'd', 1, expected_result=-1) - - rfind_start_verifier.verify(b'abcabc', b'a', -3, expected_result=3) - rfind_start_verifier.verify(b'abcabc', b'b', -2, expected_result=4) - rfind_start_verifier.verify(b'abcabc', b'c', -2, expected_result=5) - rfind_start_verifier.verify(b'abcabc', b'd', -2, expected_result=-1) - - rfind_start_end_verifier.verify(b'abcabc', b'a', 1, 2, expected_result=-1) - rfind_start_end_verifier.verify(b'abcabc', b'b', 1, 2, expected_result=1) - rfind_start_end_verifier.verify(b'abcabc', b'c', 1, 2, expected_result=-1) - rfind_start_end_verifier.verify(b'abcabc', b'd', 1, 2, expected_result=-1) - - rfind_start_end_verifier.verify(b'abcabc', b'a', -2, -1, expected_result=-1) - rfind_start_end_verifier.verify(b'abcabc', b'b', -2, -1, expected_result=4) - rfind_start_end_verifier.verify(b'abcabc', b'c', -2, -1, expected_result=-1) - rfind_start_end_verifier.verify(b'abcabc', b'd', -2, -1, expected_result=-1) - - -def test_rindex(): - def rindex(tested: bytes, item: bytes) -> int: - return tested.rindex(item) - - def rindex_start_verifier(tested: bytes, item: bytes, start: int) -> int: - return tested.rindex(item, start) - - def rindex_start_end_verifier(tested: bytes, item: bytes, start: int, end: int) -> int: - return tested.rindex(item, start, end) - - rindex_verifier = verifier_for(rindex) - rindex_start_verifier = verifier_for(rindex_start_verifier) - rindex_start_end_verifier = verifier_for(rindex_start_end_verifier) - - rindex_verifier.verify(b'abcabc', b'a', expected_result=3) - rindex_verifier.verify(b'abcabc', b'b', expected_result=4) - rindex_verifier.verify(b'abcabc', b'd', expected_error=ValueError) - - rindex_start_verifier.verify(b'abcabc', b'a', 1, expected_result=3) - rindex_start_verifier.verify(b'abcabc', b'a', 5, expected_error=ValueError) - rindex_start_verifier.verify(b'abcabc', b'b', 1, expected_result=4) - rindex_start_verifier.verify(b'abcabc', b'c', 1, expected_result=5) - rindex_start_verifier.verify(b'abcabc', b'd', 1, expected_error=ValueError) - - rindex_start_verifier.verify(b'abcabc', b'a', -3, expected_result=3) - rindex_start_verifier.verify(b'abcabc', b'b', -2, expected_result=4) - rindex_start_verifier.verify(b'abcabc', b'c', -2, expected_result=5) - rindex_start_verifier.verify(b'abcabc', b'd', -2, expected_error=ValueError) - - rindex_start_end_verifier.verify(b'abcabc', b'a', 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify(b'abcabc', b'b', 1, 2, expected_result=1) - rindex_start_end_verifier.verify(b'abcabc', b'c', 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify(b'abcabc', b'd', 1, 2, expected_error=ValueError) - - rindex_start_end_verifier.verify(b'abcabc', b'a', -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify(b'abcabc', b'b', -2, -1, expected_result=4) - rindex_start_end_verifier.verify(b'abcabc', b'c', -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify(b'abcabc', b'd', -2, -1, expected_error=ValueError) - -def test_rjust(): - def rjust(tested: bytes, width: int) -> bytes: - return tested.rjust(width) - - def rjust_with_fill(tested: bytes, width: int, fill: bytes) -> bytes: - return tested.rjust(width, fill) - - rjust_verifier = verifier_for(rjust) - rjust_with_fill_verifier = verifier_for(rjust_with_fill) - - rjust_verifier.verify(b'test', 10, expected_result=b' test') - rjust_verifier.verify(b'test', 9, expected_result=b' test') - rjust_verifier.verify(b'test', 4, expected_result=b'test') - rjust_verifier.verify(b'test', 2, expected_result=b'test') - - rjust_with_fill_verifier.verify(b'test', 10, b'#', expected_result=b'######test') - rjust_with_fill_verifier.verify(b'test', 9, b'#', expected_result=b'#####test') - rjust_with_fill_verifier.verify(b'test', 4, b'#', expected_result=b'test') - rjust_with_fill_verifier.verify(b'test', 2, b'#', expected_result=b'test') - - -def test_rpartition(): - def rpartition(tested: bytes, sep: bytes) -> tuple: - return tested.rpartition(sep) - - rpartition_verifier = verifier_for(rpartition) - - rpartition_verifier.verify(b'before+after+extra', b'+', expected_result=(b'before+after', b'+', b'extra')) - rpartition_verifier.verify(b'before and after and extra', b'+', expected_result=(b'', b'', - b'before and after and extra')) - rpartition_verifier.verify(b'before and after and extra', b' and ', - expected_result=(b'before and after', b' and ', b'extra')) - rpartition_verifier.verify(b'before+after+extra', b' and ', expected_result=(b'', b'', b'before+after+extra')) - - -def test_rsplit(): - def rsplit(tested: bytes) -> list: - return tested.rsplit() - - def rsplit_with_sep(tested: bytes, sep: bytes) -> list: - return tested.rsplit(sep) - - def rsplit_with_sep_and_count(tested: bytes, sep: bytes, count: int) -> list: - return tested.rsplit(sep, count) - - rsplit_verifier = verifier_for(rsplit) - rsplit_with_sep_verifier = verifier_for(rsplit_with_sep) - rsplit_with_sep_and_count_verifier = verifier_for(rsplit_with_sep_and_count) - - rsplit_verifier.verify(b'123', expected_result=[b'123']) - rsplit_verifier.verify(b'1 2 3', expected_result=[b'1', b'2', b'3']) - rsplit_verifier.verify(b' 1 2 3 ', expected_result=[b'1', b'2', b'3']) - rsplit_verifier.verify(b'1\n2\n3', expected_result=[b'1', b'2', b'3']) - rsplit_verifier.verify(b'1\t2\t3', expected_result=[b'1', b'2', b'3']) - - rsplit_with_sep_verifier.verify(b'1,2,3', b',', expected_result=[b'1', b'2', b'3']) - rsplit_with_sep_verifier.verify(b'1,2,,3,', b',', expected_result=[b'1', b'2', b'', b'3', b'']) - rsplit_with_sep_verifier.verify(b',1,2,,3,', b',', expected_result=[b'', b'1', b'2', b'', b'3', b'']) - - rsplit_with_sep_and_count_verifier.verify(b'1,2,3', b',', 1, expected_result=[b'1,2', b'3']) - rsplit_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 1, expected_result=[b'1,2,,3', b'']) - rsplit_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 2, expected_result=[b'1,2,', b'3', b'']) - rsplit_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 3, expected_result=[b'1,2', b'', b'3', b'']) - rsplit_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 4, expected_result=[b'1', b'2', b'', b'3', b'']) - - -def test_rstrip(): - def rstrip(tested: bytes) -> bytes: - return tested.rstrip() - - def rstrip_with_chars(tested: bytes, chars: bytes) -> bytes: - return tested.rstrip(chars) - - rstrip_verifier = verifier_for(rstrip) - rstrip_with_chars_verifier = verifier_for(rstrip_with_chars) - - rstrip_verifier.verify(b' spacious ', expected_result=b' spacious') - rstrip_with_chars_verifier.verify(b'www.example.com', b'cmowz.', expected_result=b'www.example') - - - -def test_split(): - def split(tested: bytes) -> list: - return tested.split() - - def split_with_sep(tested: bytes, sep: bytes) -> list: - return tested.split(sep) - - def split_with_sep_and_count(tested: bytes, sep: bytes, count: int) -> list: - return tested.split(sep, count) - - split_verifier = verifier_for(split) - split_with_sep_verifier = verifier_for(split_with_sep) - split_with_sep_and_count_verifier = verifier_for(split_with_sep_and_count) - - split_verifier.verify(b'123', expected_result=[b'123']) - split_verifier.verify(b'1 2 3', expected_result=[b'1', b'2', b'3']) - split_verifier.verify(b' 1 2 3 ', expected_result=[b'1', b'2', b'3']) - split_verifier.verify(b'1\n2\n3', expected_result=[b'1', b'2', b'3']) - split_verifier.verify(b'1\t2\t3', expected_result=[b'1', b'2', b'3']) - - split_with_sep_verifier.verify(b'1,2,3', b',', expected_result=[b'1', b'2', b'3']) - split_with_sep_verifier.verify(b'1,2,,3,', b',', expected_result=[b'1', b'2', b'', b'3', b'']) - split_with_sep_verifier.verify(b',1,2,,3,', b',', expected_result=[b'', b'1', b'2', b'', b'3', b'']) - - split_with_sep_and_count_verifier.verify(b'1,2,3', b',', 1, expected_result=[b'1', b'2,3']) - split_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 1, expected_result=[b'1', b'2,,3,']) - split_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 2, expected_result=[b'1', b'2', b',3,']) - split_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 3, expected_result=[b'1', b'2', b'', b'3,']) - split_with_sep_and_count_verifier.verify(b'1,2,,3,', b',', 4, expected_result=[b'1', b'2', b'', b'3', b'']) - - -def test_splitlines(): - def splitlines(tested: bytes) -> list: - return tested.splitlines() - - def splitlines_keep_ends(tested: bytes, keep_ends: bool) -> list: - return tested.splitlines(keep_ends) - - splitlines_verifier = verifier_for(splitlines) - splitlines_keep_ends_verifier = verifier_for(splitlines_keep_ends) - - splitlines_verifier.verify(b'ab c\n\nde fg\rkl\r\n', expected_result=[b'ab c', b'', b'de fg', b'kl']) - splitlines_verifier.verify(b'', expected_result=[]) - splitlines_verifier.verify(b'One line\n', expected_result=[b'One line']) - - splitlines_keep_ends_verifier.verify(b'ab c\n\nde fg\rkl\r\n', False, expected_result=[b'ab c', b'', b'de fg', b'kl']) - splitlines_keep_ends_verifier.verify(b'ab c\n\nde fg\rkl\r\n', True, - expected_result=[b'ab c\n', b'\n', b'de fg\r', b'kl\r\n']) - splitlines_keep_ends_verifier.verify(b'', True, expected_result=[]) - splitlines_keep_ends_verifier.verify(b'', False, expected_result=[]) - splitlines_keep_ends_verifier.verify(b'One line\n', True, expected_result=[b'One line\n']) - splitlines_keep_ends_verifier.verify(b'One line\n', False, expected_result=[b'One line']) - - -def test_startswith(): - def startswith(tested: bytes, suffix: bytes) -> bool: - return tested.startswith(suffix) - - def startswith_start(tested: bytes, suffix: bytes, start: int) -> bool: - return tested.startswith(suffix, start) - - def startswith_between(tested: bytes, suffix: bytes, start: int, end: int) -> bool: - return tested.startswith(suffix, start, end) - - startswith_verifier = verifier_for(startswith) - startswith_start_verifier = verifier_for(startswith_start) - startswith_between_verifier = verifier_for(startswith_between) - - startswith_verifier.verify(b'hello world', b'hello', expected_result=True) - startswith_verifier.verify(b'hello world', b'world', expected_result=False) - startswith_verifier.verify(b'hello', b'hello world', expected_result=False) - startswith_verifier.verify(b'hello world', b'hello world', expected_result=True) - - startswith_start_verifier.verify(b'hello world', b'world', 6, expected_result=True) - startswith_start_verifier.verify(b'hello world', b'hello', 6, expected_result=False) - startswith_start_verifier.verify(b'hello', b'hello world', 6, expected_result=False) - startswith_start_verifier.verify(b'hello world', b'hello world', 6, expected_result=False) - - startswith_between_verifier.verify(b'hello world', b'world', 6, 11, expected_result=True) - startswith_between_verifier.verify(b'hello world', b'world', 7, 11, expected_result=False) - startswith_between_verifier.verify(b'hello world', b'hello', 0, 5, expected_result=True) - startswith_between_verifier.verify(b'hello', b'hello world', 0, 5, expected_result=False) - startswith_between_verifier.verify(b'hello world', b'hello world', 5, 11, expected_result=False) - - -def test_strip(): - def strip(tested: bytes) -> bytes: - return tested.strip() - - def strip_with_chars(tested: bytes, chars: bytes) -> bytes: - return tested.strip(chars) - - strip_verifier = verifier_for(strip) - strip_with_chars_verifier = verifier_for(strip_with_chars) - - strip_verifier.verify(b' spacious ', expected_result=b'spacious') - strip_with_chars_verifier.verify(b'www.example.com', b'cmowz.', expected_result=b'example') - - -def test_swapcase(): - def swapcase(tested: bytes) -> bytes: - return tested.swapcase() - - swapcase_verifier = verifier_for(swapcase) - - swapcase_verifier.verify(b'', expected_result=b'') - swapcase_verifier.verify(b'abc', expected_result=b'ABC') - swapcase_verifier.verify(b'ABC', expected_result=b'abc') - swapcase_verifier.verify(b'123', expected_result=b'123') - swapcase_verifier.verify(b'ABC123', expected_result=b'abc123') - swapcase_verifier.verify(b'+', expected_result=b'+') - swapcase_verifier.verify(b'[]', expected_result=b'[]') - swapcase_verifier.verify(b'-', expected_result=b'-') - swapcase_verifier.verify(b'%', expected_result=b'%') - swapcase_verifier.verify('π'.encode(), expected_result='π'.encode()) - swapcase_verifier.verify(b'\n', expected_result=b'\n') - swapcase_verifier.verify(b'\t', expected_result=b'\t') - swapcase_verifier.verify(b' ', expected_result=b' ') - - -def test_title(): - def title(tested: bytes) -> bytes: - return tested.title() - - title_verifier = verifier_for(title) - - title_verifier.verify(b'', expected_result=b'') - title_verifier.verify(b'Hello world', expected_result=b'Hello World') - title_verifier.verify(b"they're bill's friends from the UK", - expected_result=b"They'Re Bill'S Friends From The Uk") - - title_verifier.verify(b'abc', expected_result=b'Abc') - title_verifier.verify(b'ABC', expected_result=b'Abc') - title_verifier.verify(b'123', expected_result=b'123') - title_verifier.verify(b'ABC123', expected_result=b'Abc123') - title_verifier.verify(b'+', expected_result=b'+') - title_verifier.verify(b'[]', expected_result=b'[]') - title_verifier.verify(b'-', expected_result=b'-') - title_verifier.verify(b'%', expected_result=b'%') - title_verifier.verify('π'.encode(), expected_result='π'.encode()) - title_verifier.verify(b'\n', expected_result=b'\n') - title_verifier.verify(b'\t', expected_result=b'\t') - title_verifier.verify(b' ', expected_result=b' ') - - -def test_translate(): - def translate(tested: bytes, mapping: bytes) -> bytes: - return tested.translate(mapping) - - translate_verifier = verifier_for(translate) - - mapping = b''.join([bytes([(i + 1) % 256]) for i in range(256)]) - - translate_verifier.verify(b'hello world', - mapping, expected_result=b'ifmmp!xpsme') - - -def test_upper(): - def upper(tested: bytes) -> bytes: - return tested.upper() - - upper_verifier = verifier_for(upper) - - upper_verifier.verify(b'', expected_result=b'') - upper_verifier.verify(b'abc', expected_result=b'ABC') - upper_verifier.verify(b'ABC', expected_result=b'ABC') - upper_verifier.verify(b'123', expected_result=b'123') - upper_verifier.verify(b'ABC123', expected_result=b'ABC123') - upper_verifier.verify(b'+', expected_result=b'+') - upper_verifier.verify(b'[]', expected_result=b'[]') - upper_verifier.verify(b'-', expected_result=b'-') - upper_verifier.verify(b'%', expected_result=b'%') - upper_verifier.verify('π'.encode(), expected_result='π'.encode()) - upper_verifier.verify(b'\n', expected_result=b'\n') - upper_verifier.verify(b'\t', expected_result=b'\t') - upper_verifier.verify(b' ', expected_result=b' ') - - -def test_zfill(): - def zfill(tested: bytes, padding: int) -> bytes: - return tested.zfill(padding) - - zfill_verifier = verifier_for(zfill) - - zfill_verifier.verify(b'42', 5, expected_result=b'00042') - zfill_verifier.verify(b'-42', 5, expected_result=b'-0042') - zfill_verifier.verify(b'+42', 5, expected_result=b'+0042') - zfill_verifier.verify(b'42', 1, expected_result=b'42') - zfill_verifier.verify(b'-42', 1, expected_result=b'-42') - zfill_verifier.verify(b'+42', 1, expected_result=b'+42') - zfill_verifier.verify(b'abc', 10, expected_result=b'0000000abc') diff --git a/jpyinterpreter/tests/test_classes.py b/jpyinterpreter/tests/test_classes.py deleted file mode 100644 index 1efcf865..00000000 --- a/jpyinterpreter/tests/test_classes.py +++ /dev/null @@ -1,1170 +0,0 @@ -from typing import Type - -import pytest - -from .conftest import verifier_for - - -def test_create_instance(): - class A: - value: int - - def __init__(self, value): - self.value = value - - def __eq__(self, other): - if not isinstance(other, A): - return False - return self.value == other.value - - def create_instance(x: int) -> A: - return A(x) - - verifier = verifier_for(create_instance) - - verifier.verify(1, expected_result=A(1)) - verifier.verify(2, expected_result=A(2)) - verifier.verify(3, expected_result=A(3)) - - -def test_type_coercing(): - class A: - value: float - - def __init__(self, value): - self.value = value - - def __eq__(self, other): - if not isinstance(other, A): - return False - return self.value == other.value - - def create_instance(x: int) -> A: - return A(x) - - verifier = verifier_for(create_instance) - - verifier.verify(1, expected_result=A(1)) - verifier.verify(2, expected_result=A(2)) - verifier.verify(3, expected_result=A(3)) - - -def test_deleted_field(): - class A: - value: int - - def __init__(self, value): - self.value = value - - def my_method(self, param): - return self.value + param - - def my_method(x: A, y: int) -> int: - return x.my_method(y) - - verifier = verifier_for(my_method) - - a = A(1) - verifier.verify(a, 1, expected_result=2) - - del a.value - - verifier.verify(a, 1, expected_error=AttributeError) - - -def test_virtual_method(): - class A: - value: int - - def __init__(self, value): - self.value = value - - def my_method(self, param): - return self.value + param - - def my_method(x: A, y: int) -> int: - return x.my_method(y) - - verifier = verifier_for(my_method) - - verifier.verify(A(1), 1, expected_result=2) - verifier.verify(A(1), 2, expected_result=3) - verifier.verify(A(2), 1, expected_result=3) - verifier.verify(A(2), 2, expected_result=4) - - -def test_static_method(): - class A: - @staticmethod - def my_method(param: int): - return 1 + param - - def instance_my_method(x: A, y: int) -> int: - return x.my_method(y) - - def static_my_method(x: Type[A], y: int) -> int: - return x.my_method(y) - - instance_verifier = verifier_for(instance_my_method) - static_verifier = verifier_for(static_my_method) - - static_verifier.verify(A, 1, expected_result=2) - static_verifier.verify(A, 2, expected_result=3) - instance_verifier.verify(A(), 3, expected_result=4) - instance_verifier.verify(A(), 4, expected_result=5) - - -def test_class_method(): - class A: - @classmethod - def my_method(cls: type, parameter: str): - return cls.__name__ + parameter - - class B(A): - pass - - def instance_my_method(x: A, y: str) -> str: - return x.my_method(y) - - def static_my_method(x: Type[A], y: str) -> str: - return x.my_method(y) - - instance_verifier = verifier_for(instance_my_method) - static_verifier = verifier_for(static_my_method) - - instance_verifier.verify(A(), '1', expected_result='A1') - instance_verifier.verify(B(), '2', expected_result='B2') - static_verifier.verify(A, '3', expected_result='A3') - static_verifier.verify(B, '4', expected_result='B4') - - -def test_override_method(): - class A: - def __init__(self, value): - self.value = value - - def my_method(self): - return self.value - - class B(A): - def my_method(self): - return self.value + 1 - - def my_method(x: A) -> int: - return x.my_method() - - verifier = verifier_for(my_method) - - verifier.verify(A(1), expected_result=1) - verifier.verify(B(1), expected_result=2) - - verifier.verify(A(2), expected_result=2) - verifier.verify(B(2), expected_result=3) - - -def test_simple_super_method(): - class A: - def __init__(self, start): - self.start = start - - def my_method(self, text): - return self.start + text - - class B(A): - def __init__(self, start, end): - super().__init__(start) - self.end = end - - def my_method(self, text): - return super().my_method(text) + self.end - - def my_function(start: str, end: str, text: str) -> str: - return B(start, end).my_method(text) - - verifier = verifier_for(my_function) - - verifier.verify('start', 'end', ' middle ', expected_result='start middle end') - - -def test_chained_super_method(): - class A: - def __init__(self, start): - self.start = start - - def my_method(self, text): - return self.start + text - - class B(A): - def __init__(self, start, end): - super().__init__(start) - self.end = end - - def my_method(self, text): - return super().my_method(text) + self.end - - class C(B): - def __init__(self, start, end): - super().__init__(start, end) - - def my_method(self, text): - return '[' + super().my_method(text) + ']' - - def my_function(start: str, end: str, text: str) -> str: - return C(start, end).my_method(text) - - verifier = verifier_for(my_function) - - verifier.verify('start', 'end', ' middle ', expected_result='[start middle end]') - - -def test_virtual_keyword_arguments(): - class A: - def helper(self, a: int, b: int) -> int: - return a - b - - def my_function(a: int, b: int) -> int: - return A().helper(b=b, a=a) - - verifier = verifier_for(my_function) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - -def test_virtual_default_arguments(): - class A: - def helper(self, a: int, b: int = 1, c: str = '') -> int: - return a - b - len(c) - - def my_function(a: int) -> int: - return A().helper(a) - - verifier = verifier_for(my_function) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - -def test_virtual_vargs(): - class A: - def helper(self, *items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(a: int, b: int, c: int) -> int: - return A().helper(a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - -def test_virtual_kwargs(): - class A: - def helper(self, **kwargs: int) -> frozenset: - return frozenset(kwargs.items()) - - def my_function(a: int, b: int) -> frozenset: - return A().helper(first=a, second=b) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - -def test_virtual_unpack_iterable(): - class A: - def helper(self, *items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(iterable: tuple) -> int: - return A().helper(*iterable) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - -def test_virtual_unpack_keywords(): - class A: - def helper(self, **kwargs: int) -> set: - return set(kwargs.items()) - - def my_function(items: dict) -> set: - return A().helper(**items) - - verifier = verifier_for(my_function) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - -def test_virtual_unpack_iterable_and_keywords(): - class A: - def helper(self, first: int, *positional: int, key: str, **keywords: str): - return first, positional, key, keywords - - def my_function(items, keywords): - return A().helper(*items, **keywords) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - - -def test_virtual_default_with_vargs(): - class A: - def helper(self, *items: int, start: int = 10) -> int: - total = start - for item in items: - total += item - return total - - def my_function(items): - return A().helper(*items) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - -def test_virtual_vargs_with_manatory_args(): - class A: - def helper(self, start: int, *items: int) -> int: - total = start - for item in items: - total += item - return total - - def my_function(a, b, c): - return A().helper(10, a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - -def test_static_keyword_arguments(): - class A: - @staticmethod - def helper(a: int, b: int) -> int: - return a - b - - def my_function(a: int, b: int) -> int: - return A.helper(b=b, a=a) - - def my_function_instance(a: int, b: int) -> int: - return A().helper(b=b, a=a) - - verifier = verifier_for(my_function) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - -def test_static_default_arguments(): - class A: - @staticmethod - def helper(a: int, b: int = 1, c: str = '') -> int: - return a - b - len(c) - - def my_function(a: int) -> int: - return A.helper(a) - - def my_function_instance(a: int) -> int: - return A().helper(a) - - verifier = verifier_for(my_function) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - -def test_static_vargs(): - class A: - @staticmethod - def helper(*items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(a: int, b: int, c: int) -> int: - return A.helper(a, b, c) - - def my_function_instance(a: int, b: int, c: int) -> int: - return A().helper(a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - -def test_static_kwargs(): - class A: - @staticmethod - def helper(**kwargs: int) -> frozenset: - return frozenset(kwargs.items()) - - def my_function(a: int, b: int) -> frozenset: - return A.helper(first=a, second=b) - - def my_function_instance(a: int, b: int) -> frozenset: - return A().helper(first=a, second=b) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - -def test_static_unpack_iterable(): - class A: - @staticmethod - def helper(*items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(iterable: tuple) -> int: - return A.helper(*iterable) - - def my_function_instance(iterable: tuple) -> int: - return A().helper(*iterable) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - -def test_static_unpack_keywords(): - class A: - @staticmethod - def helper(**kwargs: int) -> set: - return set(kwargs.items()) - - def my_function(items: dict) -> set: - return A.helper(**items) - - def my_function_instance(items: dict) -> set: - return A().helper(**items) - - verifier = verifier_for(my_function) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - verifier = verifier_for(my_function_instance) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - -def test_static_unpack_iterable_and_keywords(): - class A: - @staticmethod - def helper(first: int, *positional: int, key: str, **keywords: str): - return first, positional, key, keywords - - def my_function(items, keywords): - return A.helper(*items, **keywords) - - def my_function_instance(items, keywords): - return A().helper(*items, **keywords) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - - -def test_static_default_with_vargs(): - class A: - @staticmethod - def helper(*items: int, start: int = 10) -> int: - total = start - for item in items: - total += item - return total - - def my_function(items): - return A.helper(*items) - - def my_function_instance(items): - return A().helper(*items) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - -def test_static_vargs_with_manatory_args(): - class A: - @staticmethod - def helper(start: int, *items: int) -> int: - total = start - for item in items: - total += item - return total - - def my_function(a, b, c): - return A.helper(10, a, b, c) - - def my_function_instance(a, b, c): - return A().helper(10, a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - -def test_class_keyword_arguments(): - class A: - @classmethod - def helper(cls: type, a: int, b: int) -> int: - return a - b - - def my_function(a: int, b: int) -> int: - return A.helper(b=b, a=a) - - def my_function_instance(a: int, b: int) -> int: - return A().helper(b=b, a=a) - - verifier = verifier_for(my_function) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - -def test_class_default_arguments(): - class A: - @classmethod - def helper(cls: type, a: int, b: int = 1, c: str = '') -> int: - return a - b - len(c) - - def my_function(a: int) -> int: - return A.helper(a) - - def my_function_instance(a: int) -> int: - return A().helper(a) - - verifier = verifier_for(my_function) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - -def test_class_vargs(): - class A: - @classmethod - def helper(cls: type, *items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(a: int, b: int, c: int) -> int: - return A.helper(a, b, c) - - def my_function_instance(a: int, b: int, c: int) -> int: - return A().helper(a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - -def test_class_kwargs(): - class A: - @classmethod - def helper(cls: type, **kwargs: int) -> frozenset: - return frozenset(kwargs.items()) - - def my_function(a: int, b: int) -> frozenset: - return A.helper(first=a, second=b) - - def my_function_instance(a: int, b: int) -> frozenset: - return A().helper(first=a, second=b) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - -def test_class_unpack_iterable(): - class A: - @classmethod - def helper(cls: type, *items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(iterable: tuple) -> int: - return A.helper(*iterable) - - def my_function_instance(iterable: tuple) -> int: - return A().helper(*iterable) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - -def test_class_unpack_keywords(): - class A: - @classmethod - def helper(cls: type, **kwargs: int) -> set: - return set(kwargs.items()) - - def my_function(items: dict) -> set: - return A.helper(**items) - - def my_function_instance(items: dict) -> set: - return A().helper(**items) - - verifier = verifier_for(my_function) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - verifier = verifier_for(my_function_instance) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - -def test_class_unpack_iterable_and_keywords(): - class A: - @classmethod - def helper(cls: type, first: int, *positional: int, key: str, **keywords: str): - return first, positional, key, keywords - - def my_function(items, keywords): - return A.helper(*items, **keywords) - - def my_function_instance(items, keywords): - return A().helper(*items, **keywords) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - - -def test_class_default_with_vargs(): - class A: - @classmethod - def helper(cls: type, *items: int, start: int = 10) -> int: - total = start - for item in items: - total += item - return total - - def my_function(items): - return A.helper(*items) - - def my_function_instance(items): - return A().helper(*items) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - verifier = verifier_for(my_function_instance) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - -def test_class_vargs_with_manatory_args(): - class A: - @classmethod - def helper(cls: type, start: int, *items: int) -> int: - total = start - for item in items: - total += item - return total - - def my_function(a, b, c): - return A.helper(10, a, b, c) - - def my_function_instance(a, b, c): - return A().helper(10, a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - verifier = verifier_for(my_function_instance) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - -def test_enum_translate_to_class(): - from enum import Enum - from jpyinterpreter import translate_python_class_to_java_class - from ai.timefold.jpyinterpreter.types.wrappers import CPythonType - - class Color(Enum): - RED = 'RED' - GREEN = 'GREEN' - BLUE = 'BLUE' - - translated_class = translate_python_class_to_java_class(Color) - assert not isinstance(translated_class, CPythonType) - - -def test_class_annotations(): - from typing import Annotated - from java.lang import Deprecated - from java.lang.annotation import Target, ElementType - from jpyinterpreter import add_class_annotation, JavaAnnotation, translate_python_class_to_java_class - - @add_class_annotation(Deprecated, - forRemoval=True, - since='0.0.0') - class A: - my_field: Annotated[int, JavaAnnotation(Deprecated, { - 'forRemoval': True, - 'since': '1.0.0' - }), 'extra metadata', - JavaAnnotation(Target, { - 'value': [ElementType.CONSTRUCTOR, ElementType.METHOD] - })] - - def my_method(self) -> Annotated[str, 'extra', JavaAnnotation(Deprecated, { - 'forRemoval': False, - 'since': '2.0.0' - })]: - return 'hello world' - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - annotations = translated_class.getAnnotations() - assert len(annotations) == 1 - assert isinstance(annotations[0], Deprecated) - assert annotations[0].forRemoval() - assert annotations[0].since() == '0.0.0' - - annotations = translated_class.getMethod('getMy_field').getAnnotations() - assert len(annotations) == 2 - assert isinstance(annotations[0], Deprecated) - assert annotations[0].forRemoval() - assert annotations[0].since() == '1.0.0' - assert isinstance(annotations[1], Target) - assert list(annotations[1].value()) == [ElementType.CONSTRUCTOR, ElementType.METHOD] - - annotations = translated_class.getMethod('$method$my_method').getAnnotations() - assert len(annotations) == 1 - assert isinstance(annotations[0], Deprecated) - assert annotations[0].forRemoval() is False - assert annotations[0].since() == '2.0.0' - - -def test_extra_attributes(): - from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object - - class A: - pass - - a = A() - a.name = 'Name' - - converted_a = convert_to_java_python_like_object(a) - - assert getattr(converted_a, '$getAttributeOrNull')('name').value == 'Name' - - unwrapped_a = unwrap_python_like_object(converted_a) - - assert unwrapped_a.name == 'Name' - - -def function_attribute_function(): - return 10 - - -def test_function_attributes(): - from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object - - class A: - dispatch = { - 'my_function': function_attribute_function, - } - - def run(): - return A.dispatch['my_function']() - - a = A() - - converted_a = convert_to_java_python_like_object(a) - unwrapped_a = unwrap_python_like_object(converted_a) - - assert unwrapped_a.dispatch['my_function'] is function_attribute_function - - verifier = verifier_for(run) - verifier.verify(expected_result=10) - - -def test_java_class_as_field_type(): - from ai.timefold.jpyinterpreter import TypeHint - from jpyinterpreter import translate_python_class_to_java_class - - class A: - my_field: TypeHint - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - field_type = translated_class.getField('my_field').getType() - assert field_type.getName() == TypeHint.class_.getName() - - -def test_generic_field_type(): - from typing import List - from ai.timefold.jpyinterpreter.types import PythonString - from jpyinterpreter import translate_python_class_to_java_class - - class A: - my_field: List[str] - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - field_type = translated_class.getField('my_field').getGenericType() - assert field_type.getActualTypeArguments()[0].getName() == PythonString.class_.getName() - - -def test_getter_type(): - from typing import Optional, Union - from ai.timefold.jpyinterpreter.types import PythonString, PythonBytes - from ai.timefold.jpyinterpreter.types.numeric import PythonInteger - from jpyinterpreter import translate_python_class_to_java_class - - class A: - str_field: Optional[str] - int_field: Union[int, None] - bytes_field: bytes | None - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - str_field_getter_type = translated_class.getMethod('getStr_field').getReturnType() - assert str_field_getter_type == PythonString.class_ - - int_field_getter_type = translated_class.getMethod('getInt_field').getReturnType() - assert int_field_getter_type == PythonInteger.class_ - - bytes_field_getter_type = translated_class.getMethod('getBytes_field').getReturnType() - assert bytes_field_getter_type == PythonBytes.class_ - - -def test_marker_interface(): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - from jpyinterpreter import translate_python_class_to_java_class, add_java_interface - - @add_java_interface(OpaquePythonReference) - class A: - pass - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - assert OpaquePythonReference.class_.isAssignableFrom(translated_class) - - -def test_marker_interface_string(): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - from jpyinterpreter import translate_python_class_to_java_class, add_java_interface - - @add_java_interface('ai.timefold.jpyinterpreter.types.wrappers.OpaquePythonReference') - class A: - pass - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - assert OpaquePythonReference.class_.isAssignableFrom(translated_class) - - -def test_functional_interface(): - from java.util.function import ToIntFunction - from jpyinterpreter import translate_python_class_to_java_class, add_java_interface - from ai.timefold.jpyinterpreter.types import PythonNone - - @add_java_interface(ToIntFunction) - class A: - def applyAsInt(self, argument: int): - return argument + 1 - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - assert ToIntFunction.class_.isAssignableFrom(translated_class) - java_object = translated_class.getConstructor().newInstance() - assert java_object.applyAsInt(1) == 2 - - -def test_python_java_type_mapping(): - from java.lang import String - from jpyinterpreter import (translate_python_class_to_java_class, - add_python_java_type_mapping, unwrap_python_like_object) - from jpype import JImplements, JOverride - from dataclasses import dataclass - - @dataclass - class PythonClass: - data: str - - python_class_type = translate_python_class_to_java_class(PythonClass) - - @JImplements('ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping') - class MyMapping: - @JOverride - def getPythonType(self): - return python_class_type - - @JOverride - def getJavaType(self): - return String.class_ - - @JOverride - def toPythonObject(self, java_object): - from ai.timefold.jpyinterpreter.types import PythonString - instance = python_class_type.getJavaClass().getConstructor().newInstance() - instance.data = PythonString.valueOf(java_object) - return instance - - @JOverride - def toJavaObject(self, python_object): - return python_object.data.getValue() - - add_python_java_type_mapping(MyMapping()) - - @dataclass - class A: - data: PythonClass | None - - translated_class = translate_python_class_to_java_class(A).getJavaClass() - assert translated_class.getMethod('getData').getReturnType() == String.class_ - assert translated_class.getMethod('setData', String.class_) is not None - - java_object = translated_class.getConstructor().newInstance() - java_object.setData('test') - assert unwrap_python_like_object(translated_class.getField('data').get(java_object)) == PythonClass('test') - assert java_object.getData() == 'test' - - java_object.setData(None) - assert unwrap_python_like_object(translated_class.getField('data').get(java_object)) is None - assert java_object.getData() is None - - -def test_class_properties(): - from jpyinterpreter import translate_python_class_to_java_class, unwrap_python_like_object - from dataclasses import dataclass - from java.lang import NoSuchFieldException - from ai.timefold.jpyinterpreter.types import PythonString - - @dataclass - class Car: - name: str - - @property - def speed(self): - return 100 - - def is_fast(self) -> bool: - return self.speed > 50 - - translated_class = translate_python_class_to_java_class(Car) - java_class = translated_class.getJavaClass() - - with pytest.raises(NoSuchFieldException): - java_class.getField('speed') - - instance = java_class.getConstructor().newInstance() - instance.name = PythonString.valueOf('Car') - assert java_class.getMethod('$method$is_fast').invoke(instance) - assert unwrap_python_like_object(instance) == Car('Car') diff --git a/jpyinterpreter/tests/test_comprehensions.py b/jpyinterpreter/tests/test_comprehensions.py deleted file mode 100644 index c37a69a8..00000000 --- a/jpyinterpreter/tests/test_comprehensions.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Callable, Iterable -from .conftest import verifier_for - - -def test_list_comprehensions(): - def my_function(predicate: Callable, iterable: Iterable) -> list: - return [x for x in iterable if predicate(x)] - - def my_predicate(x): - return x % 2 == 0 - - verifier = verifier_for(my_function) - verifier.verify(my_predicate, [1, 2, 3, 4], expected_result=[2, 4]) - - -def test_set_comprehensions(): - def my_function(predicate: Callable, iterable: Iterable) -> tuple: - a = {x % 4 for x in iterable if predicate(x)} - return frozenset(a), len(a) - - def my_predicate(x): - return x % 2 == 0 - - verifier = verifier_for(my_function) - verifier.verify(my_predicate, [1, 2, 3, 4, 5, 6], expected_result=(frozenset({0, 2}), 2)) - - -def test_dict_comprehensions(): - def my_function(predicate: Callable, iterable: Iterable) -> dict: - return {str(x): x for x in iterable if predicate(x)} - - def my_predicate(x): - return x % 2 == 0 - - verifier = verifier_for(my_function) - verifier.verify(my_predicate, [1, 2, 3, 4], expected_result={'2': 2, '4': 4}) - - -def test_cell_variable_in_comprehensions(): - def my_function(items: list) -> bool: - return any(items[index] == index + 1 for index in range(len(items))) - - verifier = verifier_for(my_function) - verifier.verify([0, 1, 2], expected_result=False) - verifier.verify([0, 1, 3], expected_result=True) diff --git a/jpyinterpreter/tests/test_decimal.py b/jpyinterpreter/tests/test_decimal.py deleted file mode 100644 index d513c5fb..00000000 --- a/jpyinterpreter/tests/test_decimal.py +++ /dev/null @@ -1,782 +0,0 @@ -from .conftest import verifier_for -from decimal import Decimal -from typing import Callable - - -def around(a: Decimal) -> Callable[[Decimal], bool]: - def predicate(b: Decimal) -> bool: - return abs(a - b) < 0.00001 - return predicate - - -def test_add(): - def decimal_add(a: Decimal, b: Decimal) -> Decimal: - return a + b - - def int_add(a: Decimal, b: int) -> Decimal: - return a + b - - decimal_add_verifier = verifier_for(decimal_add) - int_add_verifier = verifier_for(int_add) - - decimal_add_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(2)) - decimal_add_verifier.verify(Decimal(1), Decimal(-1), expected_result=Decimal(0)) - decimal_add_verifier.verify(Decimal(-1), Decimal(1), expected_result=Decimal(0)) - decimal_add_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(1)) - decimal_add_verifier.verify(Decimal('1.5'), Decimal('1.5'), expected_result=Decimal('3.0')) - - int_add_verifier.verify(Decimal(1), 1, expected_result=Decimal(2)) - int_add_verifier.verify(Decimal(1), -1, expected_result=Decimal(0)) - int_add_verifier.verify(Decimal(-1), 1, expected_result=Decimal(0)) - int_add_verifier.verify(Decimal(0), 1, expected_result=Decimal(1)) - int_add_verifier.verify(Decimal('1.5'), 1, expected_result=Decimal('2.5')) - - -def test_sub(): - def decimal_sub(a: Decimal, b: Decimal) -> Decimal: - return a - b - - def int_sub(a: Decimal, b: int) -> Decimal: - return a - b - - decimal_sub_verifier = verifier_for(decimal_sub) - int_sub_verifier = verifier_for(int_sub) - - decimal_sub_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(0)) - decimal_sub_verifier.verify(Decimal(1), Decimal(-1), expected_result=Decimal(2)) - decimal_sub_verifier.verify(Decimal(-1), Decimal(1), expected_result=Decimal(-2)) - decimal_sub_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(-1)) - decimal_sub_verifier.verify(Decimal('1.5'), Decimal('1.5'), expected_result=Decimal(0)) - - int_sub_verifier.verify(Decimal(1), 1, expected_result=Decimal(0)) - int_sub_verifier.verify(Decimal(1), -1, expected_result=Decimal(2)) - int_sub_verifier.verify(Decimal(-1), 1, expected_result=Decimal(-2)) - int_sub_verifier.verify(Decimal(0), 1, expected_result=Decimal(-1)) - int_sub_verifier.verify(Decimal('1.5'), 1, expected_result=Decimal('0.5')) - - -def test_multiply(): - def decimal_multiply(a: Decimal, b: Decimal) -> Decimal: - return a * b - - def int_multiply(a: Decimal, b: int) -> Decimal: - return a * b - - decimal_multiply_verifier = verifier_for(decimal_multiply) - int_multiply_verifier = verifier_for(int_multiply) - - decimal_multiply_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(1)) - decimal_multiply_verifier.verify(Decimal(1), Decimal(-1), expected_result=Decimal(-1)) - decimal_multiply_verifier.verify(Decimal(-1), Decimal(1), expected_result=Decimal(-1)) - decimal_multiply_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(0)) - decimal_multiply_verifier.verify(Decimal('1.5'), Decimal('1.5'), expected_result=Decimal('2.25')) - - int_multiply_verifier.verify(Decimal(1), 1, expected_result=Decimal(1)) - int_multiply_verifier.verify(Decimal(1), -1, expected_result=Decimal(-1)) - int_multiply_verifier.verify(Decimal(-1), 1, expected_result=Decimal(-1)) - int_multiply_verifier.verify(Decimal(0), 1, expected_result=Decimal(0)) - int_multiply_verifier.verify(Decimal('1.5'), 2, expected_result=Decimal('3.0')) - - -def test_truediv(): - def decimal_truediv(a: Decimal, b: Decimal) -> Decimal: - return a / b - - def int_truediv(a: Decimal, b: int) -> Decimal: - return a / b - - decimal_truediv_verifier = verifier_for(decimal_truediv) - int_truediv_verifier = verifier_for(int_truediv) - - decimal_truediv_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(1)) - decimal_truediv_verifier.verify(Decimal(1), Decimal(-1), expected_result=Decimal(-1)) - decimal_truediv_verifier.verify(Decimal(-1), Decimal(1), expected_result=Decimal(-1)) - decimal_truediv_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(0)) - decimal_truediv_verifier.verify(Decimal(3), Decimal(2), expected_result=Decimal('1.5')) - - int_truediv_verifier.verify(Decimal(1), 1, expected_result=Decimal(1)) - int_truediv_verifier.verify(Decimal(1), -1, expected_result=Decimal(-1)) - int_truediv_verifier.verify(Decimal(-1), 1, expected_result=Decimal(-1)) - int_truediv_verifier.verify(Decimal(0), 1, expected_result=Decimal(0)) - int_truediv_verifier.verify(Decimal(3), 2, expected_result=Decimal('1.5')) - - -def test_floordiv(): - def decimal_floordiv(a: Decimal, b: Decimal) -> Decimal: - return a // b - - def int_floordiv(a: Decimal, b: int) -> Decimal: - return a // b - - decimal_floordiv_verifier = verifier_for(decimal_floordiv) - int_floordiv_verifier = verifier_for(int_floordiv) - - decimal_floordiv_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(1)) - decimal_floordiv_verifier.verify(Decimal(1), Decimal(-1), expected_result=Decimal(-1)) - decimal_floordiv_verifier.verify(Decimal(-1), Decimal(1), expected_result=Decimal(-1)) - decimal_floordiv_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(0)) - decimal_floordiv_verifier.verify(Decimal(-7), Decimal(4), expected_result=Decimal('-1')) - - int_floordiv_verifier.verify(Decimal(1), 1, expected_result=Decimal(1)) - int_floordiv_verifier.verify(Decimal(1), -1, expected_result=Decimal(-1)) - int_floordiv_verifier.verify(Decimal(-1), 1, expected_result=Decimal(-1)) - int_floordiv_verifier.verify(Decimal(0), 1, expected_result=Decimal(0)) - int_floordiv_verifier.verify(Decimal(3), 2, expected_result=Decimal(1)) - - -def test_mod(): - def decimal_mod(a: Decimal, b: Decimal) -> Decimal: - return a % b - - def int_mod(a: Decimal, b: int) -> Decimal: - return a % b - - decimal_mod_verifier = verifier_for(decimal_mod) - int_mod_verifier = verifier_for(int_mod) - - decimal_mod_verifier.verify(Decimal(-7), Decimal(4), expected_result=Decimal(-3)) - decimal_mod_verifier.verify(Decimal(0), Decimal(1), expected_result=Decimal(0)) - decimal_mod_verifier.verify(Decimal(3), Decimal(2), expected_result=Decimal('1')) - decimal_mod_verifier.verify(Decimal('3.5'), Decimal(2), expected_result=Decimal('1.5')) - - int_mod_verifier.verify(Decimal(1), 1, expected_result=Decimal(0)) - int_mod_verifier.verify(Decimal('3.5'), 2, expected_result=Decimal('1.5')) - int_mod_verifier.verify(Decimal(3), 2, expected_result=Decimal(1)) - - -def test_negate(): - def negate(x: Decimal) -> Decimal: - return -x - - negate_verifier = verifier_for(negate) - - negate_verifier.verify(Decimal(1), expected_result=Decimal(-1)) - negate_verifier.verify(Decimal(-1), expected_result=Decimal(1)) - - -def test_pos(): - def pos(x: Decimal) -> Decimal: - return +x - - pos_verifier = verifier_for(pos) - - pos_verifier.verify(Decimal(1), expected_result=Decimal(1)) - pos_verifier.verify(Decimal(-1), expected_result=Decimal(-1)) - - -def test_abs(): - def decimal_abs(x: Decimal) -> Decimal: - return abs(x) - - abs_verifier = verifier_for(decimal_abs) - - abs_verifier.verify(Decimal(1), expected_result=Decimal(1)) - abs_verifier.verify(Decimal(-1), expected_result=Decimal(1)) - - -def test_pow(): - def decimal_pow(a: Decimal, b: Decimal) -> Decimal: - return a ** b - - def int_pow(a: Decimal, b: int) -> Decimal: - return a ** b - - decimal_pow_verifier = verifier_for(decimal_pow) - int_pow_verifier = verifier_for(int_pow) - - decimal_pow_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(1)) - decimal_pow_verifier.verify(Decimal(2), Decimal(2), expected_result=Decimal(4)) - decimal_pow_verifier.verify(Decimal(3), Decimal(2), expected_result=Decimal(9)) - decimal_pow_verifier.verify(Decimal(2), Decimal(3), expected_result=Decimal(8)) - decimal_pow_verifier.verify(Decimal(2), Decimal(-1), expected_result=Decimal(0.5)) - decimal_pow_verifier.verify(Decimal(4), Decimal('0.5'), expected_result=Decimal(2)) - - int_pow_verifier.verify(Decimal(1), 2, expected_result=Decimal(1)) - int_pow_verifier.verify(Decimal(2), 2, expected_result=Decimal(4)) - int_pow_verifier.verify(Decimal(3), 2, expected_result=Decimal(9)) - int_pow_verifier.verify(Decimal(2), 3, expected_result=Decimal(8)) - int_pow_verifier.verify(Decimal(2), -1, expected_result=Decimal(0.5)) - - -def test_comparisons(): - def lt(a: Decimal, b: Decimal) -> bool: - return a < b - - def gt(a: Decimal, b: Decimal) -> bool: - return a > b - - def le(a: Decimal, b: Decimal) -> bool: - return a <= b - - def ge(a: Decimal, b: Decimal) -> bool: - return a >= b - - def eq(a: Decimal, b: Decimal) -> bool: - return a == b - - def ne(a: Decimal, b: Decimal) -> bool: - return a != b - - lt_verifier = verifier_for(lt) - gt_verifier = verifier_for(gt) - le_verifier = verifier_for(le) - ge_verifier = verifier_for(ge) - eq_verifier = verifier_for(eq) - ne_verifier = verifier_for(ne) - - lt_verifier.verify(Decimal(1), Decimal(1), expected_result=False) - gt_verifier.verify(Decimal(1), Decimal(1), expected_result=False) - le_verifier.verify(Decimal(1), Decimal(1), expected_result=True) - ge_verifier.verify(Decimal(1), Decimal(1), expected_result=True) - eq_verifier.verify(Decimal(1), Decimal(1), expected_result=True) - ne_verifier.verify(Decimal(1), Decimal(1), expected_result=False) - - lt_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=False) - gt_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=False) - le_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=True) - ge_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=True) - eq_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=True) - ne_verifier.verify(Decimal(1), Decimal('1.0'), expected_result=False) - - lt_verifier.verify(Decimal(1), Decimal(2), expected_result=True) - gt_verifier.verify(Decimal(1), Decimal(2), expected_result=False) - le_verifier.verify(Decimal(1), Decimal(2), expected_result=True) - ge_verifier.verify(Decimal(1), Decimal(2), expected_result=False) - eq_verifier.verify(Decimal(1), Decimal(2), expected_result=False) - ne_verifier.verify(Decimal(1), Decimal(2), expected_result=True) - - lt_verifier.verify(Decimal(2), Decimal(1), expected_result=False) - gt_verifier.verify(Decimal(2), Decimal(1), expected_result=True) - le_verifier.verify(Decimal(2), Decimal(1), expected_result=False) - ge_verifier.verify(Decimal(2), Decimal(1), expected_result=True) - eq_verifier.verify(Decimal(2), Decimal(1), expected_result=False) - ne_verifier.verify(Decimal(2), Decimal(1), expected_result=True) - - -def test_hash(): - def decimal_hash(a: Decimal) -> int: - return hash(a) - - hash_verifier = verifier_for(decimal_hash) - hash_verifier.verify(Decimal(1), expected_result=hash(Decimal(1))) - hash_verifier.verify(Decimal('1.5'), expected_result=hash(Decimal('1.5'))) - - -def test_round(): - def decimal_round(a: Decimal) -> int: - return round(a) - - def decimal_round_with_digits(a: Decimal, digits: int) -> Decimal: - return round(a, digits) - - decimal_round_verifier = verifier_for(decimal_round) - decimal_round_with_digits_verifier = verifier_for(decimal_round_with_digits) - - decimal_round_verifier.verify(Decimal('1.2'), expected_result=1) - decimal_round_verifier.verify(Decimal('1.5'), expected_result=2) - decimal_round_verifier.verify(Decimal('1.7'), expected_result=2) - decimal_round_verifier.verify(Decimal('2.5'), expected_result=2) - - decimal_round_with_digits_verifier.verify(Decimal('13.22'), 1, expected_result=Decimal('13.2')) - decimal_round_with_digits_verifier.verify(Decimal('13.22'), 2, expected_result=Decimal('13.22')) - decimal_round_with_digits_verifier.verify(Decimal('13.27'), 1, expected_result=Decimal('13.3')) - decimal_round_with_digits_verifier.verify(Decimal('13.25'), 1, expected_result=Decimal('13.2')) - - -def test_adjusted(): - def adjusted(a: Decimal) -> int: - return a.adjusted() - - adjusted_verifier = verifier_for(adjusted) - adjusted_verifier.verify(Decimal(100), expected_result=2) - adjusted_verifier.verify(Decimal('0.001'), expected_result=-3) - - -def test_as_integer_ratio(): - def as_integer_ratio(a: Decimal) -> tuple[int, int]: - return a.as_integer_ratio() - - adjusted_verifier = verifier_for(as_integer_ratio) - adjusted_verifier.verify(Decimal(100), expected_result=(100, 1)) - adjusted_verifier.verify(Decimal('-3.14'), expected_result=(-157, 50)) - - -# TODO: Make as_tuple use NamedTuple -def test_as_tuple(): - def as_tuple(a: Decimal) -> tuple[int, tuple[int,...], int]: - return a.as_tuple() - - def matches_tuple(t: tuple[int, tuple[int,...], int]) -> Callable[[tuple[int, tuple[int,...], int]], bool]: - def predicate(tested: tuple[int, tuple[int,...], int]) -> bool: - return t == tested - - return predicate - - as_tuple_verifier = verifier_for(as_tuple) - as_tuple_verifier.verify_property(Decimal(100), predicate=matches_tuple((0, (1, 0, 0), 0))) - as_tuple_verifier.verify_property(Decimal(-100), predicate=matches_tuple((1, (1, 0, 0), 0))) - as_tuple_verifier.verify_property(Decimal('123.45'), predicate=matches_tuple((0, (1, 2, 3, 4, 5), -2))) - - -def test_canonical(): - def canonical(a: Decimal) -> Decimal: - return a.canonical() - - canonical_verifier = verifier_for(canonical) - canonical_verifier.verify(Decimal(100), expected_result=Decimal(100)) - - -def test_compare(): - def compare(a: Decimal, b: Decimal) -> Decimal: - return a.compare(b) - - compare_verifier = verifier_for(compare) - compare_verifier.verify(Decimal(-5), Decimal(5), expected_result=Decimal(-1)) - compare_verifier.verify(Decimal(5), Decimal(-5), expected_result=Decimal(1)) - compare_verifier.verify(Decimal(5), Decimal(5), expected_result=Decimal(0)) - - -def test_compare_signal(): - def compare_signal(a: Decimal, b: Decimal) -> Decimal: - return a.compare_signal(b) - - compare_signal_verifier = verifier_for(compare_signal) - compare_signal_verifier.verify(Decimal(-5), Decimal(5), expected_result=Decimal(-1)) - compare_signal_verifier.verify(Decimal(5), Decimal(-5), expected_result=Decimal(1)) - compare_signal_verifier.verify(Decimal(5), Decimal(5), expected_result=Decimal(0)) - - -def test_compare_total(): - def compare_total(a: Decimal, b: Decimal) -> Decimal: - return a.compare_total(b) - - compare_total_verifier = verifier_for(compare_total) - compare_total_verifier.verify(Decimal(-5), Decimal(5), expected_result=Decimal(-1)) - compare_total_verifier.verify(Decimal(5), Decimal(-5), expected_result=Decimal(1)) - compare_total_verifier.verify(Decimal(5), Decimal(5), expected_result=Decimal(0)) - compare_total_verifier.verify(Decimal('12.0'), Decimal('12'), expected_result=Decimal(-1)) - compare_total_verifier.verify(Decimal('12'), Decimal('12.0'), expected_result=Decimal(1)) - - -def test_compare_total_mag(): - def compare_total_mag(a: Decimal, b: Decimal) -> Decimal: - return a.compare_total_mag(b) - - compare_total_mag_verifier = verifier_for(compare_total_mag) - compare_total_mag_verifier.verify(Decimal(3), Decimal(5), expected_result=Decimal(-1)) - compare_total_mag_verifier.verify(Decimal(-7), Decimal(5), expected_result=Decimal(1)) - compare_total_mag_verifier.verify(Decimal(-5), Decimal(5), expected_result=Decimal(0)) - compare_total_mag_verifier.verify(Decimal(5), Decimal(-5), expected_result=Decimal(0)) - compare_total_mag_verifier.verify(Decimal(5), Decimal(5), expected_result=Decimal(0)) - compare_total_mag_verifier.verify(Decimal('12.0'), Decimal('12'), expected_result=Decimal(-1)) - compare_total_mag_verifier.verify(Decimal('12'), Decimal('12.0'), expected_result=Decimal(1)) - compare_total_mag_verifier.verify(Decimal('12.0'), Decimal('-12'), expected_result=Decimal(-1)) - compare_total_mag_verifier.verify(Decimal('-12'), Decimal('12.0'), expected_result=Decimal(1)) - - -def test_conjugate(): - def conjugate(a: Decimal) -> Decimal: - return a.conjugate() - - conjugate_verifier = verifier_for(conjugate) - conjugate_verifier.verify(Decimal(10), expected_result=Decimal(10)) - - -def test_copy_abs(): - def copy_abs(a: Decimal) -> Decimal: - return a.copy_abs() - - copy_abs_verifier = verifier_for(copy_abs) - copy_abs_verifier.verify(Decimal(10), expected_result=Decimal(10)) - copy_abs_verifier.verify(Decimal(-10), expected_result=Decimal(10)) - - -def test_copy_negate(): - def copy_negate(a: Decimal) -> Decimal: - return a.copy_negate() - - copy_negate_verifier = verifier_for(copy_negate) - copy_negate_verifier.verify(Decimal(10), expected_result=Decimal(-10)) - copy_negate_verifier.verify(Decimal(-10), expected_result=Decimal(10)) - - -def test_copy_sign(): - def copy_sign(a: Decimal, b: Decimal) -> Decimal: - return a.copy_sign(b) - - copy_sign_verifier = verifier_for(copy_sign) - copy_sign_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(1)) - copy_sign_verifier.verify(Decimal('2.3'), Decimal('-1.5'), expected_result=Decimal('-2.3')) - copy_sign_verifier.verify(Decimal('-1.5'), Decimal('2.3'), expected_result=Decimal('1.5')) - - -def test_exp(): - def exp(a: Decimal) -> Decimal: - return a.exp() - - exp_verifier = verifier_for(exp) - exp_verifier.verify(Decimal(1), expected_result=Decimal('2.718281828459045235360287471')) - exp_verifier.verify(Decimal(321), expected_result=Decimal('2.561702493119680037517373933E+139')) - - -def test_fma(): - def decimal_decimal_fma(a: Decimal, b: Decimal, c: Decimal) -> Decimal: - return a.fma(b, c) - - def int_decimal_fma(a: Decimal, b: int, c: Decimal) -> Decimal: - return a.fma(b, c) - - def decimal_int_fma(a: Decimal, b: Decimal, c: int) -> Decimal: - return a.fma(b, c) - - def int_int_fma(a: Decimal, b: int, c: int) -> Decimal: - return a.fma(b, c) - - fma_decimal_decimal_verifier = verifier_for(decimal_decimal_fma) - fma_int_decimal_verifier = verifier_for(int_decimal_fma) - fma_decimal_int_decimal_verifier = verifier_for(decimal_int_fma) - fma_int_int_decimal_verifier = verifier_for(int_int_fma) - - fma_decimal_decimal_verifier.verify(Decimal(2), Decimal(3), Decimal(5), expected_result=Decimal(11)) - fma_int_decimal_verifier.verify(Decimal(2), 3, Decimal(5), expected_result=Decimal(11)) - fma_decimal_int_decimal_verifier.verify(Decimal(2), Decimal(3), 5, expected_result=Decimal(11)) - fma_int_int_decimal_verifier.verify(Decimal(2), 3, 5, expected_result=Decimal(11)) - - -def test_is_canonical(): - def is_canonical(a: Decimal) -> bool: - return a.is_canonical() - - is_canonical_verifier = verifier_for(is_canonical) - is_canonical_verifier.verify(Decimal(10), expected_result=True) - - -def test_is_finite(): - def is_finite(a: Decimal) -> bool: - return a.is_finite() - - is_finite_verifier = verifier_for(is_finite) - is_finite_verifier.verify(Decimal(10), expected_result=True) - - -def test_is_infinite(): - def is_infinite(a: Decimal) -> bool: - return a.is_infinite() - - is_infinite_verifier = verifier_for(is_infinite) - is_infinite_verifier.verify(Decimal(10), expected_result=False) - - -def test_is_nan(): - def is_nan(a: Decimal) -> bool: - return a.is_nan() - - is_nan_verifier = verifier_for(is_nan) - is_nan_verifier.verify(Decimal(10), expected_result=False) - - -def test_is_normal(): - def is_normal(a: Decimal) -> bool: - return a.is_normal() - - is_normal_verifier = verifier_for(is_normal) - is_normal_verifier.verify(Decimal(10), expected_result=True) - - -def test_is_qnan(): - def is_qnan(a: Decimal) -> bool: - return a.is_qnan() - - is_qnan_verifier = verifier_for(is_qnan) - is_qnan_verifier.verify(Decimal(10), expected_result=False) - - -def test_is_signed(): - def is_signed(a: Decimal) -> bool: - return a.is_signed() - - is_signed_verifier = verifier_for(is_signed) - is_signed_verifier.verify(Decimal(10), expected_result=False) - is_signed_verifier.verify(Decimal(0), expected_result=False) - is_signed_verifier.verify(Decimal(-10), expected_result=True) - - -def test_is_snan(): - def is_snan(a: Decimal) -> bool: - return a.is_snan() - - is_snan_verifier = verifier_for(is_snan) - is_snan_verifier.verify(Decimal(10), expected_result=False) - - -def test_is_subnormal(): - def is_subnormal(a: Decimal) -> bool: - return a.is_subnormal() - - is_subnormal_verifier = verifier_for(is_subnormal) - is_subnormal_verifier.verify(Decimal(10), expected_result=False) - - -def test_is_zero(): - def is_zero(a: Decimal) -> bool: - return a.is_zero() - - is_zero_verifier = verifier_for(is_zero) - is_zero_verifier.verify(Decimal(10), expected_result=False) - is_zero_verifier.verify(Decimal(0), expected_result=True) - - -def test_ln(): - def ln(a: Decimal) -> Decimal: - return a.ln() - - ln_verifier = verifier_for(ln) - ln_verifier.verify_property(Decimal(1), predicate=around(Decimal(0))) - ln_verifier.verify_property(Decimal(1).exp(), predicate=around(Decimal(1))) - ln_verifier.verify_property(Decimal('2.5').exp(), predicate=around(Decimal('2.5'))) - - -def test_log10(): - def log10(a: Decimal) -> Decimal: - return a.log10() - - log10_verifier = verifier_for(log10) - log10_verifier.verify_property(Decimal(1), predicate=around(Decimal(0))) - log10_verifier.verify_property(Decimal(10), predicate=around(Decimal(1))) - log10_verifier.verify_property(Decimal('0.1'), predicate=around(Decimal(-1))) - log10_verifier.verify_property(Decimal('5'), predicate=around(Decimal('0.69897'))) - - -def test_logb(): - def logb(a: Decimal) -> Decimal: - return a.logb() - - logb_verifier = verifier_for(logb) - logb_verifier.verify(Decimal(1), expected_result=Decimal(0)) - logb_verifier.verify(Decimal(100), expected_result=Decimal(2)) - logb_verifier.verify(Decimal(200), expected_result=Decimal(2)) - logb_verifier.verify(Decimal('0.1'), expected_result=Decimal(-1)) - logb_verifier.verify(Decimal('0.5'), expected_result=Decimal(-1)) - - -def test_logical_and(): - def logical_and(a: Decimal, b: Decimal) -> Decimal: - return a.logical_and(b) - - logical_and_verifier = verifier_for(logical_and) - logical_and_verifier.verify(Decimal('1010'), Decimal('1100'), expected_result=Decimal('1000')) - - -def test_logical_invert(): - def logical_invert(a: Decimal) -> Decimal: - return a.logical_invert() - - logical_invert_verifier = verifier_for(logical_invert) - logical_invert_verifier.verify(Decimal('1010'), expected_result=Decimal('1111111111111111111111110101')) - - -def test_logical_or(): - def logical_or(a: Decimal, b: Decimal) -> Decimal: - return a.logical_or(b) - - logical_or_verifier = verifier_for(logical_or) - logical_or_verifier.verify(Decimal('1010'), Decimal('1100'), expected_result=Decimal('1110')) - - -def test_logical_xor(): - def logical_xor(a: Decimal, b: Decimal) -> Decimal: - return a.logical_xor(b) - - logical_xor_verifier = verifier_for(logical_xor) - logical_xor_verifier.verify(Decimal('1010'), Decimal('1100'), expected_result=Decimal('0110')) - - -def test_max(): - def decimal_max(a: Decimal, b: Decimal) -> Decimal: - return a.max(b) - - decimal_max_verifier = verifier_for(decimal_max) - decimal_max_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(2)) - decimal_max_verifier.verify(Decimal(2), Decimal(1), expected_result=Decimal(2)) - decimal_max_verifier.verify(Decimal(1), Decimal(-2), expected_result=Decimal(1)) - - -def test_max_mag(): - def decimal_max_mag(a: Decimal, b: Decimal) -> Decimal: - return a.max_mag(b) - - decimal_max_mag_verifier = verifier_for(decimal_max_mag) - decimal_max_mag_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(2)) - decimal_max_mag_verifier.verify(Decimal(2), Decimal(1), expected_result=Decimal(2)) - decimal_max_mag_verifier.verify(Decimal(1), Decimal(-2), expected_result=Decimal(-2)) - - -def test_min(): - def decimal_min(a: Decimal, b: Decimal) -> Decimal: - return a.min(b) - - decimal_min_verifier = verifier_for(decimal_min) - decimal_min_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(1)) - decimal_min_verifier.verify(Decimal(2), Decimal(1), expected_result=Decimal(1)) - decimal_min_verifier.verify(Decimal(1), Decimal(-2), expected_result=Decimal(-2)) - - -def test_min_mag(): - def decimal_min_mag(a: Decimal, b: Decimal) -> Decimal: - return a.min_mag(b) - - decimal_min_mag_verifier = verifier_for(decimal_min_mag) - decimal_min_mag_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal(1)) - decimal_min_mag_verifier.verify(Decimal(2), Decimal(1), expected_result=Decimal(1)) - decimal_min_mag_verifier.verify(Decimal(1), Decimal(-2), expected_result=Decimal(1)) - - -def test_next_minus(): - def next_minus(a: Decimal) -> Decimal: - return a.next_minus() - - next_minus_verifier = verifier_for(next_minus) - next_minus_verifier.verify(Decimal(1), expected_result=Decimal('0.9999999999999999999999999999')) - next_minus_verifier.verify(Decimal('0.9999999999999999999999999999'), - expected_result=Decimal('0.9999999999999999999999999998')) - - -def test_next_plus(): - def next_plus(a: Decimal) -> Decimal: - return a.next_plus() - - next_plus_verifier = verifier_for(next_plus) - next_plus_verifier.verify(Decimal(1), expected_result=Decimal('1.000000000000000000000000001')) - next_plus_verifier.verify(Decimal('1.000000000000000000000000001'), - expected_result=Decimal('1.000000000000000000000000002')) - - -def test_next_toward(): - def next_toward(a: Decimal, b: Decimal) -> Decimal: - return a.next_toward(b) - - next_toward_verifier = verifier_for(next_toward) - next_toward_verifier.verify(Decimal(1), Decimal(0), expected_result=Decimal('0.9999999999999999999999999999')) - next_toward_verifier.verify(Decimal(1), Decimal(2), expected_result=Decimal('1.000000000000000000000000001')) - next_toward_verifier.verify(Decimal(1), Decimal(1), expected_result=Decimal(1)) - - -def test_normalize(): - def normalize(a: Decimal) -> Decimal: - return a.normalize() - - normalize_verifier = verifier_for(normalize) - normalize_verifier.verify(Decimal(10), expected_result=Decimal(10)) - - -def test_number_class(): - def number_class(a: Decimal) -> str: - return a.number_class() - - number_class_verifier = verifier_for(number_class) - number_class_verifier.verify(Decimal(1), expected_result='+Normal') - number_class_verifier.verify(Decimal(-1), expected_result='-Normal') - number_class_verifier.verify(Decimal(0), expected_result='+Zero') - - -def test_quantize(): - def quantize(a: Decimal, b: Decimal) -> Decimal: - return a.quantize(b) - - quantize_verifier = verifier_for(quantize) - quantize_verifier.verify(Decimal('1.41421356'), Decimal('1.000'), - expected_result=Decimal('1.414')) - - -def test_radix(): - def radix(a: Decimal) -> Decimal: - return a.radix() - - radix_verifier = verifier_for(radix) - radix_verifier.verify(Decimal(1), expected_result=Decimal(10)) - - -def test_remainder_near(): - def remainder_near(a: Decimal, b: Decimal) -> Decimal: - return a.remainder_near(b) - - remainder_near_verifier = verifier_for(remainder_near) - remainder_near_verifier.verify(Decimal(18), Decimal(10), expected_result=Decimal(-2)) - remainder_near_verifier.verify(Decimal(25), Decimal(10), expected_result=Decimal(5)) - remainder_near_verifier.verify(Decimal(35), Decimal(10), expected_result=Decimal(-5)) - - -def test_rotate(): - def rotate(a: Decimal, b: int) -> Decimal: - return a.rotate(b) - - rotate_verifier = verifier_for(rotate) - rotate_verifier.verify(Decimal('12.34'), 3, expected_result=Decimal('12340.00')) - rotate_verifier.verify(Decimal('12.34'), -3, expected_result=Decimal('23400000000000000000000000.01')) - - -def test_same_quantum(): - def same_quantum(a: Decimal, b: Decimal) -> bool: - return a.same_quantum(b) - - same_quantum_verifier = verifier_for(same_quantum) - same_quantum_verifier.verify(Decimal(1), Decimal(2), expected_result=True) - same_quantum_verifier.verify(Decimal(1), Decimal(10), expected_result=True) - same_quantum_verifier.verify(Decimal('0.1'), Decimal('0.01'), expected_result=False) - - -def test_scaleb(): - def scaleb(a: Decimal, b: int) -> Decimal: - return a.scaleb(b) - - scaleb_verifier = verifier_for(scaleb) - scaleb_verifier.verify(Decimal(1), 2, expected_result=Decimal(100)) - scaleb_verifier.verify(Decimal(1), -2, expected_result=Decimal('0.01')) - - -def test_sqrt(): - def sqrt(a: Decimal) -> Decimal: - return a.sqrt() - - sqrt_verifier = verifier_for(sqrt) - sqrt_verifier.verify(Decimal(1), expected_result=Decimal(1)) - sqrt_verifier.verify(Decimal(2), expected_result=Decimal('1.414213562373095048801688724')) - sqrt_verifier.verify(Decimal(9), expected_result=Decimal(3)) - - -def test_to_eng_string(): - def to_eng_string(a: Decimal) -> str: - return a.to_eng_string() - - to_eng_string_verifier = verifier_for(to_eng_string) - to_eng_string_verifier.verify(Decimal('123E+1'), expected_result='1.23E+3') - - -def test_to_integral(): - def to_integral(a: Decimal) -> Decimal: - return a.to_integral() - - to_integral_verifier = verifier_for(to_integral) - to_integral_verifier.verify(Decimal('1.23'), Decimal('1')) - to_integral_verifier.verify(Decimal('1.7'), Decimal('2')) - to_integral_verifier.verify(Decimal('1.5'), Decimal('2')) - - -def test_to_integral_exact(): - def to_integral_exact(a: Decimal) -> Decimal: - return a.to_integral_exact() - - to_integral_exact_verifier = verifier_for(to_integral_exact) - to_integral_exact_verifier.verify(Decimal('1.23'), Decimal('1')) - to_integral_exact_verifier.verify(Decimal('1.7'), Decimal('2')) - to_integral_exact_verifier.verify(Decimal('1.5'), Decimal('2')) - - -def test_to_integral_value(): - def to_to_integral_value(a: Decimal) -> Decimal: - return a.to_to_integral_value() - - to_to_integral_value_verifier = verifier_for(to_to_integral_value) - to_to_integral_value_verifier.verify(Decimal('1.23'), Decimal('1')) - to_to_integral_value_verifier.verify(Decimal('1.7'), Decimal('2')) - to_to_integral_value_verifier.verify(Decimal('1.5'), Decimal('2')) diff --git a/jpyinterpreter/tests/test_dict.py b/jpyinterpreter/tests/test_dict.py deleted file mode 100644 index b1e902df..00000000 --- a/jpyinterpreter/tests/test_dict.py +++ /dev/null @@ -1,495 +0,0 @@ -from .conftest import verifier_for - - -def test_membership(): - def membership(tested: dict, x: object) -> bool: - return x in tested - - def not_membership(tested: dict, x: object) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, expected_result=True) - not_membership_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, expected_result=False) - - membership_verifier.verify({ - 1: 'a', - 2: 'b' - }, 3, expected_result=False) - not_membership_verifier.verify({ - 1: 'a', - 2: 'b' - }, 3, expected_result=True) - - membership_verifier.verify({ - 'a': 1, - 'b': 2 - }, 1, expected_result=False) - not_membership_verifier.verify({ - 'a': 1, - 'b': 2 - }, 3, expected_result=True) - - -def test_iter(): - def to_list(x: dict) -> list: - return list(x) - - to_list_verifier = verifier_for(to_list) - - to_list_verifier.verify(dict(), expected_result=[]) - to_list_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[1, 2]) - to_list_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=['a', 'b']) - to_list_verifier.verify({ - 3: 'a', - 2: 'b', - 1: 'c' - }, expected_result=[3, 2, 1]) - - -def test_get_item(): - def get_item(my_dict: dict, key: object) -> str: - return my_dict[key] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, expected_result='a') - get_item_verifier.verify({ - 1: 'a', - 2: 'b' - }, 2, expected_result='b') - get_item_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'a', expected_error=KeyError) - get_item_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'd', expected_error=KeyError) - - -def test_set_item(): - def set_item(my_dict: dict, key: str, value: int) -> dict: - my_dict[key] = value - return my_dict - - set_item_verifier = verifier_for(set_item) - - set_item_verifier.verify(dict(), 'a', 1, expected_result={ - 'a': 1 - }) - set_item_verifier.verify({'a': 1}, 'a', 2, expected_result={ - 'a': 2 - }) - set_item_verifier.verify({'a': 1, 'b': 2}, 'c', 3, expected_result={ - 'a': 1, - 'b': 2, - 'c': 3 - }) - set_item_verifier.verify({'a': 1, 'b': 2}, 'c', 2, expected_result={ - 'a': 1, - 'b': 2, - 'c': 2 - }) - - -def test_delete_item(): - def delete_item(my_dict: dict, key: object) -> dict: - del my_dict[key] - return my_dict - - delete_item_verifier = verifier_for(delete_item) - - delete_item_verifier.verify({1: 'a', 2: 'b'}, 1, expected_result={2: 'b'}) - delete_item_verifier.verify({1: 'a', 2: 'b'}, 2, expected_result={1: 'a'}) - delete_item_verifier.verify({1: 'a'}, 1, expected_result=dict()) - delete_item_verifier.verify({1: 'a', 2: 'b'}, 'a', expected_error=KeyError) - delete_item_verifier.verify({1: 'a', 2: 'b'}, 'd', expected_error=KeyError) - delete_item_verifier.verify(dict(), 'a', expected_error=KeyError) - - -def test_clear(): - def clear(my_dict: dict) -> dict: - my_dict.clear() - return my_dict - - clear_verifier = verifier_for(clear) - - clear_verifier.verify({1: 'a', 2: 'b'}, expected_result=dict()) - clear_verifier.verify({2: 'b'}, expected_result=dict()) - clear_verifier.verify(dict(), expected_result=dict()) - - -def test_copy(): - def copy(my_dict: dict) -> tuple: - out = my_dict.copy() - return out, my_dict is out - - copy_verifier = verifier_for(copy) - - copy_verifier.verify({1: 'a', 2: 'b'}, expected_result=({1: 'a', 2: 'b'}, False)) - copy_verifier.verify({2: 'b'}, expected_result=({2: 'b'}, False)) - copy_verifier.verify(dict(), expected_result=(dict(), False)) - - -def test_get(): - def get(my_dict: dict, key: object) -> object: - return my_dict.get(key) - - def get_with_default(my_dict: dict, key: object, default: object) -> object: - return my_dict.get(key, default) - - get_verifier = verifier_for(get) - get_with_default_verifier = verifier_for(get_with_default) - - get_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, expected_result='a') - get_verifier.verify({ - 1: 'a', - 2: 'b' - }, 2, expected_result='b') - get_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'a', expected_result=None) - get_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'd', expected_result=None) - - get_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, 10, expected_result='a') - get_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 2, 20, expected_result='b') - get_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'a', 'A', expected_result='A') - get_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'd', 'D', expected_result='D') - - -def test_items(): - def items(my_dict: dict) -> list: - return list(my_dict.items()) - - def items_with_modification(my_dict: dict) -> list: - out = my_dict.items() - my_dict['extra'] = 10 - return list(out) - - items_verifier = verifier_for(items) - items_with_modification_verifier = verifier_for(items_with_modification) - - items_verifier.verify(dict(), expected_result=[]) - items_verifier.verify({'key': 'value'}, expected_result=[('key', 'value')]) - items_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[(1, 'a'), (2, 'b')]) - items_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=[('a', 1), ('b', 2)]) - - items_with_modification_verifier.verify(dict(), expected_result=[('extra', 10)]) - items_with_modification_verifier.verify({'key': 'value'}, expected_result=[('key', 'value'), ('extra', 10)]) - items_with_modification_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[(1, 'a'), (2, 'b'), ('extra', 10)]) - items_with_modification_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=[('a', 1), ('b', 2), ('extra', 10)]) - items_with_modification_verifier.verify({ - 'extra': 1, - 'b': 2 - }, expected_result=[('extra', 10), ('b', 2)]) - - -def test_keys(): - def keys(my_dict: dict) -> list: - return list(my_dict.keys()) - - def keys_with_modification(my_dict: dict) -> list: - out = my_dict.keys() - my_dict['extra'] = 10 - return list(out) - - keys_verifier = verifier_for(keys) - keys_with_modification_verifier = verifier_for(keys_with_modification) - - keys_verifier.verify(dict(), expected_result=[]) - keys_verifier.verify({'key': 'value'}, expected_result=['key']) - keys_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[1, 2]) - keys_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=['a', 'b']) - - keys_with_modification_verifier.verify(dict(), expected_result=['extra']) - keys_with_modification_verifier.verify({'key': 'value'}, expected_result=['key', 'extra']) - keys_with_modification_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[1, 2, 'extra']) - keys_with_modification_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=['a', 'b', 'extra']) - keys_with_modification_verifier.verify({ - 'extra': 1, - 'b': 2 - }, expected_result=['extra', 'b']) - - -def test_values(): - def values(my_dict: dict) -> list: - return list(my_dict.values()) - - def values_with_modification(my_dict: dict) -> list: - out = my_dict.values() - my_dict['extra'] = 10 - return list(out) - - values_verifier = verifier_for(values) - values_with_modification_verifier = verifier_for(values_with_modification) - - values_verifier.verify(dict(), expected_result=[]) - values_verifier.verify({'key': 'value'}, expected_result=['value']) - values_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=['a', 'b']) - values_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=[1, 2]) - values_verifier.verify({ - 'a': 1, - 'b': 2, - 'c': 2 - }, expected_result=[1, 2, 2]) - - values_with_modification_verifier.verify(dict(), expected_result=[10]) - values_with_modification_verifier.verify({'key': 'value'}, expected_result=['value', 10]) - values_with_modification_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=['a', 'b', 10]) - values_with_modification_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=[1, 2, 10]) - values_with_modification_verifier.verify({ - 'a': 1, - 'b': 2, - 'c': 2, - }, expected_result=[1, 2, 2, 10]) - values_with_modification_verifier.verify({ - 'extra': 1, - 'b': 2 - }, expected_result=[10, 2]) - - -def test_pop(): - def pop(my_dict: dict, key: object) -> tuple: - out = my_dict.pop(key) - return out, my_dict - - def pop_with_default(my_dict: dict, key: object, default: object) -> tuple: - out = my_dict.pop(key, default) - return out, my_dict - - pop_verifier = verifier_for(pop) - pop_with_default_verifier = verifier_for(pop_with_default) - - pop_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'a', expected_result=(1, {'b': 2})) - pop_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'b', expected_result=(2, {'a': 1})) - pop_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'c', expected_error=KeyError) - - pop_with_default_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'a', 3, expected_result=(1, {'b': 2})) - pop_with_default_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'b', 3, expected_result=(2, {'a': 1})) - pop_with_default_verifier.verify({ - 'a': 1, - 'b': 2 - }, 'c', 3, expected_result=(3, {'a': 1, 'b': 2})) - - -def test_popitem(): - def popitem(my_dict: dict) -> tuple: - out = my_dict.popitem() - return out, my_dict - - popitem_verifier = verifier_for(popitem) - - popitem_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=((2, 'b'), {1: 'a'})) - popitem_verifier.verify({ - 'b': 2, - 'a': 1 - }, expected_result=(('a', 1), {'b': 2})) - popitem_verifier.verify({ - 'b': 2, - 'a': 1, - 'c': 3 - }, expected_result=(('c', 3), {'b': 2, 'a': 1})) - popitem_verifier.verify(dict(), expected_error=KeyError) - - -def test_reversed(): - def to_reversed_list(x: dict) -> list: - return list(reversed(x)) - - to_reversed_list_verifier = verifier_for(to_reversed_list) - - to_reversed_list_verifier.verify(dict(), expected_result=[]) - to_reversed_list_verifier.verify({ - 1: 'a', - 2: 'b' - }, expected_result=[2, 1]) - to_reversed_list_verifier.verify({ - 'a': 1, - 'b': 2 - }, expected_result=['b', 'a']) - to_reversed_list_verifier.verify({ - 3: 'a', - 2: 'b', - 1: 'c' - }, expected_result=[1, 2, 3]) - - -def test_setdefault(): - def put(my_dict: dict, key: object) -> tuple: - out = my_dict.setdefault(key) - return out, my_dict - - def put_with_default(my_dict: dict, key: object, default: object) -> tuple: - out = my_dict.setdefault(key, default) - return out, my_dict - - put_verifier = verifier_for(put) - put_with_default_verifier = verifier_for(put_with_default) - - put_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, expected_result=('a', {1: 'a', 2: 'b'})) - put_verifier.verify({ - 1: 'a', - 2: 'b' - }, 2, expected_result=('b', {1: 'a', 2: 'b'})) - put_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'a', expected_result=(None, {1: 'a', 2: 'b', 'a': None})) - put_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'd', expected_result=(None, {1: 'a', 2: 'b', 'd': None})) - - put_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 1, 10, expected_result=('a', {1: 'a', 2: 'b'})) - put_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 2, 10, expected_result=('b', {1: 'a', 2: 'b'})) - put_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'a', 10, expected_result=(10, {1: 'a', 2: 'b', 'a': 10})) - put_with_default_verifier.verify({ - 1: 'a', - 2: 'b' - }, 'd', 100, expected_result=(100, {1: 'a', 2: 'b', 'd': 100})) - - -def test_update(): - def update_dict(my_dict: dict, items: dict) -> dict: - my_dict.update(items) - return my_dict - - def update_list(my_dict: dict, items: list) -> dict: - my_dict.update(items) - return my_dict - - update_dict_verifier = verifier_for(update_dict) - update_list_verifier = verifier_for(update_list) - - update_dict_verifier.verify({ - 'a': 1, - 'b': 2 - }, { - 'c': 3, - 'd': 4 - }, expected_result={'a': 1, 'b': 2, 'c': 3, 'd': 4}) - update_dict_verifier.verify({ - 'a': 1, - 'b': 2 - }, { - 'b': 3, - 'd': 4 - }, expected_result={'a': 1, 'b': 3, 'd': 4}) - - update_list_verifier.verify({ - 'a': 1, - 'b': 2 - }, [ - ('c', 3), - ('d', 4) - ], expected_result={'a': 1, 'b': 2, 'c': 3, 'd': 4}) - update_list_verifier.verify({ - 'a': 1, - 'b': 2 - }, [ - ('b', 3), - ('d', 4) - ], expected_result={'a': 1, 'b': 3, 'd': 4}) diff --git a/jpyinterpreter/tests/test_dunder.py b/jpyinterpreter/tests/test_dunder.py deleted file mode 100644 index db9aed93..00000000 --- a/jpyinterpreter/tests/test_dunder.py +++ /dev/null @@ -1,411 +0,0 @@ -from .conftest import verifier_for - - -def test_same_operand(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - def function(a: A, b: A) -> int: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), A(2), expected_result=1) - - -def test_only_left_defined(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - class B: - def __init__(self, value): - self.value = value - - def function(a: A, b: B) -> int: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_only_right_defined(): - class A: - def __init__(self, value): - self.value = value - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return other.value - self.value - - def function(a: A, b: B) -> int: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_both_defined(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return self.value - other.value - - def function(a: A, b: B) -> int: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_neither_defined(): - class A: - def __init__(self, value): - self.value = value - - class B: - def __init__(self, value): - self.value = value - - # We are testing raising TypeError here, so we should ignore the IDE warnings this function gives - def function(a: A, b: B) -> object: # noqa - return a - b # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_error=TypeError) - - -def test_left_return_not_implemented(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return NotImplemented - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return other.value - self.value - - def function(a: A, b: B) -> int: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_both_return_not_implemented(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return NotImplemented - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return NotImplemented - - def function(a: A, b: B) -> object: - return a - b - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_error=TypeError) - - -def test_inplace_same_operand(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - def function(a: A, b: A) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), A(2), expected_result=1) - - -def test_inplace_defined(): - class A: - def __init__(self, value): - self.value = value - - def __isub__(self, other): - return self.value - other.value - - def __sub__(self, other): - return self.value + other.value - - class B: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value + other.value - - def __rsub__(self, other): - return self.value + other.value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_only_left_defined(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - class B: - def __init__(self, value): - self.value = value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_only_right_defined(): - class A: - def __init__(self, value): - self.value = value - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return other.value - self.value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_both_defined(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return self.value - other.value - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return self.value - other.value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_neither_defined(): - class A: - def __init__(self, value): - self.value = value - - class B: - def __init__(self, value): - self.value = value - - # We are testing raising TypeError here, so we should ignore the IDE warnings this function gives - def function(a: A, b: B) -> object: # noqa - a -= b # noqa - return a - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_error=TypeError) - - -def test_inplace_left_inplace_return_not_implemented(): - class A: - def __init__(self, value): - self.value = value - - def __isub__(self, other): - return NotImplemented - - def __sub__(self, other): - return self.value - other.value - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return other.value + self.value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_left_return_not_implemented(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return NotImplemented - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return other.value - self.value - - def function(a: A, b: B) -> int: - a -= b - return a # noqa - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_result=1) - - -def test_inplace_both_return_not_implemented(): - class A: - def __init__(self, value): - self.value = value - - def __sub__(self, other): - return NotImplemented - - class B: - def __init__(self, value): - self.value = value - - def __rsub__(self, other): - return NotImplemented - - def function(a: A, b: B) -> object: - a -= b - return a - - verifier = verifier_for(function) - verifier.verify(A(3), B(2), expected_error=TypeError) - - -def test_inverted_comparisons(): - class A: - def __init__(self, value): - self.value = value - - class B: - def __init__(self, value): - self.value = value - - def __lt__(self, other): - return self.value < other.value - - def __gt__(self, other): - return self.value > other.value - - def __le__(self, other): - return self.value <= other.value - - def __ge__(self, other): - return self.value >= other.value - - def __eq__(self, other): - return self.value == other.value - - def __ne__(self, other): - return self.value != other.value - - def less_than(a: A, b: B) -> bool: - return a < b - - verifier = verifier_for(less_than) - verifier.verify(A(1), B(2), expected_result=True) - verifier.verify(A(1), B(1), expected_result=False) - verifier.verify(A(2), B(1), expected_result=False) - - def greater_than(a: A, b: B) -> bool: - return a > b - - verifier = verifier_for(greater_than) - verifier.verify(A(1), B(2), expected_result=False) - verifier.verify(A(1), B(1), expected_result=False) - verifier.verify(A(2), B(1), expected_result=True) - - def less_than_or_equal(a: A, b: B) -> bool: - return a <= b - - verifier = verifier_for(less_than_or_equal) - verifier.verify(A(1), B(2), expected_result=True) - verifier.verify(A(1), B(1), expected_result=True) - verifier.verify(A(2), B(1), expected_result=False) - - def greater_than_or_equal(a: A, b: B) -> bool: - return a >= b - - verifier = verifier_for(greater_than_or_equal) - verifier.verify(A(1), B(2), expected_result=False) - verifier.verify(A(1), B(1), expected_result=True) - verifier.verify(A(2), B(1), expected_result=True) - - def equal(a: A, b: B) -> bool: - return a == b - - verifier = verifier_for(equal) - verifier.verify(A(1), B(2), expected_result=False) - verifier.verify(A(1), B(1), expected_result=True) - verifier.verify(A(2), B(1), expected_result=False) - - def not_equal(a: A, b: B) -> bool: - return a != b - - verifier = verifier_for(not_equal) - verifier.verify(A(1), B(2), expected_result=True) - verifier.verify(A(1), B(1), expected_result=False) - verifier.verify(A(2), B(1), expected_result=True) diff --git a/jpyinterpreter/tests/test_float.py b/jpyinterpreter/tests/test_float.py deleted file mode 100644 index e91ac292..00000000 --- a/jpyinterpreter/tests/test_float.py +++ /dev/null @@ -1,481 +0,0 @@ -from .conftest import verifier_for - -MAX_LONG = 0xFFFF_FFFF_FFFF_FFFF -MIN_LONG = -MAX_LONG - - -def test_use_64_bits(): - def identity(x: float) -> float: - return x - - identity_verifier = verifier_for(identity) - - identity_verifier.verify(2345678.2345678, expected_result=2345678.2345678) - - -def test_add(): - def int_add(a: float, b: int) -> float: - return a + b - - def float_add(a: float, b: float) -> float: - return a + b - - int_add_verifier = verifier_for(int_add) - float_add_verifier = verifier_for(float_add) - - int_add_verifier.verify(1.0, 1, expected_result=2.0) - int_add_verifier.verify(1.0, -1, expected_result=0.0) - int_add_verifier.verify(-1.0, 1, expected_result=0.0) - int_add_verifier.verify(0.0, 1, expected_result=1.0) - int_add_verifier.verify(MAX_LONG + 0.0, 1, expected_result=(MAX_LONG + 1.0)) - int_add_verifier.verify(MIN_LONG + 0.0, -1, expected_result=(MIN_LONG - 1.0)) - - float_add_verifier.verify(1.0, 1.0, expected_result=2.0) - float_add_verifier.verify(1.0, -1.0, expected_result=0.0) - float_add_verifier.verify(-1.0, 1.0, expected_result=0.0) - float_add_verifier.verify(0.0, 1.0, expected_result=1.0) - float_add_verifier.verify(MAX_LONG + 0.0, 1.0, expected_result=(MAX_LONG + 1.0)) - float_add_verifier.verify(MIN_LONG + 0.0, -1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_iadd(): - def int_iadd(x: float, y: int) -> float: - old = x - x += y - if y != 0: - assert old is not x - return x - - def float_iadd(x: float, y: float) -> float: - old = x - x += y - if y != 0: - assert old is not x - return x - - int_iadd_verifier = verifier_for(int_iadd) - float_iadd_verifier = verifier_for(float_iadd) - - int_iadd_verifier.verify(1.0, 1, expected_result=2.0) - int_iadd_verifier.verify(1.0, -1, expected_result=0.0) - int_iadd_verifier.verify(-1.0, 1, expected_result=0.0) - int_iadd_verifier.verify(0.0, 1, expected_result=1.0) - int_iadd_verifier.verify(MAX_LONG + 0.0, 1, expected_result=(MAX_LONG + 1.0)) - int_iadd_verifier.verify(MIN_LONG + 0.0, -1, expected_result=(MIN_LONG - 1.0)) - - float_iadd_verifier.verify(1.0, 1.0, expected_result=2.0) - float_iadd_verifier.verify(1.0, -1.0, expected_result=0.0) - float_iadd_verifier.verify(-1.0, 1.0, expected_result=0.0) - float_iadd_verifier.verify(0.0, 1.0, expected_result=1.0) - float_iadd_verifier.verify(MAX_LONG + 0.0, 1.0, expected_result=(MAX_LONG + 1.0)) - float_iadd_verifier.verify(MIN_LONG + 0.0, -1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_sub(): - def int_sub(a: float, b: int) -> float: - return a - b - - def float_sub(a: float, b: float) -> float: - return a - b - - int_sub_verifier = verifier_for(int_sub) - float_sub_verifier = verifier_for(float_sub) - - int_sub_verifier.verify(1.0, 1, expected_result=0.0) - int_sub_verifier.verify(1.0, -1, expected_result=2.0) - int_sub_verifier.verify(-1.0, 1, expected_result=-2.0) - int_sub_verifier.verify(0.0, 1, expected_result=-1.0) - int_sub_verifier.verify(MAX_LONG + 0.0, -1, expected_result=(MAX_LONG + 1.0)) - int_sub_verifier.verify(MIN_LONG + 0.0, 1, expected_result=(MIN_LONG - 1.0)) - - float_sub_verifier.verify(1.0, 1.0, expected_result=0.0) - float_sub_verifier.verify(1.0, -1.0, expected_result=2.0) - float_sub_verifier.verify(-1.0, 1.0, expected_result=-2.0) - float_sub_verifier.verify(0.0, 1.0, expected_result=-1.0) - float_sub_verifier.verify(MAX_LONG + 0.0, -1.0, expected_result=(MAX_LONG + 1.0)) - float_sub_verifier.verify(MIN_LONG + 0.0, 1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_isub(): - def int_isub(x: float, y: int) -> float: - old = x - x -= y - if y != 0: - assert old is not x - return x - - def float_isub(x: float, y: float) -> float: - old = x - x -= y - if y != 0: - assert old is not x - return x - - int_isub_verifier = verifier_for(int_isub) - float_isub_verifier = verifier_for(float_isub) - - int_isub_verifier.verify(1.0, 1, expected_result=0.0) - int_isub_verifier.verify(1.0, -1, expected_result=2.0) - int_isub_verifier.verify(-1.0, 1, expected_result=-2.0) - int_isub_verifier.verify(0.0, 1, expected_result=-1.0) - int_isub_verifier.verify(MAX_LONG + 0.0, -1, expected_result=(MAX_LONG + 1.0)) - int_isub_verifier.verify(MIN_LONG + 0.0, 1, expected_result=(MIN_LONG - 1.0)) - - float_isub_verifier.verify(1.0, 1.0, expected_result=0.0) - float_isub_verifier.verify(1.0, -1.0, expected_result=2.0) - float_isub_verifier.verify(-1.0, 1.0, expected_result=-2.0) - float_isub_verifier.verify(0.0, 1.0, expected_result=-1.0) - float_isub_verifier.verify(MAX_LONG + 0.0, -1.0, expected_result=(MAX_LONG + 1.0)) - float_isub_verifier.verify(MIN_LONG + 0.0, 1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_multiply(): - def int_multiply(a: float, b: int) -> float: - return a * b - - def float_multiply(a: float, b: float) -> float: - return a * b - - int_multiply_verifier = verifier_for(int_multiply) - float_multiply_verifier = verifier_for(float_multiply) - - int_multiply_verifier.verify(1.0, 1, expected_result=1.0) - int_multiply_verifier.verify(1.0, -1, expected_result=-1.0) - int_multiply_verifier.verify(-1.0, 1, expected_result=-1.0) - int_multiply_verifier.verify(0.0, 1, expected_result=0.0) - int_multiply_verifier.verify(2.0, 3, expected_result=6.0) - int_multiply_verifier.verify(MAX_LONG + 0.0, 2, expected_result=(2.0 * MAX_LONG)) - int_multiply_verifier.verify(MIN_LONG + 0.0, 2, expected_result=(2.0 * MIN_LONG)) - - float_multiply_verifier.verify(1.0, 1.0, expected_result=1.0) - float_multiply_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_multiply_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_multiply_verifier.verify(0.0, 1.0, expected_result=0.0) - float_multiply_verifier.verify(2.0, 3.0, expected_result=6.0) - float_multiply_verifier.verify(MAX_LONG + 0.0, 2.0, expected_result=(2.0 * MAX_LONG)) - float_multiply_verifier.verify(MIN_LONG + 0.0, 2.0, expected_result=(2.0 * MIN_LONG)) - - -def test_imultiply(): - def int_imultiply(x: float, y: int) -> float: - old = x - x *= y - if y != 1: - assert old is not x - return x - - def float_imultiply(x: float, y: float) -> float: - old = x - x *= y - if y != 1: - assert old is not x - return x - - int_imultiply_verifier = verifier_for(int_imultiply) - float_imultiply_verifier = verifier_for(float_imultiply) - - int_imultiply_verifier.verify(1.0, 1, expected_result=1.0) - int_imultiply_verifier.verify(1.0, -1, expected_result=-1.0) - int_imultiply_verifier.verify(-1.0, 1, expected_result=-1.0) - int_imultiply_verifier.verify(0.0, 1, expected_result=0.0) - int_imultiply_verifier.verify(2.0, 3, expected_result=6.0) - int_imultiply_verifier.verify(MAX_LONG + 0.0, 2, expected_result=(2.0 * MAX_LONG)) - int_imultiply_verifier.verify(MIN_LONG + 0.0, 2, expected_result=(2.0 * MIN_LONG)) - - float_imultiply_verifier.verify(1.0, 1.0, expected_result=1.0) - float_imultiply_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_imultiply_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_imultiply_verifier.verify(0.0, 1.0, expected_result=0.0) - float_imultiply_verifier.verify(2.0, 3.0, expected_result=6.0) - float_imultiply_verifier.verify(MAX_LONG + 0.0, 2.0, expected_result=(2.0 * MAX_LONG)) - float_imultiply_verifier.verify(MIN_LONG + 0.0, 2.0, expected_result=(2.0 * MIN_LONG)) - - -def test_truediv(): - def int_truediv(a: float, b: int) -> float: - return a / b - - def float_truediv(a: float, b: float) -> float: - return a / b - - int_truediv_verifier = verifier_for(int_truediv) - float_truediv_verifier = verifier_for(float_truediv) - - int_truediv_verifier.verify(1.0, 1, expected_result=1.0) - int_truediv_verifier.verify(1.0, -1, expected_result=-1.0) - int_truediv_verifier.verify(-1.0, 1, expected_result=-1.0) - int_truediv_verifier.verify(0.0, 1, expected_result=0.0) - int_truediv_verifier.verify(3.0, 2, expected_result=1.5) - int_truediv_verifier.verify(2.0 * MAX_LONG, 2, expected_result=1.8446744073709552e+19) - int_truediv_verifier.verify(2.0 * MIN_LONG, 2, expected_result=-1.8446744073709552e+19) - int_truediv_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_truediv_verifier.verify(1.0, 1.0, expected_result=1.0) - float_truediv_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_truediv_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_truediv_verifier.verify(0.0, 1.0, expected_result=0.0) - float_truediv_verifier.verify(3.0, 2.0, expected_result=1.5) - float_truediv_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_truediv_verifier.verify(2.0 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_truediv_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_itruediv(): - def int_itruediv(x: float, y: int) -> float: - old = x - x /= y - if y != 1: - assert old is not x - return x - - def float_itruediv(x: float, y: float) -> float: - old = x - x /= y - if y != 1: - assert old is not x - return x - - int_itruediv_verifier = verifier_for(int_itruediv) - float_itruediv_verifier = verifier_for(float_itruediv) - - int_itruediv_verifier.verify(1.0, 1, expected_result=1.0) - int_itruediv_verifier.verify(1.0, -1, expected_result=-1.0) - int_itruediv_verifier.verify(-1.0, 1, expected_result=-1.0) - int_itruediv_verifier.verify(0.0, 1, expected_result=0.0) - int_itruediv_verifier.verify(3.0, 2, expected_result=1.5) - int_itruediv_verifier.verify(2.0 * MAX_LONG, 2, expected_result=1.8446744073709552e+19) - int_itruediv_verifier.verify(2.0 * MIN_LONG, 2, expected_result=-1.8446744073709552e+19) - int_itruediv_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_itruediv_verifier.verify(1.0, 1.0, expected_result=1.0) - float_itruediv_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_itruediv_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_itruediv_verifier.verify(0.0, 1.0, expected_result=0.0) - float_itruediv_verifier.verify(3.0, 2.0, expected_result=1.5) - float_itruediv_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_itruediv_verifier.verify(2.0 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_itruediv_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_floordiv(): - def int_floordiv(a: float, b: int) -> float: - return a // b - - def float_floordiv(a: float, b: float) -> float: - return a // b - - int_floordiv_verifier = verifier_for(int_floordiv) - float_floordiv_verifier = verifier_for(float_floordiv) - - int_floordiv_verifier.verify(1.0, 1, expected_result=1.0) - int_floordiv_verifier.verify(1.0, -1, expected_result=-1.0) - int_floordiv_verifier.verify(-1.0, 1, expected_result=-1.0) - int_floordiv_verifier.verify(0.0, 1, expected_result=0.0) - int_floordiv_verifier.verify(3.0, 2, expected_result=1.0) - int_floordiv_verifier.verify(2.0 * MAX_LONG, 2, expected_result=(MAX_LONG + 0.0)) - int_floordiv_verifier.verify(2.0 * MIN_LONG, 2, expected_result=(MIN_LONG + 0.0)) - int_floordiv_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_floordiv_verifier.verify(1.0, 1.0, expected_result=1.0) - float_floordiv_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_floordiv_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_floordiv_verifier.verify(0.0, 1.0, expected_result=0.0) - float_floordiv_verifier.verify(3.0, 2.0, expected_result=1.0) - float_floordiv_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_floordiv_verifier.verify(2.0 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_floordiv_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_ifloordiv(): - def int_ifloordiv(x: float, y: int) -> float: - old = x - x //= y - if y != 1: - assert old is not x - return x - - def float_ifloordiv(x: float, y: float) -> float: - old = x - x //= y - if y != 1: - assert old is not x - return x - - int_ifloordiv_verifier = verifier_for(int_ifloordiv) - float_ifloordiv_verifier = verifier_for(float_ifloordiv) - - int_ifloordiv_verifier.verify(1.0, 1, expected_result=1.0) - int_ifloordiv_verifier.verify(1.0, -1, expected_result=-1.0) - int_ifloordiv_verifier.verify(-1.0, 1, expected_result=-1.0) - int_ifloordiv_verifier.verify(0.0, 1, expected_result=0.0) - int_ifloordiv_verifier.verify(3.0, 2, expected_result=1.0) - int_ifloordiv_verifier.verify(2.0 * MAX_LONG, 2, expected_result=(MAX_LONG + 0.0)) - int_ifloordiv_verifier.verify(2.0 * MIN_LONG, 2, expected_result=(MIN_LONG + 0.0)) - int_ifloordiv_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_ifloordiv_verifier.verify(1.0, 1.0, expected_result=1.0) - float_ifloordiv_verifier.verify(1.0, -1.0, expected_result=-1.0) - float_ifloordiv_verifier.verify(-1.0, 1.0, expected_result=-1.0) - float_ifloordiv_verifier.verify(0.0, 1.0, expected_result=0.0) - float_ifloordiv_verifier.verify(3.0, 2.0, expected_result=1.0) - float_ifloordiv_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_ifloordiv_verifier.verify(2.0 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_ifloordiv_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_mod(): - def int_mod(a: float, b: int) -> float: - return a % b - - def float_mod(a: float, b: float) -> float: - return a % b - - int_mod_verifier = verifier_for(int_mod) - float_mod_verifier = verifier_for(float_mod) - - int_mod_verifier.verify(3.0, 4, expected_result=3.0) - int_mod_verifier.verify(4.0, 3, expected_result=1.0) - int_mod_verifier.verify(-4.0, 3, expected_result=2.0) - int_mod_verifier.verify(4.0, -3, expected_result=-2.0) - int_mod_verifier.verify(-4.0, -3, expected_result=-1.0) - int_mod_verifier.verify(2.0 * MAX_LONG, 2, expected_result=0.0) - int_mod_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_mod_verifier.verify(3.0, 4.0, expected_result=3.0) - float_mod_verifier.verify(4.0, 3.0, expected_result=1.0) - float_mod_verifier.verify(-4.0, 3.0, expected_result=2.0) - float_mod_verifier.verify(4.0, -3.0, expected_result=-2.0) - float_mod_verifier.verify(-4.0, -3.0, expected_result=-1.0) - float_mod_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=0.0) - float_mod_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_imod(): - def int_imod(x: float, y: int) -> float: - old = x - x %= y - if old > y: - assert old is not x - return x - - def float_imod(x: float, y: float) -> float: - old = x - x %= y - if old > y: - assert old is not x - return x - - int_imod_verifier = verifier_for(int_imod) - float_imod_verifier = verifier_for(float_imod) - - int_imod_verifier.verify(3.0, 4, expected_result=3.0) - int_imod_verifier.verify(4.0, 3, expected_result=1.0) - int_imod_verifier.verify(-4.0, 3, expected_result=2.0) - int_imod_verifier.verify(4.0, -3, expected_result=-2.0) - int_imod_verifier.verify(-4.0, -3, expected_result=-1.0) - int_imod_verifier.verify(2.0 * MAX_LONG, 2, expected_result=0.0) - int_imod_verifier.verify(1.0, 0, expected_error=ZeroDivisionError) - - float_imod_verifier.verify(3.0, 4.0, expected_result=3.0) - float_imod_verifier.verify(4.0, 3.0, expected_result=1.0) - float_imod_verifier.verify(-4.0, 3.0, expected_result=2.0) - float_imod_verifier.verify(4.0, -3.0, expected_result=-2.0) - float_imod_verifier.verify(-4.0, -3.0, expected_result=-1.0) - float_imod_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=0.0) - float_imod_verifier.verify(1.0, 0.0, expected_error=ZeroDivisionError) - - -def test_negate(): - def negate(x: float) -> float: - return -x - - negate_verifier = verifier_for(negate) - - negate_verifier.verify(1.0, expected_result=-1.0) - negate_verifier.verify(-1.0, expected_result=1.0) - negate_verifier.verify(MAX_LONG + 0.0, expected_result=-(MAX_LONG + 0.0)) - negate_verifier.verify(MIN_LONG + 0.0, expected_result=-(MIN_LONG + 0.0)) - - -def test_pos(): - def pos(x: float) -> float: - return +x - - pos_verifier = verifier_for(pos) - - pos_verifier.verify(1.0, expected_result=1.0) - pos_verifier.verify(-1.0, expected_result=-1.0) - pos_verifier.verify(MAX_LONG + 0.0, expected_result=(MAX_LONG + 0.0)) - pos_verifier.verify(MIN_LONG + 0.0, expected_result=(MIN_LONG + 0.0)) - - -def test_abs(): - def float_abs(x: float) -> float: - return abs(x) - - abs_verifier = verifier_for(float_abs) - - abs_verifier.verify(1.0, expected_result=1.0) - abs_verifier.verify(-1.0, expected_result=1.0) - abs_verifier.verify(MAX_LONG + 0.0, expected_result=(MAX_LONG + 0.0)) - abs_verifier.verify(MIN_LONG + 0.0, expected_result=-(MIN_LONG + 0.0)) - - -def test_divmod(): - def int_divmod(a: float, b: int) -> tuple: - return divmod(a, b) - - def float_divmod(a: float, b: float) -> tuple: - return divmod(a, b) - - int_divmod_verifier = verifier_for(int_divmod) - float_divmod_verifier = verifier_for(float_divmod) - - int_divmod_verifier.verify(1.0, 1, expected_result=(1.0, 0.0)) - int_divmod_verifier.verify(1.0, -1, expected_result=(-1.0, 0.0)) - int_divmod_verifier.verify(-1.0, 1, expected_result=(-1.0, 0.0)) - int_divmod_verifier.verify(0.0, 1, expected_result=(0.0, 0.0)) - int_divmod_verifier.verify(3.0, 2, expected_result=(1.0, 1.0)) - int_divmod_verifier.verify(-3.0, -2, expected_result=(1.0, -1.0)) - int_divmod_verifier.verify(2.0 * MAX_LONG, 2, expected_result=(MAX_LONG + 0.0, 0.0)) - int_divmod_verifier.verify(2.0 * MIN_LONG, 2, expected_result=(MIN_LONG + 0.0, 0.0)) - - float_divmod_verifier.verify(1.0, 1.0, expected_result=(1.0, 0.0)) - float_divmod_verifier.verify(1.0, -1.0, expected_result=(-1.0, 0.0)) - float_divmod_verifier.verify(-1.0, 1.0, expected_result=(-1.0, 0.0)) - float_divmod_verifier.verify(0.0, 1.0, expected_result=(0.0, 0.0)) - float_divmod_verifier.verify(3.0, 2.0, expected_result=(1.0, 1.0)) - float_divmod_verifier.verify(-3.0, -2.0, expected_result=(1.0, -1.0)) - float_divmod_verifier.verify(2.0 * MAX_LONG, 2.0, expected_result=(1.8446744073709552e+19, 0.0)) - float_divmod_verifier.verify(2.0 * MIN_LONG, 2.0, expected_result=(-1.8446744073709552e+19, 0.0)) - - -def test_pow(): - def int_pow(a: float, b: int) -> float: - return a ** b - - def float_pow(a: float, b: float) -> float: - return a ** b - - int_pow_verifier = verifier_for(int_pow) - float_pow_verifier = verifier_for(float_pow) - - int_pow_verifier.verify(0.0, 0, expected_result=1.0) - int_pow_verifier.verify(1.0, 2, expected_result=1.0) - int_pow_verifier.verify(2.0, 2, expected_result=4.0) - int_pow_verifier.verify(-2.0, 2, expected_result=4.0) - int_pow_verifier.verify(-2.0, 3, expected_result=-8.0) - int_pow_verifier.verify(3.0, 2, expected_result=9.0) - int_pow_verifier.verify(2.0, 3, expected_result=8.0) - int_pow_verifier.verify(2.0, -1, expected_result=0.5) - int_pow_verifier.verify(2.0, -2, expected_result=0.25) - - float_pow_verifier.verify(0.0, 0.0, expected_result=1.0) - float_pow_verifier.verify(1.0, 2.0, expected_result=1.0) - float_pow_verifier.verify(2.0, 2.0, expected_result=4.0) - float_pow_verifier.verify(-2.0, 2.0, expected_result=4.0) - float_pow_verifier.verify(-2.0, 3.0, expected_result=-8.0) - float_pow_verifier.verify(3.0, 2.0, expected_result=9.0) - float_pow_verifier.verify(2.0, 3.0, expected_result=8.0) - float_pow_verifier.verify(2.0, -1.0, expected_result=0.5) - float_pow_verifier.verify(2.0, -2.0, expected_result=0.25) diff --git a/jpyinterpreter/tests/test_frozenset.py b/jpyinterpreter/tests/test_frozenset.py deleted file mode 100644 index 8c6b1e88..00000000 --- a/jpyinterpreter/tests/test_frozenset.py +++ /dev/null @@ -1,231 +0,0 @@ -from .conftest import verifier_for - - -def test_len(): - def length(tested: frozenset) -> int: - return len(tested) - - len_verifier = verifier_for(length) - len_verifier.verify(frozenset(), expected_result=0) - len_verifier.verify(frozenset({1, 2, 3}), expected_result=3) - - -def test_membership(): - def membership(tested: frozenset, x: object) -> bool: - return x in tested - - def not_membership(tested: frozenset, x: object) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify(frozenset(), 1, expected_result=False) - not_membership_verifier.verify(frozenset(), 1, expected_result=True) - - membership_verifier.verify(frozenset({1, 2, 3}), 1, expected_result=True) - not_membership_verifier.verify(frozenset({1, 2, 3}), 1, expected_result=False) - - -def test_isdisjoint(): - def isdisjoint(x: frozenset, y: frozenset) -> bool: - return x.isdisjoint(y) - - isdisjoint_verifier = verifier_for(isdisjoint) - - isdisjoint_verifier.verify(frozenset({1, 2, 3}), frozenset({4, 5, 6}), expected_result=True) - isdisjoint_verifier.verify(frozenset({1, 2, 3}), frozenset({3, 4, 5}), expected_result=False) - - -def test_issubset(): - def issubset(x: frozenset, y: frozenset) -> bool: - return x.issubset(y) - - def issubset_le(x: frozenset, y: frozenset) -> bool: - return x <= y - - def is_strict_subset(x: frozenset, y: frozenset) -> bool: - return x < y - - issubset_verifier = verifier_for(issubset) - subset_le_verifier = verifier_for(issubset_le) - subset_strict_verifier = verifier_for(is_strict_subset) - - issubset_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=True) - subset_le_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=True) - subset_strict_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=True) - - issubset_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=True) - subset_le_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=True) - subset_strict_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=True) - - issubset_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=True) - subset_le_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=True) - subset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=False) - - issubset_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - subset_le_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - subset_strict_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - - issubset_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=False) - subset_le_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=False) - subset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=False) - - issubset_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=False) - subset_le_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=False) - subset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=False) - - -def test_issuperset(): - def issuperset(x: frozenset, y: frozenset) -> bool: - return x.issuperset(y) - - def issuperset_ge(x: frozenset, y: frozenset) -> bool: - return x >= y - - def is_strict_superset(x: frozenset, y: frozenset) -> bool: - return x > y - - issuperset_verifier = verifier_for(issuperset) - superset_ge_verifier = verifier_for(issuperset_ge) - superset_strict_verifier = verifier_for(is_strict_superset) - - issuperset_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=False) - superset_ge_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=False) - superset_strict_verifier.verify(frozenset(), frozenset({1, 2, 3}), expected_result=False) - - issuperset_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=False) - superset_ge_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=False) - superset_strict_verifier.verify(frozenset({1}), frozenset({1, 2, 3}), expected_result=False) - - issuperset_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=True) - superset_ge_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=True) - superset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=False) - - issuperset_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - superset_ge_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - superset_strict_verifier.verify(frozenset({1, 4}), frozenset({1, 2, 3}), expected_result=False) - - issuperset_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=True) - superset_ge_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=True) - superset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset({1}), expected_result=True) - - issuperset_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=True) - superset_ge_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=True) - superset_strict_verifier.verify(frozenset({1, 2, 3}), frozenset(), expected_result=True) - - -def test_union(): - def union(x: frozenset, y: frozenset) -> frozenset: - return x.union(y) - - def union_or(x: frozenset, y: frozenset) -> frozenset: - return x | y - - union_verifier = verifier_for(union) - union_or_verifier = verifier_for(union_or) - - union_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1, 2})) - union_or_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1, 2})) - - union_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1, 2, 3})) - union_or_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1, 2, 3})) - - union_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset({1})) - union_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset({1})) - - union_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset({1, 2, 3})) - union_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset({1, 2, 3})) - - -def test_intersection(): - def intersection(x: frozenset, y: frozenset) -> frozenset: - return x.intersection(y) - - def intersection_and(x: frozenset, y: frozenset) -> frozenset: - return x & y - - intersection_verifier = verifier_for(intersection) - intersection_and_verifier = verifier_for(intersection_and) - - intersection_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset()) - intersection_and_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset()) - - intersection_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({2})) - intersection_and_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({2})) - - intersection_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset()) - intersection_and_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset()) - - intersection_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset({1, 2, 3})) - intersection_and_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset({1, 2, 3})) - - -def test_difference(): - def difference(x: frozenset, y: frozenset) -> frozenset: - return x.difference(y) - - def difference_subtract(x: frozenset, y: frozenset) -> frozenset: - return x - y - - difference_verifier = verifier_for(difference) - difference_subtract_verifier = verifier_for(difference_subtract) - - difference_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1})) - difference_subtract_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1})) - - difference_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1})) - difference_subtract_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1})) - - difference_verifier.verify(frozenset({2, 3}), frozenset({1, 2}), expected_result=frozenset({3})) - difference_subtract_verifier.verify(frozenset({2, 3}), frozenset({1, 2}), expected_result=frozenset({3})) - - difference_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset()) - difference_subtract_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset()) - - difference_verifier.verify(frozenset({1}), frozenset(), expected_result=frozenset({1})) - difference_subtract_verifier.verify(frozenset({1}), frozenset(), expected_result=frozenset({1})) - - difference_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset()) - difference_subtract_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset()) - - -def test_symmetric_difference(): - def symmetric_difference(x: frozenset, y: frozenset) -> frozenset: - return x.symmetric_difference(y) - - def symmetric_difference_xor(x: frozenset, y: frozenset) -> frozenset: - return x ^ y - - symmetric_difference_verifier = verifier_for(symmetric_difference) - symmetric_difference_xor_verifier = verifier_for(symmetric_difference_xor) - - symmetric_difference_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1, 2})) - symmetric_difference_xor_verifier.verify(frozenset({1}), frozenset({2}), expected_result=frozenset({1, 2})) - - symmetric_difference_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1, 3})) - symmetric_difference_xor_verifier.verify(frozenset({1, 2}), frozenset({2, 3}), expected_result=frozenset({1, 3})) - - symmetric_difference_verifier.verify(frozenset({2, 3}), frozenset({1, 2}), expected_result=frozenset({1, 3})) - symmetric_difference_xor_verifier.verify(frozenset({2, 3}), frozenset({1, 2}), expected_result=frozenset({1, 3})) - - symmetric_difference_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset({1})) - symmetric_difference_xor_verifier.verify(frozenset(), frozenset({1}), expected_result=frozenset({1})) - - symmetric_difference_verifier.verify(frozenset({1}), frozenset(), expected_result=frozenset({1})) - symmetric_difference_xor_verifier.verify(frozenset({1}), frozenset(), expected_result=frozenset({1})) - - symmetric_difference_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset()) - symmetric_difference_xor_verifier.verify(frozenset({1, 2, 3}), frozenset({1, 2, 3}), expected_result=frozenset()) - - -def test_copy(): - def copy_function(x: frozenset) -> tuple: - out = x.copy() - return out, out is x - - copy_verifier = verifier_for(copy_function) - - copy_verifier.verify(frozenset(), expected_result=(frozenset(), True)) - copy_verifier.verify(frozenset({1}), expected_result=(frozenset({1}), True)) - copy_verifier.verify(frozenset({1, 2, 3}), expected_result=(frozenset({1, 2, 3}), True)) diff --git a/jpyinterpreter/tests/test_functions.py b/jpyinterpreter/tests/test_functions.py deleted file mode 100644 index 9f2e1e59..00000000 --- a/jpyinterpreter/tests/test_functions.py +++ /dev/null @@ -1,286 +0,0 @@ -from .conftest import verifier_for - - -def test_keyword_arguments(): - def helper(a: int, b: int) -> int: - return a - b - - def my_function(a: int, b: int) -> int: - return helper(b=b, a=a) - - verifier = verifier_for(my_function) - - verifier.verify(2, 1, expected_result=1) - verifier.verify(1, 2, expected_result=-1) - verifier.verify(1, 1, expected_result=0) - - -def test_default_arguments(): - def helper(a: int, b: int = 1, c: str = '') -> int: - return a - b - len(c) - - def my_function(a: int) -> int: - return helper(a) - - verifier = verifier_for(my_function) - - verifier.verify(2, expected_result=1) - verifier.verify(1, expected_result=0) - - -def test_vargs(): - def helper(*items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(a: int, b: int, c: int) -> int: - return helper(a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=6) - verifier.verify(2, 4, 6, expected_result=12) - verifier.verify(1, 1, 1, expected_result=3) - - -def test_kwargs(): - def helper(**kwargs: int) -> frozenset: - return frozenset(kwargs.items()) - - def my_function(a: int, b: int) -> frozenset: - return helper(first=a, second=b) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, expected_result=frozenset({('first', 1), ('second', 2)})) - verifier.verify(1, 1, expected_result=frozenset({('first', 1), ('second', 1)})) - - -def test_unpack_iterable(): - def helper(*items: int) -> int: - total = 0 - for i in items: - total += i - return total - - def my_function(iterable: tuple) -> int: - return helper(*iterable) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=6) - verifier.verify((2, 4), expected_result=6) - verifier.verify((1,), expected_result=1) - verifier.verify((1,), expected_result=1) - verifier.verify((), expected_result=0) - - -def test_unpack_keywords(): - def helper(**kwargs: int) -> set: - return set(kwargs.items()) - - def my_function(items: dict) -> set: - return helper(**items) - - verifier = verifier_for(my_function) - - verifier.verify({ - 'first': 1, - 'second': 2 - }, expected_result={('first', 1), ('second', 2)}) - - verifier.verify({ - 'third': 3, - 'fourth': 3 - }, expected_result={('third', 3), ('fourth', 3)}) - - verifier.verify({ - 'alone': 0, - }, expected_result={('alone', 0)}) - - verifier.verify(dict(), expected_result=set()) - - -def test_unpack_iterable_and_keywords(): - def helper(first: int, *positional: int, key: str, **keywords: str): - return first, positional, key, keywords - - def my_function(items, keywords): - return helper(*items, **keywords) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), {'key': 'value', 'other': 'thing'}, expected_result=(1, - (2, 3), - 'value', - {'other': 'thing'})) - - -def test_default_with_vargs(): - def helper(*items: int, start: int = 10) -> int: - total = start - for item in items: - total += item - return total - - def my_function(items): - return helper(*items) - - verifier = verifier_for(my_function) - - verifier.verify((1, 2, 3), expected_result=16) - verifier.verify((1, 2), expected_result=13) - - -def test_vargs_with_manatory_args(): - def helper(start: int, *items: int) -> int: - total = start - for item in items: - total += item - return total - - def my_function(a, b, c): - return helper(10, a, b, c) - - verifier = verifier_for(my_function) - - verifier.verify(1, 2, 3, expected_result=16) - verifier.verify(2, 4, 6, expected_result=22) - verifier.verify(1, 1, 1, expected_result=13) - - -def test_recursion(): - def fib(x: int) -> int: - if x <= 1: - return 1 - return fib(x - 1) + fib(x - 2) - - fib_verifier = verifier_for(fib) - - fib_verifier.verify(0, expected_result=1) - fib_verifier.verify(1, expected_result=1) - fib_verifier.verify(2, expected_result=2) - fib_verifier.verify(3, expected_result=3) - fib_verifier.verify(4, expected_result=5) - fib_verifier.verify(5, expected_result=8) - - -def test_alternative_recursion(): - def is_even(x: int) -> bool: - if x == 0: - return True - return is_odd(x - 1) - - def is_odd(x: int) -> bool: - if x == 0: - return False - return is_even(x - 1) - - is_even_verifier = verifier_for(is_even) - - is_even_verifier.verify(0, expected_result=True) - is_even_verifier.verify(1, expected_result=False) - is_even_verifier.verify(2, expected_result=True) - is_even_verifier.verify(3, expected_result=False) - is_even_verifier.verify(4, expected_result=True) - is_even_verifier.verify(5, expected_result=False) - - -def test_inner_function(): - def my_function(x: int) -> int: - def inner_function(y: int) -> int: - return y * y - - return inner_function(x) + inner_function(x) - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=2) - verifier.verify(2, expected_result=8) - verifier.verify(3, expected_result=18) - - -def test_read_cell_variable(): - def my_function(x: int) -> int: - def inner_function(y: int) -> int: - nonlocal x - z = y - - def get_const(): - nonlocal z - return z - - return x * get_const() - - a = inner_function(2) - x += 1 - b = inner_function(3) - - return a + b - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=8) - verifier.verify(2, expected_result=13) - verifier.verify(3, expected_result=18) - - -def test_modify_cell_variable(): - def my_function(x: int) -> int: - def inner_function(y: int) -> None: - nonlocal x - x += y - - inner_function(1) - return x - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=2) - verifier.verify(2, expected_result=3) - verifier.verify(3, expected_result=4) - - -def test_nested_cell_variable(): - def my_function(x: int) -> tuple: - def inner_function_1() -> int: - nonlocal x - y = x * x - - def inner_function_2() -> int: - nonlocal x - nonlocal y - out = x + y - x = y - return out - - return inner_function_2() - - return inner_function_1(), x - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=(2, 1)) - verifier.verify(2, expected_result=(6, 4)) - verifier.verify(3, expected_result=(12, 9)) - - -def test_code_works_if_compilation_failed(): - def my_function(x: int) -> tuple: - def inner_function(y: int) -> type: - nonlocal x - - class MyClass: # TODO: Replace this with something else that fails when class creation is supported - def __init__(self): - self.outer_arg = x - self.inner_arg = y - - def get_args(self): - return self.outer_arg, self.inner_arg - - return MyClass - - return inner_function(2 * x)().get_args() - - verifier = verifier_for(my_function) - verifier.verify(1, expected_result=(1, 2)) - verifier.verify(2, expected_result=(2, 4)) - verifier.verify(3, expected_result=(3, 6)) diff --git a/jpyinterpreter/tests/test_generators.py b/jpyinterpreter/tests/test_generators.py deleted file mode 100644 index c88da19c..00000000 --- a/jpyinterpreter/tests/test_generators.py +++ /dev/null @@ -1,266 +0,0 @@ -import pytest -from .conftest import verifier_for -from typing import Iterable, Type - - -def assert_yield_values(expected): - def predicate(generator): - for item in expected: - if next(generator) != item: - return False - - with pytest.raises(StopIteration): - next(generator) - - return True - - return predicate - - -def test_loop_generator(): - def my_function(x: int): - i = 0 - while i < x: - yield i - i += 1 - - generator_verifier = verifier_for(my_function) - generator_verifier.verify_property(1, predicate=assert_yield_values(tuple(range(1)))) - generator_verifier.verify_property(2, predicate=assert_yield_values(tuple(range(2)))) - generator_verifier.verify_property(3, predicate=assert_yield_values(tuple(range(3)))) - - -def test_iterator_loop_generator(): - def my_function(x: int): - for i in range(x): - yield i - - generator_verifier = verifier_for(my_function) - generator_verifier.verify_property(1, predicate=assert_yield_values(tuple(range(1)))) - generator_verifier.verify_property(2, predicate=assert_yield_values(tuple(range(2)))) - generator_verifier.verify_property(3, predicate=assert_yield_values(tuple(range(3)))) - - -def test_exception_in_generator(): - def my_function(x: int): - i = 0 - while i < x: - yield i - if i == 3: - raise ValueError('x > 3') - i += 1 - yield x - - def my_function_property(expected, throws_value): - def predicate(generator): - for item in expected: - if next(generator) != item: - return False - - if not throws_value: - with pytest.raises(StopIteration): - next(generator) - else: - with pytest.raises(ValueError): - next(generator) - - return True - - return predicate - - generator_verifier = verifier_for(my_function) - generator_verifier.verify_property(0, predicate=my_function_property((0,), False)) - generator_verifier.verify_property(1, predicate=my_function_property((0, 1), False)) - generator_verifier.verify_property(2, predicate=my_function_property((0, 1, 2), False)) - generator_verifier.verify_property(3, predicate=my_function_property((0, 1, 2, 3), False)) - generator_verifier.verify_property(4, predicate=my_function_property((0, 1, 2, 3), True)) - generator_verifier.verify_property(5, predicate=my_function_property((0, 1, 2, 3), True)) - - -def test_try_except_in_generator(): - def my_function(x: str): - try: - if x == 'ValueError': - raise ValueError - else: - yield 'Try' - raise KeyError - - except ValueError: - yield 'Value1' - yield 'Value2' - - except KeyError: - yield 'Key' - finally: - yield 'End' - - verifier = verifier_for(my_function) - - verifier.verify_property('ValueError', predicate=assert_yield_values(('Value1', 'Value2', 'End'))) - verifier.verify_property('KeyError', predicate=assert_yield_values(('Try', 'Key', 'End'))) - - -def test_yield_from_iterable_generator(): - def my_function(iterable1: Iterable, iterable2: Iterable): - yield from iterable1 - yield 0 - yield from iterable2 - - verifier = verifier_for(my_function) - verifier.verify_property((1, 2), (3, 4, 5), predicate=assert_yield_values((1, 2, 0, 3, 4, 5))) - verifier.verify_property((1, 2, 3), (4, 5), predicate=assert_yield_values((1, 2, 3, 0, 4, 5))) - verifier.verify_property([], (4, 5), predicate=assert_yield_values((0, 4, 5))) - verifier.verify_property((1, 2, 3), [], predicate=assert_yield_values((1, 2, 3, 0))) - - -def test_yield_from_generator_generator(): - def my_function(x: int): - def inner_generator(y: int): - for i in range(y): - yield 2 * i - - yield from inner_generator(x) - - verifier = verifier_for(my_function) - verifier.verify_property(1, predicate=assert_yield_values((2,))) - verifier.verify_property(2, predicate=assert_yield_values((2, 4))) - verifier.verify_property(3, predicate=assert_yield_values((2, 4, 6))) - - -def test_send_generator(): - def my_function(x: int): - a = yield x - yield a + x - - def send_predicate(x, sent): - def predicate(generator): - if next(generator) != x: - return False - - if generator.send(sent) != x + sent: - return False - - with pytest.raises(StopIteration): - next(generator) - - return True - - return predicate - - verifier = verifier_for(my_function) - verifier.verify_property(1, predicate=send_predicate(1, 1)) - verifier.verify_property(1, predicate=send_predicate(1, 2)) - - verifier.verify_property(2, predicate=send_predicate(2, 1)) - verifier.verify_property(2, predicate=send_predicate(2, 2)) - - verifier.verify_property(3, predicate=send_predicate(3, 1)) - verifier.verify_property(3, predicate=send_predicate(3, 2)) - - -def test_throw_generator(): - def my_function(): - try: - yield 'Try' - except ValueError: - yield 'Value' - finally: - yield 'Finally' - - def throw_predicate(thrown, expected, error: Type[BaseException] = StopIteration): - def predicate(generator): - if next(generator) != expected[0]: - return False - - if generator.throw(thrown) != expected[1]: - return False - - for item in expected[2:]: - if next(generator) != item: - return False - - with pytest.raises(error): - next(generator) - - return True - - return predicate - - verifier = verifier_for(my_function) - verifier.verify_property(predicate=throw_predicate(ValueError(), ('Try', 'Value', 'Finally'))) - verifier.verify_property(predicate=throw_predicate(KeyError(), ('Try', 'Finally'), error=KeyError)) - - -def test_send_inner_generator(): - def my_function(x: int): - def inner_generator(y: int): - a = yield y - yield a + y - - yield from inner_generator(x) - - def send_predicate(x, sent): - def predicate(generator): - if next(generator) != x: - return False - - if generator.send(sent) != x + sent: - return False - - with pytest.raises(StopIteration): - next(generator) - - return True - - return predicate - - verifier = verifier_for(my_function) - verifier.verify_property(1, predicate=send_predicate(1, 1)) - verifier.verify_property(1, predicate=send_predicate(1, 2)) - - verifier.verify_property(2, predicate=send_predicate(2, 1)) - verifier.verify_property(2, predicate=send_predicate(2, 2)) - - verifier.verify_property(3, predicate=send_predicate(3, 1)) - verifier.verify_property(3, predicate=send_predicate(3, 2)) - - -def test_throw_inner_generator(): - def my_function(): - def inner_generator(): - try: - yield 'Try' - except ValueError: - yield 'Value' - finally: - yield 'Finally' - - try: - yield from inner_generator() - except KeyError: - yield 'Key' - - def throw_predicate(thrown, expected, error: Type[BaseException] = StopIteration): - def predicate(generator): - if next(generator) != expected[0]: - return False - - if generator.throw(thrown) != expected[1]: - return False - - for item in expected[2:]: - if next(generator) != item: - return False - - with pytest.raises(error): - next(generator) - - return True - - return predicate - - verifier = verifier_for(my_function) - verifier.verify_property(predicate=throw_predicate(ValueError(), ('Try', 'Value', 'Finally'))) - verifier.verify_property(predicate=throw_predicate(KeyError(), ('Try', 'Finally', 'Key'))) - verifier.verify_property(predicate=throw_predicate(AssertionError(), ('Try', 'Finally'), error=AssertionError)) diff --git a/jpyinterpreter/tests/test_int.py b/jpyinterpreter/tests/test_int.py deleted file mode 100644 index 700f74e2..00000000 --- a/jpyinterpreter/tests/test_int.py +++ /dev/null @@ -1,483 +0,0 @@ -from typing import Union -from .conftest import verifier_for - -MAX_LONG = 0xFFFF_FFFF_FFFF_FFFF -MIN_LONG = -MAX_LONG - - -def test_add(): - def int_add(a: int, b: int) -> int: - return a + b - - def float_add(a: int, b: float) -> float: - return a + b - - int_add_verifier = verifier_for(int_add) - float_add_verifier = verifier_for(float_add) - - int_add_verifier.verify(1, 1, expected_result=2) - int_add_verifier.verify(1, -1, expected_result=0) - int_add_verifier.verify(-1, 1, expected_result=0) - int_add_verifier.verify(0, 1, expected_result=1) - int_add_verifier.verify(MAX_LONG, 1, expected_result=(MAX_LONG + 1)) - int_add_verifier.verify(MIN_LONG, -1, expected_result=(MIN_LONG - 1)) - - float_add_verifier.verify(1, 1.0, expected_result=2.0) - float_add_verifier.verify(1, -1.0, expected_result=0.0) - float_add_verifier.verify(-1, 1.0, expected_result=0.0) - float_add_verifier.verify(0, 1.0, expected_result=1.0) - float_add_verifier.verify(MAX_LONG, 1.0, expected_result=(MAX_LONG + 1.0)) - float_add_verifier.verify(MIN_LONG, -1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_iadd(): - def int_iadd(x: int, y: int) -> int: - old = x - x += y - if y != 0: - assert old is not x - return x - - def float_iadd(x: int, y: float) -> float: - old = x - x += y - if y != 0: - assert old is not x - return x - - int_iadd_verifier = verifier_for(int_iadd) - float_iadd_verifier = verifier_for(float_iadd) - - int_iadd_verifier.verify(1, 1, expected_result=2) - int_iadd_verifier.verify(1, -1, expected_result=0) - int_iadd_verifier.verify(-1, 1, expected_result=0) - int_iadd_verifier.verify(0, 1, expected_result=1) - int_iadd_verifier.verify(MAX_LONG, 1, expected_result=(MAX_LONG + 1)) - int_iadd_verifier.verify(MIN_LONG, -1, expected_result=(MIN_LONG - 1)) - - float_iadd_verifier.verify(1, 1.0, expected_result=2.0) - float_iadd_verifier.verify(1, -1.0, expected_result=0.0) - float_iadd_verifier.verify(-1, 1.0, expected_result=0.0) - float_iadd_verifier.verify(0, 1.0, expected_result=1.0) - float_iadd_verifier.verify(MAX_LONG, 1.0, expected_result=(MAX_LONG + 1.0)) - float_iadd_verifier.verify(MIN_LONG, -1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_sub(): - def int_sub(a: int, b: int) -> int: - return a - b - - def float_sub(a: int, b: float) -> float: - return a - b - - int_sub_verifier = verifier_for(int_sub) - float_sub_verifier = verifier_for(float_sub) - - int_sub_verifier.verify(1, 1, expected_result=0) - int_sub_verifier.verify(1, -1, expected_result=2) - int_sub_verifier.verify(-1, 1, expected_result=-2) - int_sub_verifier.verify(0, 1, expected_result=-1) - int_sub_verifier.verify(MAX_LONG, -1, expected_result=(MAX_LONG + 1)) - int_sub_verifier.verify(MIN_LONG, 1, expected_result=(MIN_LONG - 1)) - - float_sub_verifier.verify(1, 1.0, expected_result=0.0) - float_sub_verifier.verify(1, -1.0, expected_result=2.0) - float_sub_verifier.verify(-1, 1.0, expected_result=-2.0) - float_sub_verifier.verify(0, 1.0, expected_result=-1.0) - float_sub_verifier.verify(MAX_LONG, -1.0, expected_result=(MAX_LONG + 1.0)) - float_sub_verifier.verify(MIN_LONG, 1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_isub(): - def int_isub(x: int, y: int) -> int: - old = x - x -= y - if y != 0: - assert old is not x - return x - - def float_isub(x: int, y: float) -> float: - old = x - x -= y - if y != 0: - assert old is not x - return x - - int_isub_verifier = verifier_for(int_isub) - float_isub_verifier = verifier_for(float_isub) - - int_isub_verifier.verify(1, 1, expected_result=0) - int_isub_verifier.verify(1, -1, expected_result=2) - int_isub_verifier.verify(-1, 1, expected_result=-2) - int_isub_verifier.verify(0, 1, expected_result=-1) - int_isub_verifier.verify(MAX_LONG, -1, expected_result=(MAX_LONG + 1)) - int_isub_verifier.verify(MIN_LONG, 1, expected_result=(MIN_LONG - 1)) - - float_isub_verifier.verify(1, 1.0, expected_result=0.0) - float_isub_verifier.verify(1, -1.0, expected_result=2.0) - float_isub_verifier.verify(-1, 1.0, expected_result=-2.0) - float_isub_verifier.verify(0, 1.0, expected_result=-1.0) - float_isub_verifier.verify(MAX_LONG, -1.0, expected_result=(MAX_LONG + 1.0)) - float_isub_verifier.verify(MIN_LONG, 1.0, expected_result=(MIN_LONG - 1.0)) - - -def test_multiply(): - def int_multiply(a: int, b: int) -> int: - return a * b - - def float_multiply(a: int, b: float) -> float: - return a * b - - int_multiply_verifier = verifier_for(int_multiply) - float_multiply_verifier = verifier_for(float_multiply) - - int_multiply_verifier.verify(1, 1, expected_result=1) - int_multiply_verifier.verify(1, -1, expected_result=-1) - int_multiply_verifier.verify(-1, 1, expected_result=-1) - int_multiply_verifier.verify(0, 1, expected_result=0) - int_multiply_verifier.verify(2, 3, expected_result=6) - int_multiply_verifier.verify(MAX_LONG, 2, expected_result=(2 * MAX_LONG)) - int_multiply_verifier.verify(MIN_LONG, 2, expected_result=(2 * MIN_LONG)) - - float_multiply_verifier.verify(1, 1.0, expected_result=1.0) - float_multiply_verifier.verify(1, -1.0, expected_result=-1.0) - float_multiply_verifier.verify(-1, 1.0, expected_result=-1.0) - float_multiply_verifier.verify(0, 1.0, expected_result=0.0) - float_multiply_verifier.verify(2, 3.0, expected_result=6.0) - float_multiply_verifier.verify(MAX_LONG, 2.0, expected_result=(2.0 * MAX_LONG)) - float_multiply_verifier.verify(MIN_LONG, 2.0, expected_result=(2.0 * MIN_LONG)) - - -def test_imultiply(): - def int_imultiply(x: int, y: int) -> int: - old = x - x *= y - if y != 1: - assert old is not x - return x - - def float_imultiply(x: int, y: float) -> float: - old = x - x *= y - if y != 1: - assert old is not x - return x - - int_imultiply_verifier = verifier_for(int_imultiply) - float_imultiply_verifier = verifier_for(float_imultiply) - - int_imultiply_verifier.verify(1, 1, expected_result=1) - int_imultiply_verifier.verify(1, -1, expected_result=-1) - int_imultiply_verifier.verify(-1, 1, expected_result=-1) - int_imultiply_verifier.verify(0, 1, expected_result=0) - int_imultiply_verifier.verify(2, 3, expected_result=6) - int_imultiply_verifier.verify(MAX_LONG, 2, expected_result=(2 * MAX_LONG)) - int_imultiply_verifier.verify(MIN_LONG, 2, expected_result=(2 * MIN_LONG)) - - float_imultiply_verifier.verify(1, 1.0, expected_result=1.0) - float_imultiply_verifier.verify(1, -1.0, expected_result=-1.0) - float_imultiply_verifier.verify(-1, 1.0, expected_result=-1.0) - float_imultiply_verifier.verify(0, 1.0, expected_result=0.0) - float_imultiply_verifier.verify(2, 3.0, expected_result=6.0) - float_imultiply_verifier.verify(MAX_LONG, 2.0, expected_result=(2.0 * MAX_LONG)) - float_imultiply_verifier.verify(MIN_LONG, 2.0, expected_result=(2.0 * MIN_LONG)) - - -def test_truediv(): - def int_truediv(a: int, b: int) -> float: - return a / b - - def float_truediv(a: int, b: float) -> float: - return a / b - - int_truediv_verifier = verifier_for(int_truediv) - float_truediv_verifier = verifier_for(float_truediv) - - int_truediv_verifier.verify(1, 1, expected_result=1.0) - int_truediv_verifier.verify(1, -1, expected_result=-1.0) - int_truediv_verifier.verify(-1, 1, expected_result=-1.0) - int_truediv_verifier.verify(0, 1, expected_result=0.0) - int_truediv_verifier.verify(3, 2, expected_result=1.5) - int_truediv_verifier.verify(2 * MAX_LONG, 2, expected_result=1.8446744073709552e+19) - int_truediv_verifier.verify(2 * MIN_LONG, 2, expected_result=-1.8446744073709552e+19) - int_truediv_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_truediv_verifier.verify(1, 1.0, expected_result=1.0) - float_truediv_verifier.verify(1, -1.0, expected_result=-1.0) - float_truediv_verifier.verify(-1, 1.0, expected_result=-1.0) - float_truediv_verifier.verify(0, 1.0, expected_result=0.0) - float_truediv_verifier.verify(3, 2.0, expected_result=1.5) - float_truediv_verifier.verify(2 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_truediv_verifier.verify(2 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_truediv_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_itruediv(): - def int_itruediv(x: int, y: int) -> float: - old = x - x /= y - if y != 1: - assert old is not x - return x - - def float_itruediv(x: int, y: float) -> float: - old = x - x /= y - if y != 1: - assert old is not x - return x - - int_itruediv_verifier = verifier_for(int_itruediv) - float_itruediv_verifier = verifier_for(float_itruediv) - - int_itruediv_verifier.verify(1, 1, expected_result=1.0) - int_itruediv_verifier.verify(1, -1, expected_result=-1.0) - int_itruediv_verifier.verify(-1, 1, expected_result=-1.0) - int_itruediv_verifier.verify(0, 1, expected_result=0.0) - int_itruediv_verifier.verify(3, 2, expected_result=1.5) - int_itruediv_verifier.verify(2 * MAX_LONG, 2, expected_result=1.8446744073709552e+19) - int_itruediv_verifier.verify(2 * MIN_LONG, 2, expected_result=-1.8446744073709552e+19) - int_itruediv_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_itruediv_verifier.verify(1, 1.0, expected_result=1.0) - float_itruediv_verifier.verify(1, -1.0, expected_result=-1.0) - float_itruediv_verifier.verify(-1, 1.0, expected_result=-1.0) - float_itruediv_verifier.verify(0, 1.0, expected_result=0.0) - float_itruediv_verifier.verify(3, 2.0, expected_result=1.5) - float_itruediv_verifier.verify(2 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_itruediv_verifier.verify(2 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_itruediv_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_floordiv(): - def int_floordiv(a: int, b: int) -> int: - return a // b - - def float_floordiv(a: int, b: float) -> float: - return a // b - - int_floordiv_verifier = verifier_for(int_floordiv) - float_floordiv_verifier = verifier_for(float_floordiv) - - int_floordiv_verifier.verify(1, 1, expected_result=1) - int_floordiv_verifier.verify(1, -1, expected_result=-1) - int_floordiv_verifier.verify(-1, 1, expected_result=-1) - int_floordiv_verifier.verify(0, 1, expected_result=0) - int_floordiv_verifier.verify(3, 2, expected_result=1) - int_floordiv_verifier.verify(2 * MAX_LONG, 2, expected_result=MAX_LONG) - int_floordiv_verifier.verify(2 * MIN_LONG, 2, expected_result=MIN_LONG) - int_floordiv_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_floordiv_verifier.verify(1, 1.0, expected_result=1.0) - float_floordiv_verifier.verify(1, -1.0, expected_result=-1.0) - float_floordiv_verifier.verify(-1, 1.0, expected_result=-1.0) - float_floordiv_verifier.verify(0, 1.0, expected_result=0.0) - float_floordiv_verifier.verify(3, 2.0, expected_result=1.0) - float_floordiv_verifier.verify(2 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_floordiv_verifier.verify(2 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_floordiv_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_ifloordiv(): - def int_ifloordiv(x: int, y: int) -> int: - old = x - x //= y - if y != 1: - assert old is not x - return x - - def float_ifloordiv(x: int, y: float) -> float: - old = x - x //= y - if y != 1: - assert old is not x - return x - - int_ifloordiv_verifier = verifier_for(int_ifloordiv) - float_ifloordiv_verifier = verifier_for(float_ifloordiv) - - int_ifloordiv_verifier.verify(1, 1, expected_result=1) - int_ifloordiv_verifier.verify(1, -1, expected_result=-1) - int_ifloordiv_verifier.verify(-1, 1, expected_result=-1) - int_ifloordiv_verifier.verify(0, 1, expected_result=0) - int_ifloordiv_verifier.verify(3, 2, expected_result=1) - int_ifloordiv_verifier.verify(2 * MAX_LONG, 2, expected_result=MAX_LONG) - int_ifloordiv_verifier.verify(2 * MIN_LONG, 2, expected_result=MIN_LONG) - int_ifloordiv_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_ifloordiv_verifier.verify(1, 1.0, expected_result=1.0) - float_ifloordiv_verifier.verify(1, -1.0, expected_result=-1.0) - float_ifloordiv_verifier.verify(-1, 1.0, expected_result=-1.0) - float_ifloordiv_verifier.verify(0, 1.0, expected_result=0.0) - float_ifloordiv_verifier.verify(3, 2.0, expected_result=1.0) - float_ifloordiv_verifier.verify(2 * MAX_LONG, 2.0, expected_result=1.8446744073709552e+19) - float_ifloordiv_verifier.verify(2 * MIN_LONG, 2.0, expected_result=-1.8446744073709552e+19) - float_ifloordiv_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_mod(): - def int_mod(a: int, b: int) -> int: - return a % b - - def float_mod(a: int, b: float) -> float: - return a % b - - int_mod_verifier = verifier_for(int_mod) - float_mod_verifier = verifier_for(float_mod) - - int_mod_verifier.verify(3, 4, expected_result=3) - int_mod_verifier.verify(4, 3, expected_result=1) - int_mod_verifier.verify(-4, 3, expected_result=2) - int_mod_verifier.verify(4, -3, expected_result=-2) - int_mod_verifier.verify(-4, -3, expected_result=-1) - int_mod_verifier.verify(2 * MAX_LONG, 2, expected_result=0) - int_mod_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_mod_verifier.verify(3, 4.0, expected_result=3.0) - float_mod_verifier.verify(4, 3.0, expected_result=1.0) - float_mod_verifier.verify(-4, 3.0, expected_result=2.0) - float_mod_verifier.verify(4, -3.0, expected_result=-2.0) - float_mod_verifier.verify(-4, -3.0, expected_result=-1.0) - float_mod_verifier.verify(2 * MAX_LONG, 2.0, expected_result=0.0) - float_mod_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_imod(): - def int_imod(x: int, y: int) -> int: - old = x - x %= y - if old > y: - assert old is not x - return x - - def float_imod(x: int, y: float) -> float: - old = x - x %= y - if old > y: - assert old is not x - return x - - int_imod_verifier = verifier_for(int_imod) - float_imod_verifier = verifier_for(float_imod) - - int_imod_verifier.verify(3, 4, expected_result=3) - int_imod_verifier.verify(4, 3, expected_result=1) - int_imod_verifier.verify(-4, 3, expected_result=2) - int_imod_verifier.verify(4, -3, expected_result=-2) - int_imod_verifier.verify(-4, -3, expected_result=-1) - int_imod_verifier.verify(2 * MAX_LONG, 2, expected_result=0) - int_imod_verifier.verify(1, 0, expected_error=ZeroDivisionError) - - float_imod_verifier.verify(3, 4.0, expected_result=3.0) - float_imod_verifier.verify(4, 3.0, expected_result=1.0) - float_imod_verifier.verify(-4, 3.0, expected_result=2.0) - float_imod_verifier.verify(4, -3.0, expected_result=-2.0) - float_imod_verifier.verify(-4, -3.0, expected_result=-1.0) - float_imod_verifier.verify(2 * MAX_LONG, 2.0, expected_result=0.0) - float_imod_verifier.verify(1, 0.0, expected_error=ZeroDivisionError) - - -def test_negate(): - def negate(x: int) -> int: - return -x - - negate_verifier = verifier_for(negate) - - negate_verifier.verify(1, expected_result=-1) - negate_verifier.verify(-1, expected_result=1) - negate_verifier.verify(MAX_LONG, expected_result=-MAX_LONG) - negate_verifier.verify(MIN_LONG, expected_result=-MIN_LONG) - - -def test_pos(): - def pos(x: int) -> int: - return +x - - pos_verifier = verifier_for(pos) - - pos_verifier.verify(1, expected_result=1) - pos_verifier.verify(-1, expected_result=-1) - pos_verifier.verify(MAX_LONG, expected_result=MAX_LONG) - pos_verifier.verify(MIN_LONG, expected_result=MIN_LONG) - - -def test_abs(): - def int_abs(x: int) -> int: - return abs(x) - - abs_verifier = verifier_for(int_abs) - - abs_verifier.verify(1, expected_result=1) - abs_verifier.verify(-1, expected_result=1) - abs_verifier.verify(MAX_LONG, expected_result=MAX_LONG) - abs_verifier.verify(MIN_LONG, expected_result=-MIN_LONG) - - -def test_divmod(): - def int_divmod(a: int, b: int) -> tuple: - return divmod(a, b) - - def float_divmod(a: int, b: float) -> tuple: - return divmod(a, b) - - int_divmod_verifier = verifier_for(int_divmod) - float_divmod_verifier = verifier_for(float_divmod) - - int_divmod_verifier.verify(1, 1, expected_result=(1, 0)) - int_divmod_verifier.verify(1, -1, expected_result=(-1, 0)) - int_divmod_verifier.verify(-1, 1, expected_result=(-1, 0)) - int_divmod_verifier.verify(0, 1, expected_result=(0, 0)) - int_divmod_verifier.verify(3, 2, expected_result=(1, 1)) - int_divmod_verifier.verify(-3, -2, expected_result=(1, -1)) - int_divmod_verifier.verify(2 * MAX_LONG, 2, expected_result=(MAX_LONG, 0)) - int_divmod_verifier.verify(2 * MIN_LONG, 2, expected_result=(MIN_LONG, 0)) - - float_divmod_verifier.verify(1, 1.0, expected_result=(1, 0)) - float_divmod_verifier.verify(1, -1.0, expected_result=(-1, 0)) - float_divmod_verifier.verify(-1, 1.0, expected_result=(-1, 0)) - float_divmod_verifier.verify(0, 1.0, expected_result=(0, 0)) - float_divmod_verifier.verify(3, 2.0, expected_result=(1, 1)) - float_divmod_verifier.verify(-3, -2.0, expected_result=(1, -1)) - float_divmod_verifier.verify(2 * MAX_LONG, 2.0, expected_result=(1.8446744073709552e+19, 0)) - float_divmod_verifier.verify(2 * MIN_LONG, 2.0, expected_result=(-1.8446744073709552e+19, 0)) - - -def test_pow(): - def int_pow(a: int, b: int) -> Union[int, float]: - return a ** b - - def float_pow(a: int, b: float) -> float: - return a ** b - - int_pow_verifier = verifier_for(int_pow) - float_pow_verifier = verifier_for(float_pow) - - int_pow_verifier.verify(0, 0, expected_result=1) - int_pow_verifier.verify(1, 2, expected_result=1) - int_pow_verifier.verify(2, 2, expected_result=4) - int_pow_verifier.verify(-2, 2, expected_result=4) - int_pow_verifier.verify(-2, 3, expected_result=-8) - int_pow_verifier.verify(3, 2, expected_result=9) - int_pow_verifier.verify(2, 3, expected_result=8) - int_pow_verifier.verify(2, -1, expected_result=0.5) - int_pow_verifier.verify(2, -2, expected_result=0.25) - - float_pow_verifier.verify(0, 0.0, expected_result=1.0) - float_pow_verifier.verify(1, 2.0, expected_result=1.0) - float_pow_verifier.verify(2, 2.0, expected_result=4.0) - float_pow_verifier.verify(-2, 2.0, expected_result=4.0) - float_pow_verifier.verify(-2, 3.0, expected_result=-8.0) - float_pow_verifier.verify(3, 2.0, expected_result=9.0) - float_pow_verifier.verify(2, 3.0, expected_result=8.0) - float_pow_verifier.verify(2, -1.0, expected_result=0.5) - float_pow_verifier.verify(2, -2.0, expected_result=0.25) - - -def test_mod_pow(): - def mod_pow(x: int, y: int, z: int) -> int: - return pow(x, y, z) - - mod_pow_verifier = verifier_for(mod_pow) - - mod_pow_verifier.verify(2, 3, 3, expected_result=2) - mod_pow_verifier.verify(2, -1, 3, expected_result=2) diff --git a/jpyinterpreter/tests/test_list.py b/jpyinterpreter/tests/test_list.py deleted file mode 100644 index 5e5f6034..00000000 --- a/jpyinterpreter/tests/test_list.py +++ /dev/null @@ -1,471 +0,0 @@ -from .conftest import verifier_for -from typing import Union - - -def test_membership(): - def membership(tested: list, x: object) -> bool: - return x in tested - - def not_membership(tested: list, x: object) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify([1, 2, 3], 1, expected_result=True) - not_membership_verifier.verify([1, 2, 3], 1, expected_result=False) - - membership_verifier.verify([1, 2, 3], 4, expected_result=False) - not_membership_verifier.verify([1, 2, 3], 4, expected_result=True) - - membership_verifier.verify([1, 2, 3], 3, expected_result=True) - not_membership_verifier.verify([1, 2, 3], 3, expected_result=False) - - -def test_concat(): - def concat(x: list, y: list) -> tuple: - out = x + y - return out, out is x, out is y - - concat_verifier = verifier_for(concat) - - concat_verifier.verify([1, 2], [3, 4], expected_result=([1, 2, 3, 4], False, False)) - concat_verifier.verify([], [1, 2, 3], expected_result=([1, 2, 3], False, False)) - concat_verifier.verify([1, 2, 3], [], expected_result=([1, 2, 3], False, False)) - concat_verifier.verify([3], [2, 1], expected_result=([3, 2, 1], False, False)) - - -def test_repeat(): - def left_repeat(x: list, y: int) -> tuple: - out = x * y - return out, out is x, out is y - - def right_repeat(x: int, y: list) -> tuple: - out = x * y - return out, out is x, out is y - - left_repeat_verifier = verifier_for(left_repeat) - right_repeat_verifier = verifier_for(right_repeat) - - left_repeat_verifier.verify([1, 2, 3], 2, expected_result=([1, 2, 3, 1, 2, 3], False, False)) - left_repeat_verifier.verify([1, 2], 4, expected_result=([1, 2, 1, 2, 1, 2, 1, 2], False, False)) - left_repeat_verifier.verify([1, 2, 3], 0, expected_result=([], False, False)) - left_repeat_verifier.verify([1, 2, 3], -1, expected_result=([], False, False)) - left_repeat_verifier.verify([1, 2, 3], -2, expected_result=([], False, False)) - - right_repeat_verifier.verify(2, [1, 2, 3], expected_result=([1, 2, 3, 1, 2, 3], False, False)) - right_repeat_verifier.verify(4, [1, 2], expected_result=([1, 2, 1, 2, 1, 2, 1, 2], False, False)) - right_repeat_verifier.verify(0, [1, 2, 3], expected_result=([], False, False)) - right_repeat_verifier.verify(-1, [1, 2, 3], expected_result=([], False, False)) - right_repeat_verifier.verify(-2, [1, 2, 3], expected_result=([], False, False)) - - -def test_get_item(): - def get_item(tested: list, index: int) -> int: - return tested[index] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify([1, 2, 3], 1, expected_result=2) - get_item_verifier.verify([1, 2, 3], -1, expected_result=3) - get_item_verifier.verify([1, 2, 3, 4], -1, expected_result=4) - get_item_verifier.verify([1, 2, 3, 4], -2, expected_result=3) - get_item_verifier.verify([1, 2, 3, 4], 0, expected_result=1) - get_item_verifier.verify([1, 2, 3], 3, expected_error=IndexError) - get_item_verifier.verify([1, 2, 3], -4, expected_error=IndexError) - - -def test_get_slice(): - def get_slice(tested: list, start: Union[int, None], end: Union[int, None]) -> list: - return tested[start:end] - - get_slice_verifier = verifier_for(get_slice) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, expected_result=[2, 3]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -3, -1, expected_result=[3, 4]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, -2, expected_result=[1, 2, 3]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -3, 4, expected_result=[3, 4]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 3, 1, expected_result=[]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -1, -3, expected_result=[]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 100, 1000, expected_result=[]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, 1000, expected_result=[1, 2, 3, 4, 5]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 1, None, expected_result=[2, 3, 4, 5]) - get_slice_verifier.verify([1, 2, 3, 4, 5], None, 2, expected_result=[1, 2]) - get_slice_verifier.verify([1, 2, 3, 4, 5], None, None, expected_result=[1, 2, 3, 4, 5]) - - -def test_get_slice_with_step(): - def get_slice_with_step(tested: list, start: Union[int, None], end: Union[int, None], - step: Union[int, None]) -> list: - return tested[start:end:step] - - get_slice_verifier = verifier_for(get_slice_with_step) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, None, 2, expected_result=[1, 3, 5]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 1, None, 2, expected_result=[2, 4]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, 5, 2, expected_result=[1, 3, 5]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 1, 5, 2, expected_result=[2, 4]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, -1, 2, expected_result=[1, 3]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 1, -1, 2, expected_result=[2, 4]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 4, None, -2, expected_result=[5, 3, 1]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 3, None, -2, expected_result=[4, 2]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -1, -6, -2, expected_result=[5, 3, 1]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -2, -6, -2, expected_result=[4, 2]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 4, 0, -2, expected_result=[5, 3]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 3, 0, -2, expected_result=[4, 2]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, None, None, expected_result=[1, 2, 3, 4, 5]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 0, 3, None, expected_result=[1, 2, 3]) - - get_slice_verifier.verify([1, 2, 3, 4, 5], 3, 1, -1, expected_result=[4, 3]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -1, -3, -1, expected_result=[5, 4]) - get_slice_verifier.verify([1, 2, 3, 4, 5], 3, 1, 1, expected_result=[]) - get_slice_verifier.verify([1, 2, 3, 4, 5], -1, -3, 1, expected_result=[]) - - -def test_len(): - def length(tested: list) -> int: - return len(tested) - - len_verifier = verifier_for(length) - - len_verifier.verify([], expected_result=0) - len_verifier.verify([1], expected_result=1) - len_verifier.verify([1, 2], expected_result=2) - len_verifier.verify([3, 2, 1], expected_result=3) - - -def test_index(): - def index(tested: list, item: object) -> int: - return tested.index(item) - - def index_start(tested: list, item: object, start: int) -> int: - return tested.index(item, start) - - def index_start_end(tested: list, item: object, start: int, end: int) -> int: - return tested.index(item, start, end) - - index_verifier = verifier_for(index) - index_start_verifier = verifier_for(index_start) - index_start_end_verifier = verifier_for(index_start_end) - - index_verifier.verify([1, 2, 3], 1, expected_result=0) - index_verifier.verify([1, 2, 3], 2, expected_result=1) - index_verifier.verify([1, 2, 3], 5, expected_error=ValueError) - - index_start_verifier.verify([1, 2, 3], 1, 1, expected_error=ValueError) - index_start_verifier.verify([1, 2, 3], 2, 1, expected_result=1) - index_start_verifier.verify([1, 2, 3], 3, 1, expected_result=2) - index_start_verifier.verify([1, 2, 3], 5, 1, expected_error=ValueError) - - index_start_verifier.verify([1, 2, 3], 1, -2, expected_error=ValueError) - index_start_verifier.verify([1, 2, 3], 2, -2, expected_result=1) - index_start_verifier.verify([1, 2, 3], 3, -2, expected_result=2) - index_start_verifier.verify([1, 2, 3], 5, -2, expected_error=ValueError) - - index_start_end_verifier.verify([1, 2, 3], 1, 1, 2, expected_error=ValueError) - index_start_end_verifier.verify([1, 2, 3], 2, 1, 2, expected_result=1) - index_start_end_verifier.verify([1, 2, 3], 3, 1, 2, expected_error=ValueError) - index_start_end_verifier.verify([1, 2, 3], 5, 1, 2, expected_error=ValueError) - - index_start_end_verifier.verify([1, 2, 3], 1, -2, -1, expected_error=ValueError) - index_start_end_verifier.verify([1, 2, 3], 2, -2, -1, expected_result=1) - index_start_end_verifier.verify([1, 2, 3], 3, -2, -1, expected_error=ValueError) - index_start_end_verifier.verify([1, 2, 3], 5, -2, -1, expected_error=ValueError) - - -def test_count(): - def count(tested: list, item: object) -> int: - return tested.count(item) - - count_verifier = verifier_for(count) - - count_verifier.verify([1, 2, 3], 1, expected_result=1) - count_verifier.verify([1, 2, 3], 2, expected_result=1) - count_verifier.verify([1, 2, 3], 3, expected_result=1) - count_verifier.verify([1, 2, 3], 4, expected_result=0) - - count_verifier.verify([1, 2, 3, 1], 1, expected_result=2) - count_verifier.verify([1, 1, 3, 1], 1, expected_result=3) - count_verifier.verify([], 1, expected_result=0) - - -def test_set_item(): - def set_item(tested: list, index: int, value: object) -> list: - tested[index] = value - return tested - - set_item_verifier = verifier_for(set_item) - set_item_verifier.verify([1, 2, 3, 4, 5], 0, 10, expected_result=[10, 2, 3, 4, 5]) - set_item_verifier.verify([1, 2, 3, 4, 5], 2, -3, expected_result=[1, 2, -3, 4, 5]) - set_item_verifier.verify([1, 2, 3, 4, 5], -1, -5, expected_result=[1, 2, 3, 4, -5]) - set_item_verifier.verify([1, 2, 3, 4, 5], -10, 1, expected_error=IndexError) - set_item_verifier.verify([1, 2, 3, 4, 5], 10, 1, expected_error=IndexError) - -def test_set_slice(): - def set_slice(tested: list, start: Union[int, None], stop: Union[int, None], value: list) -> list: - tested[start:stop] = value - return tested - - set_slice_verifier = verifier_for(set_slice) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, [], expected_result=[1, 4, 5]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 2, 2, [30], expected_result=[1, 2, 30, 3, 4, 5]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, [20], expected_result=[1, 20, 4, 5]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, [20, 30], expected_result=[1, 20, 30, 4, 5]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, [20, 30, 40], expected_result=[1, 20, 30, 40, 4, 5]) - - set_slice_verifier.verify([1, 2, 3, 4, 5], -4, -2, [20, 30], expected_result=[1, 20, 30, 4, 5]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, -2, [20, 30], expected_result=[1, 20, 30, 4, 5]) - - set_slice_verifier.verify([1, 2, 3, 4, 5], 5, 5, [6], expected_result=[1, 2, 3, 4, 5, 6]) - - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, None, [], expected_result=[1]) - set_slice_verifier.verify([1, 2, 3, 4, 5], 1, None, [20, 30], expected_result=[1, 20, 30]) - - -def test_delete_slice(): - def delete_slice(tested: list, start: Union[int, None], stop: Union[int, None]) -> list: - del tested[start:stop] - return tested - - delete_slice_verifier = verifier_for(delete_slice) - delete_slice_verifier.verify([1, 2, 3, 4, 5], 1, 3, expected_result=[1, 4, 5]) - delete_slice_verifier.verify([1, 2, 3, 4, 5], 3, 5, expected_result=[1, 2, 3]) - delete_slice_verifier.verify([1, 2, 3, 4, 5], 1, None, expected_result=[1]) - delete_slice_verifier.verify([1, 2, 3, 4, 5], None, 3, expected_result=[4, 5]) - delete_slice_verifier.verify([1, 2, 3, 4, 5], None, None, expected_result=[]) - - -def test_set_slice_with_step(): - def set_slice_with_step(tested: list, start: Union[int, None], stop: Union[int, None], step: Union[int, None], - value: list) -> list: - tested[start:stop:step] = value - return tested - - set_slice_with_step_verifier = verifier_for(set_slice_with_step) - - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 3, 0, -1, [20, 30, 40], expected_result=[1, 40, 30, 20, 5]) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 1, 4, 2, [20, 40], expected_result=[1, 20, 3, 40, 5]) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 1, -1, 2, [20, 40], expected_result=[1, 20, 3, 40, 5]) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 0, None, 2, [10, 30, 50], - expected_result=[10, 2, 30, 4, 50]) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], None, 4, 2, [10, 30], - expected_result=[10, 2, 30, 4, 5]) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], None, None, 2, [10, 30, 50], - expected_result=[10, 2, 30, 4, 50]) - - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 3, 0, -1, [], expected_error=ValueError) - set_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 3, 0, -1, [20, 30, 40, 50], expected_error=ValueError) - - -def test_delete_slice_with_step(): - def delete_slice_with_step(tested: list, start: Union[int, None], stop: Union[int, None], - step: Union[int, None]) -> list: - del tested[start:stop:step] - return tested - - delete_slice_with_step_verifier = verifier_for(delete_slice_with_step) - - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 3, 0, -1, expected_result=[1, 5]) - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 1, 4, 2, expected_result=[1, 3, 5]) - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 1, -1, 2, expected_result=[1, 3, 5]) - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], 0, None, 2, - expected_result=[2, 4]) - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], None, 4, 2, - expected_result=[2, 4, 5]) - delete_slice_with_step_verifier.verify([1, 2, 3, 4, 5], None, None, 2, - expected_result=[2, 4]) - - -def test_append(): - def append(tested: list, item: object) -> list: - tested.append(item) - return tested - - append_verifier = verifier_for(append) - - append_verifier.verify([], 1, expected_result=[1]) - append_verifier.verify([1], 2, expected_result=[1, 2]) - append_verifier.verify([1, 2], 3, expected_result=[1, 2, 3]) - append_verifier.verify([1, 2, 3], 3, expected_result=[1, 2, 3, 3]) - - -def test_clear(): - def clear(tested: list) -> list: - tested.clear() - return tested - - clear_verifier = verifier_for(clear) - - clear_verifier.verify([], expected_result=[]) - clear_verifier.verify([1], expected_result=[]) - clear_verifier.verify([1, 2], expected_result=[]) - clear_verifier.verify([1, 2, 3], expected_result=[]) - - -def test_copy(): - def copy(tested: list) -> tuple: - out = tested.copy() - return out, out is tested - - copy_verifier = verifier_for(copy) - - copy_verifier.verify([], expected_result=([], False)) - copy_verifier.verify([1], expected_result=([1], False)) - copy_verifier.verify([1, 2], expected_result=([1, 2], False)) - copy_verifier.verify([1, 2, 3], expected_result=([1, 2, 3], False)) - - -def test_extend(): - def extend(tested: list, item: object) -> list: - tested.extend(item) - return tested - - extend_verifier = verifier_for(extend) - - extend_verifier.verify([], [1], expected_result=[1]) - extend_verifier.verify([1], [2], expected_result=[1, 2]) - extend_verifier.verify([1, 2], [3], expected_result=[1, 2, 3]) - extend_verifier.verify([1, 2, 3], [], expected_result=[1, 2, 3]) - extend_verifier.verify([1, 2, 3], [4, 5], expected_result=[1, 2, 3, 4, 5]) - extend_verifier.verify([1, 2, 3], [[4, 5], [6, 7]], expected_result=[1, 2, 3, [4, 5], [6, 7]]) - - -def test_inplace_add(): - def extend(tested: list, item: list) -> list: - tested += item - return tested - - extend_verifier = verifier_for(extend) - - extend_verifier.verify([], [1], expected_result=[1]) - extend_verifier.verify([1], [2], expected_result=[1, 2]) - extend_verifier.verify([1, 2], [3], expected_result=[1, 2, 3]) - extend_verifier.verify([1, 2, 3], [], expected_result=[1, 2, 3]) - extend_verifier.verify([1, 2, 3], [4, 5], expected_result=[1, 2, 3, 4, 5]) - extend_verifier.verify([1, 2, 3], [[4, 5], [6, 7]], expected_result=[1, 2, 3, [4, 5], [6, 7]]) - - -def test_inplace_multiply(): - def multiply(tested: list, item: int) -> list: - tested *= item - return tested - - multiply_verifier = verifier_for(multiply) - - multiply_verifier.verify([1, 2, 3], 1, expected_result=[1, 2, 3]) - multiply_verifier.verify([1, 2], 2, expected_result=[1, 2, 1, 2]) - multiply_verifier.verify([1, 2], 3, expected_result=[1, 2, 1, 2, 1, 2]) - multiply_verifier.verify([1, 2, 3], 0, expected_result=[]) - multiply_verifier.verify([1, 2, 3], -1, expected_result=[]) - - - -def test_insert(): - def insert(tested: list, index: int, item: object) -> list: - tested.insert(index, item) - return tested - - insert_verifier = verifier_for(insert) - - insert_verifier.verify([], 0, 1, expected_result=[1]) - insert_verifier.verify([1], 0, 2, expected_result=[2, 1]) - insert_verifier.verify([1], 1, 2, expected_result=[1, 2]) - insert_verifier.verify([1, 2], 0, 3, expected_result=[3, 1, 2]) - insert_verifier.verify([1, 2], 1, 3, expected_result=[1, 3, 2]) - insert_verifier.verify([1, 2], 2, 3, expected_result=[1, 2, 3]) - insert_verifier.verify([1, 2, 3], -1, 4, expected_result=[1, 2, 4, 3]) - insert_verifier.verify([1, 2, 3], -2, 4, expected_result=[1, 4, 2, 3]) - insert_verifier.verify([1, 2, 3], 3, 4, expected_result=[1, 2, 3, 4]) - insert_verifier.verify([1, 2, 3], 4, 4, expected_result=[1, 2, 3, 4]) - insert_verifier.verify([1, 2, 3], -4, 4, expected_result=[4, 1, 2, 3]) - insert_verifier.verify([1, 2, 3], -5, 4, expected_result=[4, 1, 2, 3]) - - -def test_pop(): - def pop(tested: list) -> tuple: - item = tested.pop() - return item, tested - - pop_verifier = verifier_for(pop) - - pop_verifier.verify([1, 2, 3], expected_result=(3, [1, 2])) - pop_verifier.verify([1, 2], expected_result=(2, [1])) - pop_verifier.verify([1], expected_result=(1, [])) - - pop_verifier.verify([1, 2, 5], expected_result=(5, [1, 2])) - - pop_verifier.verify([], expected_error=IndexError) - - -def test_pop_at_index(): - def pop_at_index(tested: list, index: int) -> tuple: - item = tested.pop(index) - return item, tested - - pop_at_index_verifier = verifier_for(pop_at_index) - - pop_at_index_verifier.verify([1, 2, 3], -1, expected_result=(3, [1, 2])) - pop_at_index_verifier.verify([1, 2], -1, expected_result=(2, [1])) - pop_at_index_verifier.verify([1], -1, expected_result=(1, [])) - - pop_at_index_verifier.verify([1, 2, 3], 1, expected_result=(2, [1, 3])) - pop_at_index_verifier.verify([1, 2, 3], 0, expected_result=(1, [2, 3])) - pop_at_index_verifier.verify([1, 2, 3], 2, expected_result=(3, [1, 2])) - pop_at_index_verifier.verify([1, 2, 3], -2, expected_result=(2, [1, 3])) - - pop_at_index_verifier.verify([1, 2, 3], -4, expected_error=IndexError) - pop_at_index_verifier.verify([1, 2, 3], 4, expected_error=IndexError) - pop_at_index_verifier.verify([], 0, expected_error=IndexError) - - -def test_remove(): - def remove(tested: list, item: object) -> list: - tested.remove(item) - return tested - - remove_verifier = verifier_for(remove) - - remove_verifier.verify([1, 2, 3], 1, expected_result=[2, 3]) - remove_verifier.verify([1, 2, 3], 2, expected_result=[1, 3]) - remove_verifier.verify([1, 2, 3], 3, expected_result=[1, 2]) - - remove_verifier.verify([1, 3, 5], 3, expected_result=[1, 5]) - - remove_verifier.verify([1, 2, 3], 4, expected_error=ValueError) - remove_verifier.verify([], 1, expected_error=ValueError) - - -def test_reverse(): - def reverse(tested: list) -> list: - tested.reverse() - return tested - - reverse_verifier = verifier_for(reverse) - - reverse_verifier.verify([1, 2, 3], expected_result=[3, 2, 1]) - reverse_verifier.verify([3, 2, 1], expected_result=[1, 2, 3]) - reverse_verifier.verify([1, 2], expected_result=[2, 1]) - reverse_verifier.verify([2, 1], expected_result=[1, 2]) - reverse_verifier.verify([1], expected_result=[1]) - reverse_verifier.verify([], expected_result=[]) - - -def test_sort(): - def sort(tested: list) -> list: - tested.sort() - return tested - - sort_verifier = verifier_for(sort) - - sort_verifier.verify([1, 2, 3], expected_result=[1, 2, 3]) - sort_verifier.verify([1, 3, 2], expected_result=[1, 2, 3]) - sort_verifier.verify([2, 1, 3], expected_result=[1, 2, 3]) - sort_verifier.verify([2, 3, 1], expected_result=[1, 2, 3]) - sort_verifier.verify([3, 1, 2], expected_result=[1, 2, 3]) - sort_verifier.verify([3, 2, 1], expected_result=[1, 2, 3]) diff --git a/jpyinterpreter/tests/test_loops.py b/jpyinterpreter/tests/test_loops.py deleted file mode 100644 index 082609c3..00000000 --- a/jpyinterpreter/tests/test_loops.py +++ /dev/null @@ -1,171 +0,0 @@ -from .conftest import verifier_for - - -def test_while_loops(): - def my_function(x: int) -> int: - total = 0 - while x > 0: - total += x - x = x - 1 - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify(0, expected_result=0) - function_verifier.verify(1, expected_result=1) - function_verifier.verify(2, expected_result=3) - function_verifier.verify(3, expected_result=6) - function_verifier.verify(4, expected_result=10) - function_verifier.verify(5, expected_result=15) - - -def test_inner_loops(): - def my_function(x: int, y: int) -> int: - total = 0 - while x > 0: - remaining = y - while remaining > 0: - total += remaining * x - remaining = remaining - 1 - x = x - 1 - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify(0, 0, expected_result=0) - function_verifier.verify(1, 0, expected_result=0) - function_verifier.verify(1, 1, expected_result=1) - function_verifier.verify(1, 2, expected_result=3) - function_verifier.verify(1, 3, expected_result=6) - function_verifier.verify(2, 1, expected_result=3) - function_verifier.verify(2, 2, expected_result=9) - function_verifier.verify(2, 3, expected_result=18) - - -def test_iterable_loops(): - def my_function(x: list) -> int: - total = 0 - for item in x: - total += item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify([], expected_result=0) - function_verifier.verify([1, 2, 3], expected_result=6) - function_verifier.verify([10, 20, 30], expected_result=60) - - -def test_inner_iterable_loops(): - def my_function(x: list, y: list) -> int: - total = 0 - for x_item in x: - for y_item in y: - total += x_item * y_item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify([], [], expected_result=0) - function_verifier.verify([], [1, 2, 3], expected_result=0) - function_verifier.verify([1, 2, 3], [], expected_result=0) - - function_verifier.verify([1, 2, 3], [1, 2], expected_result=18) - function_verifier.verify([1, 2], [-1, 3], expected_result=6) - - -def test_breaks_in_iterable_loop(): - def my_function(x: list) -> int: - total = 0 - for item in x: - if item == 0: - break - total += item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify([], expected_result=0) - function_verifier.verify([1, 0, 3], expected_result=1) - function_verifier.verify([1, 2, 3], expected_result=6) - function_verifier.verify([1, 2, 3, 0, 4], expected_result=6) - - -def test_continues_in_iterable_loop(): - def my_function(x: list) -> int: - total = 0 - for item in x: - if item == 5: - continue - total += item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify([], expected_result=0) - function_verifier.verify([1, 5, 3], expected_result=4) - function_verifier.verify([1, 2, 3], expected_result=6) - function_verifier.verify([1, 2, 3, 5, 4], expected_result=10) - - -def test_iterating_generator(): - def my_generator(x: int): - for i in range(x + 1): - yield i - - def my_function(x: int) -> int: - total = 0 - for item in my_generator(x): - total += item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify(0, expected_result=0) - function_verifier.verify(1, expected_result=1) - function_verifier.verify(2, expected_result=3) - function_verifier.verify(3, expected_result=6) - - -def test_try_except_in_loop(): - def my_function(x: int) -> int: - total = 0 - for item in range(x + 1): - try: - if item % 2 == 0: - raise ValueError - total += item - except ValueError: - total -= item - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify(0, expected_result=0) - function_verifier.verify(1, expected_result=1) - function_verifier.verify(2, expected_result=-1) - function_verifier.verify(3, expected_result=2) - function_verifier.verify(4, expected_result=-2) - function_verifier.verify(5, expected_result=3) - function_verifier.verify(6, expected_result=-3) - - -def test_try_except_outside_loop(): - def my_function(x: list) -> int: - total = 0 - try: - for item in x: - if item == 0: - raise ValueError - total += item - except ValueError: - return 0 - return total - - function_verifier = verifier_for(my_function) - - function_verifier.verify([], expected_result=0) - function_verifier.verify([1], expected_result=1) - function_verifier.verify([1, 2], expected_result=3) - function_verifier.verify([1, 0, 2], expected_result=0) - function_verifier.verify([1, 2, 3, 0], expected_result=0) diff --git a/jpyinterpreter/tests/test_modules.py b/jpyinterpreter/tests/test_modules.py deleted file mode 100644 index d85dfa01..00000000 --- a/jpyinterpreter/tests/test_modules.py +++ /dev/null @@ -1,43 +0,0 @@ -from .conftest import verifier_for - - -def test_import(): - def function(x: int) -> int: - import datetime - return datetime.timedelta(days=x).days - - verifier = verifier_for(function) - - verifier.verify(0, expected_result=0) - verifier.verify(1, expected_result=1) - verifier.verify(2, expected_result=2) - - -def test_import_from(): - def function(x: int) -> int: - from datetime import timedelta - return timedelta(days=x).days - - verifier = verifier_for(function) - - verifier.verify(0, expected_result=0) - verifier.verify(1, expected_result=1) - verifier.verify(2, expected_result=2) - - -def test_import_native(): - def function(x): - import math # TODO: Use another native module once math is builtin to jpyinterpreter - return math.ceil(x) - - verifier = verifier_for(function) - - verifier.verify(-1.7, expected_result=-1) - verifier.verify(-1, expected_result=-1) - verifier.verify(-0.4, expected_result=0) - verifier.verify(0, expected_result=0) - verifier.verify(0.5, expected_result=1) - verifier.verify(1, expected_result=1) - verifier.verify(1.1, expected_result=2) - verifier.verify(2.1, expected_result=3) - verifier.verify(2, expected_result=2) diff --git a/jpyinterpreter/tests/test_set.py b/jpyinterpreter/tests/test_set.py deleted file mode 100644 index 0bb7c317..00000000 --- a/jpyinterpreter/tests/test_set.py +++ /dev/null @@ -1,386 +0,0 @@ -from .conftest import verifier_for - - -def test_len(): - def length(tested: set) -> int: - return len(tested) - - len_verifier = verifier_for(length) - len_verifier.verify(set(), expected_result=0) - len_verifier.verify({1, 2, 3}, expected_result=3) - - -def test_membership(): - def membership(tested: set, x: object) -> bool: - return x in tested - - def not_membership(tested: set, x: object) -> bool: - return x not in tested - - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify(set(), 1, expected_result=False) - not_membership_verifier.verify(set(), 1, expected_result=True) - - membership_verifier.verify({1, 2, 3}, 1, expected_result=True) - not_membership_verifier.verify({1, 2, 3}, 1, expected_result=False) - - -def test_isdisjoint(): - def isdisjoint(x: set, y: set) -> bool: - return x.isdisjoint(y) - - isdisjoint_verifier = verifier_for(isdisjoint) - - isdisjoint_verifier.verify({1, 2, 3}, {4, 5, 6}, expected_result=True) - isdisjoint_verifier.verify({1, 2, 3}, {3, 4, 5}, expected_result=False) - - -def test_issubset(): - def issubset(x: set, y: set) -> bool: - return x.issubset(y) - - def issubset_le(x: set, y: set) -> bool: - return x <= y - - def issubset_strict(x: set, y: set) -> bool: - return x < y - - issubset_verifier = verifier_for(issubset) - subset_le_verifier = verifier_for(issubset_le) - subset_strict_verifier = verifier_for(issubset_strict) - - issubset_verifier.verify(set(), {1, 2, 3}, expected_result=True) - subset_le_verifier.verify(set(), {1, 2, 3}, expected_result=True) - subset_strict_verifier.verify(set(), {1, 2, 3}, expected_result=True) - - issubset_verifier.verify({1}, {1, 2, 3}, expected_result=True) - subset_le_verifier.verify({1}, {1, 2, 3}, expected_result=True) - subset_strict_verifier.verify({1}, {1, 2, 3}, expected_result=True) - - issubset_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=True) - subset_le_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=True) - subset_strict_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=False) - - issubset_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - subset_le_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - subset_strict_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - - issubset_verifier.verify({1, 2, 3}, {1}, expected_result=False) - subset_le_verifier.verify({1, 2, 3}, {1}, expected_result=False) - subset_strict_verifier.verify({1, 2, 3}, {1}, expected_result=False) - - issubset_verifier.verify({1, 2, 3}, set(), expected_result=False) - subset_le_verifier.verify({1, 2, 3}, set(), expected_result=False) - subset_strict_verifier.verify({1, 2, 3}, set(), expected_result=False) - - -def test_issuperset(): - def issuperset(x: set, y: set) -> bool: - return x.issuperset(y) - - def issuperset_ge(x: set, y: set) -> bool: - return x >= y - - def issuperset_strict(x: set, y: set) -> bool: - return x > y - - issuperset_verifier = verifier_for(issuperset) - superset_ge_verifier = verifier_for(issuperset_ge) - superset_strict_verifier = verifier_for(issuperset_strict) - - issuperset_verifier.verify(set(), {1, 2, 3}, expected_result=False) - superset_ge_verifier.verify(set(), {1, 2, 3}, expected_result=False) - superset_strict_verifier.verify(set(), {1, 2, 3}, expected_result=False) - - issuperset_verifier.verify({1}, {1, 2, 3}, expected_result=False) - superset_ge_verifier.verify({1}, {1, 2, 3}, expected_result=False) - superset_strict_verifier.verify({1}, {1, 2, 3}, expected_result=False) - - issuperset_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=True) - superset_ge_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=True) - superset_strict_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=False) - - issuperset_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - superset_ge_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - superset_strict_verifier.verify({1, 4}, {1, 2, 3}, expected_result=False) - - issuperset_verifier.verify({1, 2, 3}, {1}, expected_result=True) - superset_ge_verifier.verify({1, 2, 3}, {1}, expected_result=True) - superset_strict_verifier.verify({1, 2, 3}, {1}, expected_result=True) - - issuperset_verifier.verify({1, 2, 3}, set(), expected_result=True) - superset_ge_verifier.verify({1, 2, 3}, set(), expected_result=True) - superset_strict_verifier.verify({1, 2, 3}, set(), expected_result=True) - - -def test_union(): - def union(x: set, y: set) -> set: - return x.union(y) - - def union_or(x: set, y: set) -> set: - return x | y - - union_verifier = verifier_for(union) - union_or_verifier = verifier_for(union_or) - - union_verifier.verify({1}, {2}, expected_result={1, 2}) - union_or_verifier.verify({1}, {2}, expected_result={1, 2}) - - union_verifier.verify({1, 2}, {2, 3}, expected_result={1, 2, 3}) - union_or_verifier.verify({1, 2}, {2, 3}, expected_result={1, 2, 3}) - - union_verifier.verify(set(), {1}, expected_result={1}) - union_verifier.verify(set(), {1}, expected_result={1}) - - union_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result={1, 2, 3}) - union_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result={1, 2, 3}) - - -def test_intersection(): - def intersection(x: set, y: set) -> set: - return x.intersection(y) - - def intersection_and(x: set, y: set) -> set: - return x & y - - intersection_verifier = verifier_for(intersection) - intersection_and_verifier = verifier_for(intersection_and) - - intersection_verifier.verify({1}, {2}, expected_result=set()) - intersection_and_verifier.verify({1}, {2}, expected_result=set()) - - intersection_verifier.verify({1, 2}, {2, 3}, expected_result={2}) - intersection_and_verifier.verify({1, 2}, {2, 3}, expected_result={2}) - - intersection_verifier.verify(set(), {1}, expected_result=set()) - intersection_and_verifier.verify(set(), {1}, expected_result=set()) - - intersection_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result={1, 2, 3}) - intersection_and_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result={1, 2, 3}) - - -def test_difference(): - def difference(x: set, y: set) -> set: - return x.difference(y) - - def difference_subtract(x: set, y: set) -> set: - return x - y - - difference_verifier = verifier_for(difference) - difference_subtract_verifier = verifier_for(difference_subtract) - - difference_verifier.verify({1}, {2}, expected_result={1}) - difference_subtract_verifier.verify({1}, {2}, expected_result={1}) - - difference_verifier.verify({1, 2}, {2, 3}, expected_result={1}) - difference_subtract_verifier.verify({1, 2}, {2, 3}, expected_result={1}) - - difference_verifier.verify({2, 3}, {1, 2}, expected_result={3}) - difference_subtract_verifier.verify({2, 3}, {1, 2}, expected_result={3}) - - difference_verifier.verify(set(), {1}, expected_result=set()) - difference_subtract_verifier.verify(set(), {1}, expected_result=set()) - - difference_verifier.verify({1}, set(), expected_result={1}) - difference_subtract_verifier.verify({1}, set(), expected_result={1}) - - difference_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=set()) - difference_subtract_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=set()) - - -def test_symmetric_difference(): - def symmetric_difference(x: set, y: set) -> set: - return x.symmetric_difference(y) - - def symmetric_difference_xor(x: set, y: set) -> set: - return x ^ y - - symmetric_difference_verifier = verifier_for(symmetric_difference) - symmetric_difference_xor_verifier = verifier_for(symmetric_difference_xor) - - symmetric_difference_verifier.verify({1}, {2}, expected_result={1, 2}) - symmetric_difference_xor_verifier.verify({1}, {2}, expected_result={1, 2}) - - symmetric_difference_verifier.verify({1, 2}, {2, 3}, expected_result={1, 3}) - symmetric_difference_xor_verifier.verify({1, 2}, {2, 3}, expected_result={1, 3}) - - symmetric_difference_verifier.verify({2, 3}, {1, 2}, expected_result={1, 3}) - symmetric_difference_xor_verifier.verify({2, 3}, {1, 2}, expected_result={1, 3}) - - symmetric_difference_verifier.verify(set(), {1}, expected_result={1}) - symmetric_difference_xor_verifier.verify(set(), {1}, expected_result={1}) - - symmetric_difference_verifier.verify({1}, set(), expected_result={1}) - symmetric_difference_xor_verifier.verify({1}, set(), expected_result={1}) - - symmetric_difference_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=set()) - symmetric_difference_xor_verifier.verify({1, 2, 3}, {1, 2, 3}, expected_result=set()) - - -def test_copy(): - def copy_function(x: set) -> tuple: - out = x.copy() - return out, out is x - - copy_verifier = verifier_for(copy_function) - - copy_verifier.verify(set(), expected_result=(set(), False)) - copy_verifier.verify({1}, expected_result=({1}, False)) - copy_verifier.verify({1, 2, 3}, expected_result=({1, 2, 3}, False)) - - -def test_update(): - def update_function(x: set, y: set) -> set: - x.update(y) - return x - - def update_ior_function(x: set, y: set) -> set: - x |= y - return x - - update_verifier = verifier_for(update_function) - update_ior_verifier = verifier_for(update_ior_function) - - update_verifier.verify(set(), {1, 2}, expected_result={1, 2}) - update_ior_verifier.verify(set(), {1, 2}, expected_result={1, 2}) - - update_verifier.verify({1, 2}, {3}, expected_result={1, 2, 3}) - update_ior_verifier.verify({1, 2}, {3}, expected_result={1, 2, 3}) - - update_verifier.verify({3}, {1, 2}, expected_result={1, 2, 3}) - update_ior_verifier.verify({3}, {1, 2}, expected_result={1, 2, 3}) - - -def test_intersection_update(): - def intersection_update_function(x: set, y: set) -> set: - x.intersection_update(y) - return x - - def intersection_update_iand_function(x: set, y: set) -> set: - x &= y - return x - - intersection_update_verifier = verifier_for(intersection_update_function) - intersection_update_iand_verifier = verifier_for(intersection_update_iand_function) - - intersection_update_verifier.verify(set(), {1, 2}, expected_result=set()) - intersection_update_iand_verifier.verify(set(), {1, 2}, expected_result=set()) - - intersection_update_verifier.verify({1, 2}, {3}, expected_result=set()) - intersection_update_iand_verifier.verify({1, 2}, {3}, expected_result=set()) - - intersection_update_verifier.verify({1, 2, 3}, {2}, expected_result={2}) - intersection_update_iand_verifier.verify({1, 2, 3}, {2}, expected_result={2}) - - -def test_difference_update(): - def difference_update_function(x: set, y: set) -> set: - x.difference_update(y) - return x - - def difference_update_isub_function(x: set, y: set) -> set: - x -= y - return x - - difference_update_verifier = verifier_for(difference_update_function) - difference_update_isub_verifier = verifier_for(difference_update_isub_function) - - difference_update_verifier.verify(set(), {1, 2}, expected_result=set()) - difference_update_isub_verifier.verify(set(), {1, 2}, expected_result=set()) - - difference_update_verifier.verify({1, 2}, {3}, expected_result={1, 2}) - difference_update_isub_verifier.verify({1, 2}, {3}, expected_result={1, 2}) - - difference_update_verifier.verify({1, 2, 3}, {2}, expected_result={1, 3}) - difference_update_isub_verifier.verify({1, 2, 3}, {2}, expected_result={1, 3}) - - -def test_symmetric_difference_update(): - def symmetric_difference_update_function(x: set, y: set) -> set: - x.symmetric_difference_update(y) - return x - - def symmetric_difference_update_ixor_function(x: set, y: set) -> set: - x ^= y - return x - - symmetric_difference_update_verifier = verifier_for(symmetric_difference_update_function) - symmetric_difference_update_ixor_verifier = verifier_for(symmetric_difference_update_ixor_function) - - symmetric_difference_update_verifier.verify(set(), {1, 2}, expected_result={1, 2}) - symmetric_difference_update_ixor_verifier.verify(set(), {1, 2}, expected_result={1, 2}) - - symmetric_difference_update_verifier.verify({1, 2}, {3}, expected_result={1, 2, 3}) - symmetric_difference_update_ixor_verifier.verify({1, 2}, {3}, expected_result={1, 2, 3}) - - symmetric_difference_update_verifier.verify({1, 2, 3}, {2}, expected_result={1, 3}) - symmetric_difference_update_ixor_verifier.verify({1, 2, 3}, {2}, expected_result={1, 3}) - - symmetric_difference_update_verifier.verify({2}, {1, 2, 3}, expected_result={1, 3}) - symmetric_difference_update_ixor_verifier.verify({2}, {1, 2, 3}, expected_result={1, 3}) - - -def test_add(): - def add_function(x: set, y: object) -> set: - x.add(y) - return x - - add_verifier = verifier_for(add_function) - - add_verifier.verify(set(), 1, expected_result={1}) - add_verifier.verify({1, 2}, 3, expected_result={1, 2, 3}) - add_verifier.verify({1, 2, 3}, 1, expected_result={1, 2, 3}) - - -def test_remove(): - def remove_function(x: set, y: object) -> set: - x.remove(y) - return x - - remove_verifier = verifier_for(remove_function) - - remove_verifier.verify(set(), 1, expected_error=KeyError) - remove_verifier.verify({1, 2}, 3, expected_error=KeyError) - remove_verifier.verify({1, 2, 3}, 2, expected_result={1, 3}) - - -def test_discard(): - def discard_function(x: set, y: object) -> set: - x.discard(y) - return x - - discard_verifier = verifier_for(discard_function) - - discard_verifier.verify(set(), 1, expected_result=set()) - discard_verifier.verify({1, 2}, 3, expected_result={1, 2}) - discard_verifier.verify({1, 2, 3}, 2, expected_result={1, 3}) - - -def test_pop(): - def pop_function(x: set) -> tuple: - element = x.pop() - return element, x - - def pop_property(x): - return lambda result: result[0] in x and result[1] == x - {result[0]} - - pop_verifier = verifier_for(pop_function) - - pop_verifier.verify(set(), expected_error=KeyError) - pop_verifier.verify_property({1}, predicate=pop_property({1})) - pop_verifier.verify_property({1, 2, 3}, predicate=pop_property({1, 2, 3})) - - -def test_clear(): - def clear_function(x: set) -> set: - x.clear() - return x - - clear_verifier = verifier_for(clear_function) - - clear_verifier.verify(set(), expected_result=set()) - clear_verifier.verify({1}, expected_result=set()) - clear_verifier.verify({1, 2, 3}, expected_result=set()) diff --git a/jpyinterpreter/tests/test_slice.py b/jpyinterpreter/tests/test_slice.py deleted file mode 100644 index 6048708c..00000000 --- a/jpyinterpreter/tests/test_slice.py +++ /dev/null @@ -1,55 +0,0 @@ -from .conftest import verifier_for - - -def test_list_slice(): - def my_function(sequence: list, x: int, y: int) -> list: - return sequence[x:y] - - def my_function_with_step(sequence: list, x: int, y: int, z: int) -> list: - return sequence[x:y:z] - - slice_verifier = verifier_for(my_function) - slice_with_step_verifier = verifier_for(my_function_with_step) - - slice_verifier.verify([2, 4, 6, 8, 10], 1, 3, expected_result=[4, 6]) - slice_verifier.verify([2, 4, 6, 8, 10], 2, -1, expected_result=[6, 8]) - slice_verifier.verify([2, 4, 6, 8, 10], -3, 3, expected_result=[6]) - - slice_with_step_verifier.verify([2, 4, 6, 8, 10], 0, 3, 2, expected_result=[2, 6]) - slice_with_step_verifier.verify([2, 4, 6, 8, 10], -1, -3, -1, expected_result=[10, 8]) - - -def test_tuple_slice(): - def my_function(sequence: tuple, x: int, y: int) -> tuple: - return sequence[x:y] - - def my_function_with_step(sequence: tuple, x: int, y: int, z: int) -> tuple: - return sequence[x:y:z] - - slice_verifier = verifier_for(my_function) - slice_with_step_verifier = verifier_for(my_function_with_step) - - slice_verifier.verify((2, 4, 6, 8, 10), 1, 3, expected_result=(4, 6)) - slice_verifier.verify((2, 4, 6, 8, 10), 2, -1, expected_result=(6, 8)) - slice_verifier.verify((2, 4, 6, 8, 10), -3, 3, expected_result=(6,)) - - slice_with_step_verifier.verify((2, 4, 6, 8, 10), 0, 3, 2, expected_result=(2, 6)) - slice_with_step_verifier.verify((2, 4, 6, 8, 10), -1, -3, -1, expected_result=(10, 8)) - - -def test_str_slice(): - def my_function(sequence: str, x: int, y: int) -> str: - return sequence[x:y] - - def my_function_with_step(sequence: str, x: int, y: int, z: int) -> str: - return sequence[x:y:z] - - slice_verifier = verifier_for(my_function) - slice_with_step_verifier = verifier_for(my_function_with_step) - - slice_verifier.verify('abcde', 1, 3, expected_result='bc') - slice_verifier.verify('abcde', 2, -1, expected_result='cd') - slice_verifier.verify('abcde', -3, 3, expected_result='c') - - slice_with_step_verifier.verify('abcde', 0, 3, 2, expected_result='ac') - slice_with_step_verifier.verify('abcde', -1, -3, -1, expected_result='ed') diff --git a/jpyinterpreter/tests/test_str.py b/jpyinterpreter/tests/test_str.py deleted file mode 100644 index 7d051e9a..00000000 --- a/jpyinterpreter/tests/test_str.py +++ /dev/null @@ -1,1792 +0,0 @@ -from .conftest import verifier_for -from typing import Union - - -######################################## -# Sequence methods -######################################## - -def test_membership(): - def membership(tested: str, x: str) -> bool: - return x in tested - - def not_membership(tested: str, x: str) -> bool: - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify('hello world', 'world', expected_result=True) - not_membership_verifier.verify('hello world', 'world', expected_result=False) - - membership_verifier.verify('hello world', 'test', expected_result=False) - not_membership_verifier.verify('hello world', 'test', expected_result=True) - - membership_verifier.verify('hello world', '', expected_result=True) - not_membership_verifier.verify('hello world', '', expected_result=False) - - -def test_concat(): - def concat(x: str, y: str) -> tuple: - out = x + y - return out, out is x, out is y - - concat_verifier = verifier_for(concat) - - concat_verifier.verify('hello ', 'world', expected_result=('hello world', False, False)) - concat_verifier.verify('', 'hello world', expected_result=('hello world', False, True)) - concat_verifier.verify('hello world', '', expected_result=('hello world', True, False)) - concat_verifier.verify('world ', 'hello', expected_result=('world hello', False, False)) - - -def test_repeat(): - def left_repeat(x: str, y: int) -> tuple: - out = x * y - return out, out is x, out is y - - def right_repeat(x: int, y: str) -> tuple: - out = x * y - return out, out is x, out is y - - left_repeat_verifier = verifier_for(left_repeat) - right_repeat_verifier = verifier_for(right_repeat) - - left_repeat_verifier.verify('hi', 1, expected_result=('hi', True, False)) - left_repeat_verifier.verify('abc', 2, expected_result=('abcabc', False, False)) - left_repeat_verifier.verify('a', 4, expected_result=('aaaa', False, False)) - left_repeat_verifier.verify('test', 0, expected_result=('', False, False)) - left_repeat_verifier.verify('test', -1, expected_result=('', False, False)) - left_repeat_verifier.verify('test', -2, expected_result=('', False, False)) - - right_repeat_verifier.verify(1, 'hi', expected_result=('hi', False, True)) - right_repeat_verifier.verify(2, 'abc', expected_result=('abcabc', False, False)) - right_repeat_verifier.verify(4, 'a', expected_result=('aaaa', False, False)) - right_repeat_verifier.verify(0, 'test', expected_result=('', False, False)) - right_repeat_verifier.verify(-1, 'test', expected_result=('', False, False)) - right_repeat_verifier.verify(-2, 'test', expected_result=('', False, False)) - - -def test_get_item(): - def get_item(tested: str, index: int) -> str: - return tested[index] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify('abc', 1, expected_result='b') - get_item_verifier.verify('abc', -1, expected_result='c') - get_item_verifier.verify('abcd', -1, expected_result='d') - get_item_verifier.verify('abcd', -2, expected_result='c') - get_item_verifier.verify('abcd', 0, expected_result='a') - get_item_verifier.verify('abc', 3, expected_error=IndexError) - get_item_verifier.verify('abc', -4, expected_error=IndexError) - - -def test_get_slice(): - def get_slice(tested: str, start: Union[int, None], end: Union[int, None]) -> str: - return tested[start:end] - - get_slice_verifier = verifier_for(get_slice) - - get_slice_verifier.verify('abcde', 1, 3, expected_result='bc') - get_slice_verifier.verify('abcde', -3, -1, expected_result='cd') - - get_slice_verifier.verify('abcde', 0, -2, expected_result='abc') - get_slice_verifier.verify('abcde', -3, 4, expected_result='cd') - - get_slice_verifier.verify('abcde', 3, 1, expected_result='') - get_slice_verifier.verify('abcde', -1, -3, expected_result='') - - get_slice_verifier.verify('abcde', 100, 1000, expected_result='') - get_slice_verifier.verify('abcde', 0, 1000, expected_result='abcde') - - get_slice_verifier.verify('abcde', 1, None, expected_result='bcde') - get_slice_verifier.verify('abcde', None, 2, expected_result='ab') - get_slice_verifier.verify('abcde', None, None, expected_result='abcde') - - -def test_get_slice_with_step(): - def get_slice_with_step(tested: str, start: Union[int, None], end: Union[int, None], step: Union[int, None]) -> str: - return tested[start:end:step] - - get_slice_verifier = verifier_for(get_slice_with_step) - - get_slice_verifier.verify('abcde', 0, None, 2, expected_result='ace') - get_slice_verifier.verify('abcde', 1, None, 2, expected_result='bd') - get_slice_verifier.verify('abcde', 0, 5, 2, expected_result='ace') - get_slice_verifier.verify('abcde', 1, 5, 2, expected_result='bd') - get_slice_verifier.verify('abcde', 0, -1, 2, expected_result='ac') - get_slice_verifier.verify('abcde', 1, -1, 2, expected_result='bd') - - get_slice_verifier.verify('abcde', 4, None, -2, expected_result='eca') - get_slice_verifier.verify('abcde', 3, None, -2, expected_result='db') - get_slice_verifier.verify('abcde', -1, -6, -2, expected_result='eca') - get_slice_verifier.verify('abcde', -2, -6, -2, expected_result='db') - get_slice_verifier.verify('abcde', 4, 0, -2, expected_result='ec') - get_slice_verifier.verify('abcde', 3, 0, -2, expected_result='db') - - get_slice_verifier.verify('abcde', 0, None, None, expected_result='abcde') - get_slice_verifier.verify('abcde', 0, 3, None, expected_result='abc') - - get_slice_verifier.verify('abcde', 3, 1, -1, expected_result='dc') - get_slice_verifier.verify('abcde', -1, -3, -1, expected_result='ed') - get_slice_verifier.verify('abcde', 3, 1, 1, expected_result='') - get_slice_verifier.verify('abcde', -1, -3, 1, expected_result='') - - -def test_len(): - def length(tested: str) -> int: - return len(tested) - - len_verifier = verifier_for(length) - - len_verifier.verify('', expected_result=0) - len_verifier.verify('a', expected_result=1) - len_verifier.verify('ab', expected_result=2) - len_verifier.verify('cba', expected_result=3) - - -def test_index(): - def index(tested: str, item: str) -> int: - return tested.index(item) - - def index_start(tested: str, item: str, start: int) -> int: - return tested.index(item, start) - - def index_start_end(tested: str, item: str, start: int, end: int) -> int: - return tested.index(item, start, end) - - index_verifier = verifier_for(index) - index_start_verifier = verifier_for(index_start) - index_start_end_verifier = verifier_for(index_start_end) - - index_verifier.verify('abcabc', 'a', expected_result=0) - index_verifier.verify('abcabc', 'b', expected_result=1) - index_verifier.verify('abcabc', 'd', expected_error=ValueError) - - index_start_verifier.verify('abcabc', 'a', 1, expected_result=3) - index_start_verifier.verify('abcabc', 'a', 5, expected_error=ValueError) - index_start_verifier.verify('abcabc', 'b', 1, expected_result=1) - index_start_verifier.verify('abcabc', 'c', 1, expected_result=2) - index_start_verifier.verify('abcabc', 'd', 1, expected_error=ValueError) - - index_start_verifier.verify('abcabc', 'a', -3, expected_result=3) - index_start_verifier.verify('abcabc', 'b', -2, expected_result=4) - index_start_verifier.verify('abcabc', 'c', -2, expected_result=5) - index_start_verifier.verify('abcabc', 'd', -2, expected_error=ValueError) - - index_start_end_verifier.verify('abcabc', 'a', 1, 2, expected_error=ValueError) - index_start_end_verifier.verify('abcabc', 'b', 1, 2, expected_result=1) - index_start_end_verifier.verify('abcabc', 'c', 1, 2, expected_error=ValueError) - index_start_end_verifier.verify('abcabc', 'd', 1, 2, expected_error=ValueError) - - index_start_end_verifier.verify('abcabc', 'a', -2, -1, expected_error=ValueError) - index_start_end_verifier.verify('abcabc', 'b', -2, -1, expected_result=4) - index_start_end_verifier.verify('abcabc', 'c', -2, -1, expected_error=ValueError) - index_start_end_verifier.verify('abcabc', 'd', -2, -1, expected_error=ValueError) - - -def test_count(): - def count(tested: str, item: str) -> int: - return tested.count(item) - - count_verifier = verifier_for(count) - - count_verifier.verify('abc', 'a', expected_result=1) - count_verifier.verify('abc', 'b', expected_result=1) - count_verifier.verify('abc', 'c', expected_result=1) - count_verifier.verify('abc', 'd', expected_result=0) - - count_verifier.verify('abca', 'a', expected_result=2) - count_verifier.verify('aaca', 'a', expected_result=3) - count_verifier.verify('', 'a', expected_result=0) - - -######################################## -# String operations -######################################## -def test_interpolation(): - def interpolation(tested: str, values: object) -> str: - return tested % values - - interpolation_verifier = verifier_for(interpolation) - - interpolation_verifier.verify('%d', 100, expected_result='100') - interpolation_verifier.verify('%d', 0b1111, expected_result='15') - interpolation_verifier.verify('%s', 'foo', expected_result='foo') - interpolation_verifier.verify('%s %s', ('foo', 'bar'), expected_result='foo bar') - interpolation_verifier.verify('%(foo)s', {'foo': 10, 'bar': 20}, expected_result='10') - - interpolation_verifier.verify('%d', 101, expected_result='101') - interpolation_verifier.verify('%i', 101, expected_result='101') - - interpolation_verifier.verify('%o', 27, expected_result='33') - interpolation_verifier.verify('%#o', 27, expected_result='0o33') - - interpolation_verifier.verify('%x', 27, expected_result='1b') - interpolation_verifier.verify('%X', 27, expected_result='1B') - interpolation_verifier.verify('%#x', 27, expected_result='0x1b') - interpolation_verifier.verify('%#X', 27, expected_result='0X1B') - - interpolation_verifier.verify('%03d', 1, expected_result='001') - interpolation_verifier.verify('%-5d', 1, expected_result='1 ') - interpolation_verifier.verify('%0-5d', 1, expected_result='1 ') - - interpolation_verifier.verify('%d', 1, expected_result='1') - interpolation_verifier.verify('%d', -1, expected_result='-1') - interpolation_verifier.verify('% d', 1, expected_result=' 1') - interpolation_verifier.verify('% d', -1, expected_result='-1') - interpolation_verifier.verify('%+d', 1, expected_result='+1') - interpolation_verifier.verify('%+d', -1, expected_result='-1') - - interpolation_verifier.verify('%f', 3.14, expected_result='3.140000') - interpolation_verifier.verify('%F', 3.14, expected_result='3.140000') - interpolation_verifier.verify('%.1f', 3.14, expected_result='3.1') - interpolation_verifier.verify('%.2f', 3.14, expected_result='3.14') - interpolation_verifier.verify('%.3f', 3.14, expected_result='3.140') - - interpolation_verifier.verify('%g', 1234567890, expected_result='1.23457e+09') - interpolation_verifier.verify('%G', 1234567890, expected_result='1.23457E+09') - interpolation_verifier.verify('%e', 1234567890, expected_result='1.234568e+09') - interpolation_verifier.verify('%E', 1234567890, expected_result='1.234568E+09') - - interpolation_verifier.verify('ABC %c', 10, expected_result='ABC \n') - interpolation_verifier.verify('ABC %c', 67, expected_result='ABC C') - interpolation_verifier.verify('ABC %c', 68, expected_result='ABC D') - interpolation_verifier.verify('ABC %c', 'D', expected_result='ABC D') - interpolation_verifier.verify('ABC %s', 'test', expected_result='ABC test') - interpolation_verifier.verify('ABC %r', 'test', expected_result='ABC \'test\'') - - interpolation_verifier.verify('Give it %d%%!', 100, expected_result='Give it 100%!') - interpolation_verifier.verify('Give it %(all-you-got)d%%!', {'all-you-got': 100}, expected_result='Give it 100%!') - - -######################################## -# String methods -######################################## - - -def test_capitalize(): - def capitalize(tested: str) -> str: - return tested.capitalize() - - capitalize_verifier = verifier_for(capitalize) - - capitalize_verifier.verify('', expected_result='') - capitalize_verifier.verify('test', expected_result='Test') - capitalize_verifier.verify('TEST', expected_result='Test') - capitalize_verifier.verify('hello world', expected_result='Hello world') - capitalize_verifier.verify('Hello World', expected_result='Hello world') - capitalize_verifier.verify('HELLO WORLD', expected_result='Hello world') - - -def test_casefold(): - def casefold(tested: str) -> str: - return tested.casefold() - - casefold_verifier = verifier_for(casefold) - - casefold_verifier.verify('', expected_result='') - casefold_verifier.verify('test', expected_result='test') - casefold_verifier.verify('TEST', expected_result='test') - casefold_verifier.verify('hello world', expected_result='hello world') - casefold_verifier.verify('Hello World', expected_result='hello world') - casefold_verifier.verify('HELLO WORLD', expected_result='hello world') - - casefold_verifier.verify('ßest', expected_result='ssest') - - -def test_center(): - def center(tested: str, width: int) -> str: - return tested.center(width) - - def center_with_fill(tested: str, width: int, fill: str) -> str: - return tested.center(width, fill) - - center_verifier = verifier_for(center) - center_with_fill_verifier = verifier_for(center_with_fill) - - center_verifier.verify('test', 10, expected_result=' test ') - center_verifier.verify('test', 9, expected_result=' test ') - center_verifier.verify('test', 4, expected_result='test') - center_verifier.verify('test', 2, expected_result='test') - - center_with_fill_verifier.verify('test', 10, '#', expected_result='###test###') - center_with_fill_verifier.verify('test', 9, '#', expected_result='###test##') - center_with_fill_verifier.verify('test', 4, '#', expected_result='test') - center_with_fill_verifier.verify('test', 2, '#', expected_result='test') - - -def test_count_str(): - def count(tested: str, item: str) -> int: - return tested.count(item) - - def count_from_start(tested: str, item: str, start: int) -> int: - return tested.count(item, start) - - def count_between(tested: str, item: str, start: int, end: int) -> int: - return tested.count(item, start, end) - - count_verifier = verifier_for(count) - count_from_start_verifier = verifier_for(count_from_start) - count_between_verifier = verifier_for(count_between) - - count_verifier.verify('abc', 'a', expected_result=1) - count_verifier.verify('abc', 'b', expected_result=1) - count_verifier.verify('abc', 'c', expected_result=1) - count_verifier.verify('abc', 'd', expected_result=0) - - count_verifier.verify('abca', 'a', expected_result=2) - count_verifier.verify('aaca', 'a', expected_result=3) - count_verifier.verify('', 'a', expected_result=0) - - count_from_start_verifier.verify('abc', 'a', 1, expected_result=0) - count_from_start_verifier.verify('abc', 'b', 1, expected_result=1) - count_from_start_verifier.verify('abc', 'c', 1, expected_result=1) - count_from_start_verifier.verify('abc', 'd', 1, expected_result=0) - - count_from_start_verifier.verify('abca', 'a', 1, expected_result=1) - count_from_start_verifier.verify('aaca', 'a', 1, expected_result=2) - count_from_start_verifier.verify('', 'a', 1, expected_result=0) - - count_between_verifier.verify('abc', 'a', 1, 2, expected_result=0) - count_between_verifier.verify('abc', 'b', 1, 2, expected_result=1) - count_between_verifier.verify('abc', 'c', 1, 2, expected_result=0) - count_between_verifier.verify('abc', 'd', 1, 2, expected_result=0) - - count_between_verifier.verify('abca', 'a', 1, 2, expected_result=0) - count_between_verifier.verify('abca', 'a', 1, 4, expected_result=1) - count_between_verifier.verify('abca', 'a', 0, 2, expected_result=1) - count_between_verifier.verify('aaca', 'a', 1, 2, expected_result=1) - count_between_verifier.verify('', 'a', 1, 2, expected_result=0) - - -def test_endswith(): - def endswith(tested: str, suffix: str) -> bool: - return tested.endswith(suffix) - - def endswith_start(tested: str, suffix: str, start: int) -> bool: - return tested.endswith(suffix, start) - - def endswith_between(tested: str, suffix: str, start: int, end: int) -> bool: - return tested.endswith(suffix, start, end) - - endswith_verifier = verifier_for(endswith) - endswith_start_verifier = verifier_for(endswith_start) - endswith_between_verifier = verifier_for(endswith_between) - - endswith_verifier.verify('hello world', 'world', expected_result=True) - endswith_verifier.verify('hello world', 'hello', expected_result=False) - endswith_verifier.verify('hello', 'hello world', expected_result=False) - endswith_verifier.verify('hello world', 'hello world', expected_result=True) - - endswith_start_verifier.verify('hello world', 'world', 6, expected_result=True) - endswith_start_verifier.verify('hello world', 'hello', 6, expected_result=False) - endswith_start_verifier.verify('hello', 'hello world', 6, expected_result=False) - endswith_start_verifier.verify('hello world', 'hello world', 6, expected_result=False) - - endswith_between_verifier.verify('hello world', 'world', 6, 11, expected_result=True) - endswith_between_verifier.verify('hello world', 'world', 7, 11, expected_result=False) - endswith_between_verifier.verify('hello world', 'hello', 0, 5, expected_result=True) - endswith_between_verifier.verify('hello', 'hello world', 0, 5, expected_result=False) - endswith_between_verifier.verify('hello world', 'hello world', 5, 11, expected_result=False) - - -def test_expandtabs(): - def expandtabs(tested: str) -> str: - return tested.expandtabs() - - def expandtabs_with_tabsize(tested: str, tabsize: int) -> str: - return tested.expandtabs(tabsize) - - expandtabs_verifier = verifier_for(expandtabs) - expandtabs_with_tabsize_verifier = verifier_for(expandtabs_with_tabsize) - - expandtabs_verifier.verify('01\t012\t0123\t01234', expected_result='01 012 0123 01234') - expandtabs_with_tabsize_verifier.verify('01\t012\t0123\t01234', 8, expected_result='01 012 0123 01234') - expandtabs_with_tabsize_verifier.verify('01\t012\t0123\t01234', 4, expected_result='01 012 0123 01234') - - -def test_find(): - def find(tested: str, item: str) -> int: - return tested.find(item) - - def find_start_verifier(tested: str, item: str, start: int) -> int: - return tested.find(item, start) - - def find_start_end_verifier(tested: str, item: str, start: int, end: int) -> int: - return tested.find(item, start, end) - - find_verifier = verifier_for(find) - find_start_verifier = verifier_for(find_start_verifier) - find_start_end_verifier = verifier_for(find_start_end_verifier) - - find_verifier.verify('abcabc', 'a', expected_result=0) - find_verifier.verify('abcabc', 'b', expected_result=1) - find_verifier.verify('abcabc', 'd', expected_result=-1) - - find_start_verifier.verify('abcabc', 'a', 1, expected_result=3) - find_start_verifier.verify('abcabc', 'a', 5, expected_result=-1) - find_start_verifier.verify('abcabc', 'b', 1, expected_result=1) - find_start_verifier.verify('abcabc', 'c', 1, expected_result=2) - find_start_verifier.verify('abcabc', 'd', 1, expected_result=-1) - - find_start_verifier.verify('abcabc', 'a', -3, expected_result=3) - find_start_verifier.verify('abcabc', 'b', -2, expected_result=4) - find_start_verifier.verify('abcabc', 'c', -2, expected_result=5) - find_start_verifier.verify('abcabc', 'd', -2, expected_result=-1) - - find_start_end_verifier.verify('abcabc', 'a', 1, 2, expected_result=-1) - find_start_end_verifier.verify('abcabc', 'b', 1, 2, expected_result=1) - find_start_end_verifier.verify('abcabc', 'c', 1, 2, expected_result=-1) - find_start_end_verifier.verify('abcabc', 'd', 1, 2, expected_result=-1) - - find_start_end_verifier.verify('abcabc', 'a', -2, -1, expected_result=-1) - find_start_end_verifier.verify('abcabc', 'b', -2, -1, expected_result=4) - find_start_end_verifier.verify('abcabc', 'c', -2, -1, expected_result=-1) - find_start_end_verifier.verify('abcabc', 'd', -2, -1, expected_result=-1) - - -def test_format(): - def my_format(tested: str, positional: Union[tuple, None], keywords: Union[dict, None]) -> str: - if positional is None: - return tested.format(**keywords) - elif keywords is None: - return tested.format(*positional) - else: - return tested.format(*positional, **keywords) - - format_verifier = verifier_for(my_format) - - format_verifier.verify('{0}, {1}, {2}', ('a', 'b', 'c'), None, expected_result='a, b, c') - format_verifier.verify('{}, {}, {}', ('a', 'b', 'c'), None, expected_result='a, b, c') - format_verifier.verify('{2}, {1}, {0}', ('a', 'b', 'c'), None, expected_result='c, b, a') - format_verifier.verify('{0}{1}{0}', ('abra', 'cad'), None, expected_result='abracadabra') - format_verifier.verify('Coordinates: {latitude}, {longitude}', None, {'latitude': '37.24N', - 'longitude': '-115.81W'}, - expected_result='Coordinates: 37.24N, -115.81W') - format_verifier.verify("repr() shows quotes: {!r}; str() doesn't: {!s}", ('test1', 'test2'), None, - expected_result="repr() shows quotes: 'test1'; str() doesn't: test2") - format_verifier.verify('{:<30}', ('left aligned',), None, - expected_result='left aligned ') - format_verifier.verify('{:>30}', ('right aligned',), None, - expected_result=' right aligned') - format_verifier.verify('{:^30}', ('centered',), None, - expected_result=' centered ') - format_verifier.verify('{:*^30}', ('centered',), None, - expected_result='***********centered***********') - format_verifier.verify('{:+f}; {:+f}', (3.14, -3.14), None, - expected_result='+3.140000; -3.140000') - format_verifier.verify('{: f}; {: f}', (3.14, -3.14), None, - expected_result=' 3.140000; -3.140000') - format_verifier.verify('{:-f}; {:-f}', (3.14, -3.14), None, - expected_result='3.140000; -3.140000') - format_verifier.verify("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", (42,), None, - expected_result='int: 42; hex: 2a; oct: 52; bin: 101010') - format_verifier.verify("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", (42,), None, - expected_result='int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010') - format_verifier.verify("{:,}", (1234567890,), None, - expected_result='1,234,567,890') - format_verifier.verify("{:_}", (1234567890,), None, - expected_result='1_234_567_890') - format_verifier.verify("Correct answers: {:.2%}", (19/22,), None, - expected_result='Correct answers: 86.36%') - - -def test_isalnum(): - def isalnum(tested: str) -> bool: - return tested.isalnum() - - isalnum_verifier = verifier_for(isalnum) - - isalnum_verifier.verify('', expected_result=False) - - isalnum_verifier.verify('abc', expected_result=True) - isalnum_verifier.verify('αβγ', expected_result=True) - - isalnum_verifier.verify('ABC', expected_result=True) - isalnum_verifier.verify('ΑΒΓ', expected_result=True) - - isalnum_verifier.verify('NjDzᾩ', expected_result=True) - - isalnum_verifier.verify('ᐎᐐᐊ', expected_result=True) - - isalnum_verifier.verify('ʷʰʸ', expected_result=True) - - isalnum_verifier.verify('123', expected_result=True) - isalnum_verifier.verify('౧౨౩', expected_result=True) - - isalnum_verifier.verify('ⅠⅡⅢ', expected_result=True) - isalnum_verifier.verify('¼½¾', expected_result=True) - - isalnum_verifier.verify('ABC123', expected_result=True) - isalnum_verifier.verify('αβγ౧౨౩', expected_result=True) - - isalnum_verifier.verify('+', expected_result=False) - isalnum_verifier.verify('±', expected_result=False) - isalnum_verifier.verify('×', expected_result=False) - - isalnum_verifier.verify('©', expected_result=False) - - isalnum_verifier.verify('[]', expected_result=False) - - isalnum_verifier.verify('︳', expected_result=False) - - isalnum_verifier.verify('-', expected_result=False) - - isalnum_verifier.verify('%', expected_result=False) - - isalnum_verifier.verify('\n', expected_result=False) - - isalnum_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isalnum_verifier.verify('\t', expected_result=False) - isalnum_verifier.verify(' ', expected_result=False) - - isalnum_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isalnum_verifier.verify('\u0000', expected_result=False) - - -def test_isalpha(): - def isalpha(tested: str) -> bool: - return tested.isalpha() - - isalpha_verifier = verifier_for(isalpha) - - isalpha_verifier.verify('', expected_result=False) - - isalpha_verifier.verify('abc', expected_result=True) - isalpha_verifier.verify('αβγ', expected_result=True) - - isalpha_verifier.verify('ABC', expected_result=True) - isalpha_verifier.verify('ΑΒΓ', expected_result=True) - - isalpha_verifier.verify('NjDzᾩ', expected_result=True) - - isalpha_verifier.verify('ᐎᐐᐊ', expected_result=True) - - isalpha_verifier.verify('ʷʰʸ', expected_result=True) - - isalpha_verifier.verify('123', expected_result=False) - isalpha_verifier.verify('౧౨౩', expected_result=False) - - isalpha_verifier.verify('ⅠⅡⅢ', expected_result=False) - isalpha_verifier.verify('¼½¾', expected_result=False) - - isalpha_verifier.verify('ABC123', expected_result=False) - isalpha_verifier.verify('αβγ౧౨౩', expected_result=False) - - isalpha_verifier.verify('+', expected_result=False) - isalpha_verifier.verify('±', expected_result=False) - isalpha_verifier.verify('×', expected_result=False) - - isalpha_verifier.verify('©', expected_result=False) - - isalpha_verifier.verify('[]', expected_result=False) - - isalpha_verifier.verify('︳', expected_result=False) - - isalpha_verifier.verify('-', expected_result=False) - - isalpha_verifier.verify('%', expected_result=False) - - isalpha_verifier.verify('\n', expected_result=False) - - isalpha_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isalpha_verifier.verify('\t', expected_result=False) - isalpha_verifier.verify(' ', expected_result=False) - - isalpha_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isalpha_verifier.verify('\u0000', expected_result=False) - - -def test_isascii(): - def isascii(tested: str) -> bool: - return tested.isascii() - - isascii_verifier = verifier_for(isascii) - - isascii_verifier.verify('', expected_result=True) - - isascii_verifier.verify('abc', expected_result=True) - isascii_verifier.verify('αβγ', expected_result=False) - - isascii_verifier.verify('ABC', expected_result=True) - isascii_verifier.verify('ΑΒΓ', expected_result=False) - - isascii_verifier.verify('NjDzᾩ', expected_result=False) - - isascii_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isascii_verifier.verify('ʷʰʸ', expected_result=False) - - isascii_verifier.verify('123', expected_result=True) - isascii_verifier.verify('౧౨౩', expected_result=False) - - isascii_verifier.verify('ⅠⅡⅢ', expected_result=False) - isascii_verifier.verify('¼½¾', expected_result=False) - - isascii_verifier.verify('ABC123', expected_result=True) - isascii_verifier.verify('αβγ౧౨౩', expected_result=False) - - isascii_verifier.verify('+', expected_result=True) - isascii_verifier.verify('±', expected_result=False) - isascii_verifier.verify('×', expected_result=False) - - isascii_verifier.verify('©', expected_result=False) - - isascii_verifier.verify('[]', expected_result=True) - - isascii_verifier.verify('︳', expected_result=False) - - isascii_verifier.verify('-', expected_result=True) - - isascii_verifier.verify('%', expected_result=True) - - isascii_verifier.verify('\n', expected_result=True) - - isascii_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isascii_verifier.verify('\t', expected_result=True) - isascii_verifier.verify(' ', expected_result=True) - - isascii_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isascii_verifier.verify('\u0000', expected_result=True) - - -def test_isdecimal(): - def isdecimal(tested: str) -> bool: - return tested.isdecimal() - - isdecimal_verifier = verifier_for(isdecimal) - - isdecimal_verifier.verify('', expected_result=False) - - isdecimal_verifier.verify('abc', expected_result=False) - isdecimal_verifier.verify('αβγ', expected_result=False) - - isdecimal_verifier.verify('ABC', expected_result=False) - isdecimal_verifier.verify('ΑΒΓ', expected_result=False) - - isdecimal_verifier.verify('NjDzᾩ', expected_result=False) - - isdecimal_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isdecimal_verifier.verify('ʷʰʸ', expected_result=False) - - isdecimal_verifier.verify('123', expected_result=True) - isdecimal_verifier.verify('౧౨౩', expected_result=True) - isdecimal_verifier.verify('\u00B2', expected_result=False) # superscript 2 - - isdecimal_verifier.verify('ⅠⅡⅢ', expected_result=False) - isdecimal_verifier.verify('¼½¾', expected_result=False) - - isdecimal_verifier.verify('ABC123', expected_result=False) - isdecimal_verifier.verify('αβγ౧౨౩', expected_result=False) - - isdecimal_verifier.verify('+', expected_result=False) - isdecimal_verifier.verify('±', expected_result=False) - isdecimal_verifier.verify('×', expected_result=False) - - isdecimal_verifier.verify('©', expected_result=False) - - isdecimal_verifier.verify('[]', expected_result=False) - - isdecimal_verifier.verify('︳', expected_result=False) - - isdecimal_verifier.verify('-', expected_result=False) - - isdecimal_verifier.verify('%', expected_result=False) - - isdecimal_verifier.verify('\n', expected_result=False) - - isdecimal_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isdecimal_verifier.verify('\t', expected_result=False) - isdecimal_verifier.verify(' ', expected_result=False) - - isdecimal_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isdecimal_verifier.verify('\u0000', expected_result=False) - - -def test_isdigit(): - def isdigit(tested: str) -> bool: - return tested.isdigit() - - isdigit_verifier = verifier_for(isdigit) - - isdigit_verifier.verify('', expected_result=False) - - isdigit_verifier.verify('abc', expected_result=False) - isdigit_verifier.verify('αβγ', expected_result=False) - - isdigit_verifier.verify('ABC', expected_result=False) - isdigit_verifier.verify('ΑΒΓ', expected_result=False) - - isdigit_verifier.verify('NjDzᾩ', expected_result=False) - - isdigit_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isdigit_verifier.verify('ʷʰʸ', expected_result=False) - - isdigit_verifier.verify('123', expected_result=True) - isdigit_verifier.verify('౧౨౩', expected_result=True) - isdigit_verifier.verify('\u00B2', expected_result=True) # superscript 2 - - isdigit_verifier.verify('ⅠⅡⅢ', expected_result=False) - isdigit_verifier.verify('¼½¾', expected_result=False) - - isdigit_verifier.verify('ABC123', expected_result=False) - isdigit_verifier.verify('αβγ౧౨౩', expected_result=False) - - isdigit_verifier.verify('+', expected_result=False) - isdigit_verifier.verify('±', expected_result=False) - isdigit_verifier.verify('×', expected_result=False) - - isdigit_verifier.verify('©', expected_result=False) - - isdigit_verifier.verify('[]', expected_result=False) - - isdigit_verifier.verify('︳', expected_result=False) - - isdigit_verifier.verify('-', expected_result=False) - - isdigit_verifier.verify('%', expected_result=False) - - isdigit_verifier.verify('\n', expected_result=False) - - isdigit_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isdigit_verifier.verify('\t', expected_result=False) - isdigit_verifier.verify(' ', expected_result=False) - - isdigit_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isdigit_verifier.verify('\u0000', expected_result=False) - - -def test_isidentifier(): - def isidentifier(tested: str) -> bool: - return tested.isidentifier() - - isidentifier_verifier = verifier_for(isidentifier) - - isidentifier_verifier.verify('', expected_result=False) - - isidentifier_verifier.verify('abc', expected_result=True) - isidentifier_verifier.verify('αβγ', expected_result=True) - - isidentifier_verifier.verify('ABC', expected_result=True) - isidentifier_verifier.verify('ΑΒΓ', expected_result=True) - - isidentifier_verifier.verify('NjDzᾩ', expected_result=True) - - isidentifier_verifier.verify('ᐎᐐᐊ', expected_result=True) - - isidentifier_verifier.verify('ʷʰʸ', expected_result=True) - - isidentifier_verifier.verify('123', expected_result=False) - isidentifier_verifier.verify('౧౨౩', expected_result=False) - - isidentifier_verifier.verify('ⅠⅡⅢ', expected_result=True) - isidentifier_verifier.verify('¼½¾', expected_result=False) - - isidentifier_verifier.verify('123abc', expected_result=False) - isidentifier_verifier.verify('ABC123', expected_result=True) - isidentifier_verifier.verify('αβγ౧౨౩', expected_result=True) - - isidentifier_verifier.verify('+', expected_result=False) - isidentifier_verifier.verify('±', expected_result=False) - isidentifier_verifier.verify('×', expected_result=False) - - isidentifier_verifier.verify('©', expected_result=False) - - isidentifier_verifier.verify('[]', expected_result=False) - - isidentifier_verifier.verify('︳', expected_result=False) - - isidentifier_verifier.verify('-', expected_result=False) - - isidentifier_verifier.verify('%', expected_result=False) - - isidentifier_verifier.verify('\n', expected_result=False) - - isidentifier_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isidentifier_verifier.verify('\t', expected_result=False) - isidentifier_verifier.verify(' ', expected_result=False) - - isidentifier_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isidentifier_verifier.verify('\u0000', expected_result=False) - - -def test_islower(): - def islower(tested: str) -> bool: - return tested.islower() - - islower_verifier = verifier_for(islower) - - islower_verifier.verify('', expected_result=False) - - islower_verifier.verify('abc', expected_result=True) - islower_verifier.verify('αβγ', expected_result=True) - - islower_verifier.verify('ABC', expected_result=False) - islower_verifier.verify('ΑΒΓ', expected_result=False) - - islower_verifier.verify('NjDzᾩ', expected_result=False) - - islower_verifier.verify('ᐎᐐᐊ', expected_result=False) - - islower_verifier.verify('ʷʰʸ', expected_result=True) - - islower_verifier.verify('123', expected_result=False) - islower_verifier.verify('౧౨౩', expected_result=False) - - islower_verifier.verify('ⅠⅡⅢ', expected_result=False) - islower_verifier.verify('¼½¾', expected_result=False) - - islower_verifier.verify('ABC123', expected_result=False) - islower_verifier.verify('αβγ౧౨౩', expected_result=True) - - islower_verifier.verify('+', expected_result=False) - islower_verifier.verify('±', expected_result=False) - islower_verifier.verify('×', expected_result=False) - - islower_verifier.verify('©', expected_result=False) - - islower_verifier.verify('[]', expected_result=False) - - islower_verifier.verify('︳', expected_result=False) - - islower_verifier.verify('-', expected_result=False) - - islower_verifier.verify('%', expected_result=False) - - islower_verifier.verify('\n', expected_result=False) - - islower_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - islower_verifier.verify('\t', expected_result=False) - islower_verifier.verify(' ', expected_result=False) - - islower_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - islower_verifier.verify('\u0000', expected_result=False) - - -def test_isnumeric(): - def isnumeric(tested: str) -> bool: - return tested.isnumeric() - - isnumeric_verifier = verifier_for(isnumeric) - - isnumeric_verifier.verify('', expected_result=False) - - isnumeric_verifier.verify('abc', expected_result=False) - isnumeric_verifier.verify('αβγ', expected_result=False) - - isnumeric_verifier.verify('ABC', expected_result=False) - isnumeric_verifier.verify('ΑΒΓ', expected_result=False) - - isnumeric_verifier.verify('NjDzᾩ', expected_result=False) - - isnumeric_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isnumeric_verifier.verify('ʷʰʸ', expected_result=False) - - isnumeric_verifier.verify('123', expected_result=True) - isnumeric_verifier.verify('౧౨౩', expected_result=True) - - isnumeric_verifier.verify('ⅠⅡⅢ', expected_result=True) - isnumeric_verifier.verify('¼½¾', expected_result=True) - - isnumeric_verifier.verify('ABC123', expected_result=False) - isnumeric_verifier.verify('αβγ౧౨౩', expected_result=False) - - isnumeric_verifier.verify('+', expected_result=False) - isnumeric_verifier.verify('±', expected_result=False) - isnumeric_verifier.verify('×', expected_result=False) - - isnumeric_verifier.verify('©', expected_result=False) - - isnumeric_verifier.verify('[]', expected_result=False) - - isnumeric_verifier.verify('︳', expected_result=False) - - isnumeric_verifier.verify('-', expected_result=False) - - isnumeric_verifier.verify('%', expected_result=False) - - isnumeric_verifier.verify('\n', expected_result=False) - - isnumeric_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isnumeric_verifier.verify('\t', expected_result=False) - isnumeric_verifier.verify(' ', expected_result=False) - - isnumeric_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isnumeric_verifier.verify('\u0000', expected_result=False) - - -def test_isprintable(): - def isprintable(tested: str) -> bool: - return tested.isprintable() - - isprintable_verifier = verifier_for(isprintable) - - isprintable_verifier.verify('', expected_result=True) - - isprintable_verifier.verify('abc', expected_result=True) - isprintable_verifier.verify('αβγ', expected_result=True) - - isprintable_verifier.verify('ABC', expected_result=True) - isprintable_verifier.verify('ΑΒΓ', expected_result=True) - - isprintable_verifier.verify('NjDzᾩ', expected_result=True) - - isprintable_verifier.verify('ᐎᐐᐊ', expected_result=True) - - isprintable_verifier.verify('ʷʰʸ', expected_result=True) - - isprintable_verifier.verify('123', expected_result=True) - isprintable_verifier.verify('౧౨౩', expected_result=True) - - isprintable_verifier.verify('ⅠⅡⅢ', expected_result=True) - isprintable_verifier.verify('¼½¾', expected_result=True) - - isprintable_verifier.verify('ABC123', expected_result=True) - isprintable_verifier.verify('αβγ౧౨౩', expected_result=True) - - isprintable_verifier.verify('+', expected_result=True) - isprintable_verifier.verify('±', expected_result=True) - isprintable_verifier.verify('×', expected_result=True) - - isprintable_verifier.verify('©', expected_result=True) - - isprintable_verifier.verify('[]', expected_result=True) - - isprintable_verifier.verify('︳', expected_result=True) - - isprintable_verifier.verify('-', expected_result=True) - - isprintable_verifier.verify('%', expected_result=True) - - isprintable_verifier.verify('\n', expected_result=False) - - isprintable_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isprintable_verifier.verify('\t', expected_result=False) - isprintable_verifier.verify(' ', expected_result=True) - - isprintable_verifier.verify('\U0001f64f', expected_result=True) # Need surrogates in UTF-16 - - isprintable_verifier.verify('\u0000', expected_result=False) - - -def test_isspace(): - def isspace(tested: str) -> bool: - return tested.isspace() - - isspace_verifier = verifier_for(isspace) - - isspace_verifier.verify('', expected_result=False) - - isspace_verifier.verify('abc', expected_result=False) - isspace_verifier.verify('αβγ', expected_result=False) - - isspace_verifier.verify('ABC', expected_result=False) - isspace_verifier.verify('ΑΒΓ', expected_result=False) - - isspace_verifier.verify('NjDzᾩ', expected_result=False) - - isspace_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isspace_verifier.verify('ʷʰʸ', expected_result=False) - - isspace_verifier.verify('123', expected_result=False) - isspace_verifier.verify('౧౨౩', expected_result=False) - - isspace_verifier.verify('ⅠⅡⅢ', expected_result=False) - isspace_verifier.verify('¼½¾', expected_result=False) - - isspace_verifier.verify('ABC123', expected_result=False) - isspace_verifier.verify('αβγ౧౨౩', expected_result=False) - - isspace_verifier.verify('+', expected_result=False) - isspace_verifier.verify('±', expected_result=False) - isspace_verifier.verify('×', expected_result=False) - - isspace_verifier.verify('©', expected_result=False) - - isspace_verifier.verify('[]', expected_result=False) - - isspace_verifier.verify('︳', expected_result=False) - - isspace_verifier.verify('-', expected_result=False) - - isspace_verifier.verify('%', expected_result=False) - - isspace_verifier.verify('\n', expected_result=True) - - isspace_verifier.verify('\u2029', expected_result=True) # paragraph seperator - - isspace_verifier.verify('\t', expected_result=True) - isspace_verifier.verify(' ', expected_result=True) - - isspace_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isspace_verifier.verify('\u0000', expected_result=False) - - -def test_istitle(): - def istitle(tested: str) -> bool: - return tested.istitle() - - istitle_verifier = verifier_for(istitle) - - istitle_verifier.verify('', expected_result=False) - - istitle_verifier.verify('Abc', expected_result=True) - istitle_verifier.verify('Abc Αβγ', expected_result=True) - istitle_verifier.verify('Abc αβγ', expected_result=False) - - istitle_verifier.verify('abc', expected_result=False) - istitle_verifier.verify('αβγ', expected_result=False) - - istitle_verifier.verify('ABC', expected_result=False) - istitle_verifier.verify('ΑΒΓ', expected_result=False) - - istitle_verifier.verify('NjDzᾩ', expected_result=False) - - istitle_verifier.verify('ᐎᐐᐊ', expected_result=False) - - istitle_verifier.verify('ʷʰʸ', expected_result=False) - - istitle_verifier.verify('123', expected_result=False) - istitle_verifier.verify('౧౨౩', expected_result=False) - - istitle_verifier.verify('ⅠⅡⅢ', expected_result=False) - istitle_verifier.verify('¼½¾', expected_result=False) - - istitle_verifier.verify('ABC123', expected_result=False) - istitle_verifier.verify('αβγ౧౨౩', expected_result=False) - - istitle_verifier.verify('+', expected_result=False) - istitle_verifier.verify('±', expected_result=False) - istitle_verifier.verify('×', expected_result=False) - - istitle_verifier.verify('©', expected_result=False) - - istitle_verifier.verify('[]', expected_result=False) - - istitle_verifier.verify('︳', expected_result=False) - - istitle_verifier.verify('-', expected_result=False) - - istitle_verifier.verify('%', expected_result=False) - - istitle_verifier.verify('\n', expected_result=False) - - istitle_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - istitle_verifier.verify('\t', expected_result=False) - istitle_verifier.verify(' ', expected_result=False) - - istitle_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - istitle_verifier.verify('\u0000', expected_result=False) - - -def test_isupper(): - def isupper(tested: str) -> bool: - return tested.isupper() - - isupper_verifier = verifier_for(isupper) - - isupper_verifier.verify('', expected_result=False) - - isupper_verifier.verify('abc', expected_result=False) - isupper_verifier.verify('αβγ', expected_result=False) - - isupper_verifier.verify('ABC', expected_result=True) - isupper_verifier.verify('ΑΒΓ', expected_result=True) - - isupper_verifier.verify('NjDzᾩ', expected_result=False) - - isupper_verifier.verify('ᐎᐐᐊ', expected_result=False) - - isupper_verifier.verify('ʷʰʸ', expected_result=False) - - isupper_verifier.verify('123', expected_result=False) - isupper_verifier.verify('౧౨౩', expected_result=False) - - isupper_verifier.verify('ⅠⅡⅢ', expected_result=True) - isupper_verifier.verify('¼½¾', expected_result=False) - - isupper_verifier.verify('ABC123', expected_result=True) - isupper_verifier.verify('αβγ౧౨౩', expected_result=False) - - isupper_verifier.verify('+', expected_result=False) - isupper_verifier.verify('±', expected_result=False) - isupper_verifier.verify('×', expected_result=False) - - isupper_verifier.verify('©', expected_result=False) - - isupper_verifier.verify('[]', expected_result=False) - - isupper_verifier.verify('︳', expected_result=False) - - isupper_verifier.verify('-', expected_result=False) - - isupper_verifier.verify('%', expected_result=False) - - isupper_verifier.verify('\n', expected_result=False) - - isupper_verifier.verify('\u2029', expected_result=False) # paragraph seperator - - isupper_verifier.verify('\t', expected_result=False) - isupper_verifier.verify(' ', expected_result=False) - - isupper_verifier.verify('\U0001f64f', expected_result=False) # Need surrogates in UTF-16 - - isupper_verifier.verify('\u0000', expected_result=False) - - -def test_join(): - def join(tested: str, iterable: list) -> str: - return tested.join(iterable) - - join_verifier = verifier_for(join) - - join_verifier.verify(', ', [], expected_result='') - join_verifier.verify(', ', ['a'], expected_result='a') - join_verifier.verify(', ', ['a', 'b'], expected_result='a, b') - join_verifier.verify(' ', ['hello', 'world', 'again'], expected_result='hello world again') - join_verifier.verify('\n', ['1', '2', '3'], expected_result='1\n2\n3') - join_verifier.verify(', ', [1, 2], expected_error=TypeError) - - -def test_ljust(): - def ljust(tested: str, width: int) -> str: - return tested.ljust(width) - - def ljust_with_fill(tested: str, width: int, fill: str) -> str: - return tested.ljust(width, fill) - - ljust_verifier = verifier_for(ljust) - ljust_with_fill_verifier = verifier_for(ljust_with_fill) - - ljust_verifier.verify('test', 10, expected_result='test ') - ljust_verifier.verify('test', 9, expected_result='test ') - ljust_verifier.verify('test', 4, expected_result='test') - ljust_verifier.verify('test', 2, expected_result='test') - - ljust_with_fill_verifier.verify('test', 10, '#', expected_result='test######') - ljust_with_fill_verifier.verify('test', 9, '#', expected_result='test#####') - ljust_with_fill_verifier.verify('test', 4, '#', expected_result='test') - ljust_with_fill_verifier.verify('test', 2, '#', expected_result='test') - - -def test_lower(): - def lower(tested: str) -> str: - return tested.lower() - - lower_verifier = verifier_for(lower) - - lower_verifier.verify('', expected_result='') - - lower_verifier.verify('abc', expected_result='abc') - lower_verifier.verify('αβγ', expected_result='αβγ') - - lower_verifier.verify('ABC', expected_result='abc') - lower_verifier.verify('ΑΒΓ', expected_result='αβγ') - - lower_verifier.verify('NjDzᾩ', expected_result='njdzᾡ') - - lower_verifier.verify('ᐎᐐᐊ', expected_result='ᐎᐐᐊ') - - lower_verifier.verify('ʷʰʸ', expected_result='ʷʰʸ') - - lower_verifier.verify('123', expected_result='123') - lower_verifier.verify('౧౨౩', expected_result='౧౨౩') - - lower_verifier.verify('ⅠⅡⅢ', expected_result='ⅰⅱⅲ') - lower_verifier.verify('¼½¾', expected_result='¼½¾') - - lower_verifier.verify('ABC123', expected_result='abc123') - lower_verifier.verify('αβγ౧౨౩', expected_result='αβγ౧౨౩') - - lower_verifier.verify('+', expected_result='+') - lower_verifier.verify('±', expected_result='±') - lower_verifier.verify('×', expected_result='×') - - lower_verifier.verify('©', expected_result='©') - - lower_verifier.verify('[]', expected_result='[]') - - lower_verifier.verify('︳', expected_result='︳') - - lower_verifier.verify('-', expected_result='-') - - lower_verifier.verify('%', expected_result='%') - - lower_verifier.verify('\n', expected_result='\n') - - lower_verifier.verify('\u2029', expected_result='\u2029') # paragraph seperator - - lower_verifier.verify('\t', expected_result='\t') - lower_verifier.verify(' ', expected_result=' ') - - lower_verifier.verify('\U0001f64f', expected_result='\U0001f64f') # Need surrogates in UTF-16 - - # Fails due to https://github.com/jpype-project/jpype/issues/1091 - # lower_verifier.verify('\u0000', expected_result='\u0000') - - -def test_lstrip(): - def lstrip(tested: str) -> str: - return tested.lstrip() - - def lstrip_with_chars(tested: str, chars: str) -> str: - return tested.lstrip(chars) - - lstrip_verifier = verifier_for(lstrip) - lstrip_with_chars_verifier = verifier_for(lstrip_with_chars) - - lstrip_verifier.verify(' spacious ', expected_result='spacious ') - lstrip_with_chars_verifier.verify('www.example.com', 'cmowz.', expected_result='example.com') - - -def test_partition(): - def partition(tested: str, sep: str) -> tuple: - return tested.partition(sep) - - partition_verifier = verifier_for(partition) - - partition_verifier.verify('before+after+extra', '+', expected_result=('before', '+', 'after+extra')) - partition_verifier.verify('before and after and extra', '+', expected_result=('before and after and extra', '', '')) - partition_verifier.verify('before and after and extra', ' and ', - expected_result=('before', ' and ', 'after and extra')) - partition_verifier.verify('before+after+extra', ' and ', expected_result=('before+after+extra', '', '')) - - -def test_removeprefix(): - def removeprefix(tested: str, prefix: str) -> str: - return tested.removeprefix(prefix) - - removeprefix_verifier = verifier_for(removeprefix) - - removeprefix_verifier.verify('TestHook', 'Test', expected_result='Hook') - removeprefix_verifier.verify('BaseTestCase', 'Test', expected_result='BaseTestCase') - removeprefix_verifier.verify('BaseCaseTest', 'Test', expected_result='BaseCaseTest') - removeprefix_verifier.verify('BaseCase', 'Test', expected_result='BaseCase') - - -def test_removesuffix(): - def removesuffix(tested: str, prefix: str) -> str: - return tested.removesuffix(prefix) - - removesuffix_verifier = verifier_for(removesuffix) - - removesuffix_verifier.verify('MiscTests', 'Tests', expected_result='Misc') - removesuffix_verifier.verify('TmpTestsDirMixin', 'Tests', expected_result='TmpTestsDirMixin') - removesuffix_verifier.verify('TestsTmpDirMixin', 'Tests', expected_result='TestsTmpDirMixin') - removesuffix_verifier.verify('TmpDirMixin', 'Tests', expected_result='TmpDirMixin') - - -def test_replace(): - def replace(tested: str, substring: str, replacement: str) -> str: - return tested.replace(substring, replacement) - - def replace_with_count(tested: str, substring: str, replacement: str, count: int) -> str: - return tested.replace(substring, replacement, count) - - - replace_verifier = verifier_for(replace) - replace_with_count_verifier = verifier_for(replace_with_count) - - replace_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', - expected_result='all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', 0, - expected_result='all cats, including the cat Alcato, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', 1, - expected_result='all dogs, including the cat Alcato, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', 2, - expected_result='all dogs, including the dog Alcato, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', 3, - expected_result='all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', 4, - expected_result='all dogs, including the dog Aldogo, are animals') - replace_with_count_verifier.verify('all cats, including the cat Alcato, are animals', 'cat', 'dog', -1, - expected_result='all dogs, including the dog Aldogo, are animals') - - -def test_rfind(): - def rfind(tested: str, item: str) -> int: - return tested.rfind(item) - - def rfind_start_verifier(tested: str, item: str, start: int) -> int: - return tested.rfind(item, start) - - def rfind_start_end_verifier(tested: str, item: str, start: int, end: int) -> int: - return tested.rfind(item, start, end) - - rfind_verifier = verifier_for(rfind) - rfind_start_verifier = verifier_for(rfind_start_verifier) - rfind_start_end_verifier = verifier_for(rfind_start_end_verifier) - - rfind_verifier.verify('abcabc', 'a', expected_result=3) - rfind_verifier.verify('abcabc', 'b', expected_result=4) - rfind_verifier.verify('abcabc', 'd', expected_result=-1) - - rfind_start_verifier.verify('abcabc', 'a', 1, expected_result=3) - rfind_start_verifier.verify('abcabc', 'a', 5, expected_result=-1) - rfind_start_verifier.verify('abcabc', 'b', 1, expected_result=4) - rfind_start_verifier.verify('abcabc', 'c', 1, expected_result=5) - rfind_start_verifier.verify('abcabc', 'd', 1, expected_result=-1) - - rfind_start_verifier.verify('abcabc', 'a', -3, expected_result=3) - rfind_start_verifier.verify('abcabc', 'b', -2, expected_result=4) - rfind_start_verifier.verify('abcabc', 'c', -2, expected_result=5) - rfind_start_verifier.verify('abcabc', 'd', -2, expected_result=-1) - - rfind_start_end_verifier.verify('abcabc', 'a', 1, 2, expected_result=-1) - rfind_start_end_verifier.verify('abcabc', 'b', 1, 2, expected_result=1) - rfind_start_end_verifier.verify('abcabc', 'c', 1, 2, expected_result=-1) - rfind_start_end_verifier.verify('abcabc', 'd', 1, 2, expected_result=-1) - - rfind_start_end_verifier.verify('abcabc', 'a', -2, -1, expected_result=-1) - rfind_start_end_verifier.verify('abcabc', 'b', -2, -1, expected_result=4) - rfind_start_end_verifier.verify('abcabc', 'c', -2, -1, expected_result=-1) - rfind_start_end_verifier.verify('abcabc', 'd', -2, -1, expected_result=-1) - - -def test_rindex(): - def rindex(tested: str, item: str) -> int: - return tested.rindex(item) - - def rindex_start_verifier(tested: str, item: str, start: int) -> int: - return tested.rindex(item, start) - - def rindex_start_end_verifier(tested: str, item: str, start: int, end: int) -> int: - return tested.rindex(item, start, end) - - rindex_verifier = verifier_for(rindex) - rindex_start_verifier = verifier_for(rindex_start_verifier) - rindex_start_end_verifier = verifier_for(rindex_start_end_verifier) - - rindex_verifier.verify('abcabc', 'a', expected_result=3) - rindex_verifier.verify('abcabc', 'b', expected_result=4) - rindex_verifier.verify('abcabc', 'd', expected_error=ValueError) - - rindex_start_verifier.verify('abcabc', 'a', 1, expected_result=3) - rindex_start_verifier.verify('abcabc', 'a', 5, expected_error=ValueError) - rindex_start_verifier.verify('abcabc', 'b', 1, expected_result=4) - rindex_start_verifier.verify('abcabc', 'c', 1, expected_result=5) - rindex_start_verifier.verify('abcabc', 'd', 1, expected_error=ValueError) - - rindex_start_verifier.verify('abcabc', 'a', -3, expected_result=3) - rindex_start_verifier.verify('abcabc', 'b', -2, expected_result=4) - rindex_start_verifier.verify('abcabc', 'c', -2, expected_result=5) - rindex_start_verifier.verify('abcabc', 'd', -2, expected_error=ValueError) - - rindex_start_end_verifier.verify('abcabc', 'a', 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify('abcabc', 'b', 1, 2, expected_result=1) - rindex_start_end_verifier.verify('abcabc', 'c', 1, 2, expected_error=ValueError) - rindex_start_end_verifier.verify('abcabc', 'd', 1, 2, expected_error=ValueError) - - rindex_start_end_verifier.verify('abcabc', 'a', -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify('abcabc', 'b', -2, -1, expected_result=4) - rindex_start_end_verifier.verify('abcabc', 'c', -2, -1, expected_error=ValueError) - rindex_start_end_verifier.verify('abcabc', 'd', -2, -1, expected_error=ValueError) - -def test_rjust(): - def rjust(tested: str, width: int) -> str: - return tested.rjust(width) - - def rjust_with_fill(tested: str, width: int, fill: str) -> str: - return tested.rjust(width, fill) - - rjust_verifier = verifier_for(rjust) - rjust_with_fill_verifier = verifier_for(rjust_with_fill) - - rjust_verifier.verify('test', 10, expected_result=' test') - rjust_verifier.verify('test', 9, expected_result=' test') - rjust_verifier.verify('test', 4, expected_result='test') - rjust_verifier.verify('test', 2, expected_result='test') - - rjust_with_fill_verifier.verify('test', 10, '#', expected_result='######test') - rjust_with_fill_verifier.verify('test', 9, '#', expected_result='#####test') - rjust_with_fill_verifier.verify('test', 4, '#', expected_result='test') - rjust_with_fill_verifier.verify('test', 2, '#', expected_result='test') - - -def test_rpartition(): - def rpartition(tested: str, sep: str) -> tuple: - return tested.rpartition(sep) - - rpartition_verifier = verifier_for(rpartition) - - rpartition_verifier.verify('before+after+extra', '+', expected_result=('before+after', '+', 'extra')) - rpartition_verifier.verify('before and after and extra', '+', expected_result=('', '', 'before and after and extra')) - rpartition_verifier.verify('before and after and extra', ' and ', - expected_result=('before and after', ' and ', 'extra')) - rpartition_verifier.verify('before+after+extra', ' and ', expected_result=('', '', 'before+after+extra')) - - -def test_rsplit(): - def rsplit(tested: str) -> list: - return tested.rsplit() - - def rsplit_with_sep(tested: str, sep: str) -> list: - return tested.rsplit(sep) - - def rsplit_with_sep_and_count(tested: str, sep: str, count: int) -> list: - return tested.rsplit(sep, count) - - rsplit_verifier = verifier_for(rsplit) - rsplit_with_sep_verifier = verifier_for(rsplit_with_sep) - rsplit_with_sep_and_count_verifier = verifier_for(rsplit_with_sep_and_count) - - rsplit_verifier.verify('123', expected_result=['123']) - rsplit_verifier.verify('1 2 3', expected_result=['1', '2', '3']) - rsplit_verifier.verify(' 1 2 3 ', expected_result=['1', '2', '3']) - rsplit_verifier.verify('1\n2\n3', expected_result=['1', '2', '3']) - rsplit_verifier.verify('1\t2\t3', expected_result=['1', '2', '3']) - - rsplit_with_sep_verifier.verify('1,2,3', ',', expected_result=['1', '2', '3']) - rsplit_with_sep_verifier.verify('1,2,,3,', ',', expected_result=['1', '2', '', '3', '']) - rsplit_with_sep_verifier.verify(',1,2,,3,', ',', expected_result=['', '1', '2', '', '3', '']) - - rsplit_with_sep_and_count_verifier.verify('1,2,3', ',', 1, expected_result=['1,2', '3']) - rsplit_with_sep_and_count_verifier.verify('1,2,,3,', ',', 1, expected_result=['1,2,,3', '']) - rsplit_with_sep_and_count_verifier.verify('1,2,,3,', ',', 2, expected_result=['1,2,', '3', '']) - rsplit_with_sep_and_count_verifier.verify('1,2,,3,', ',', 3, expected_result=['1,2', '', '3', '']) - rsplit_with_sep_and_count_verifier.verify('1,2,,3,', ',', 4, expected_result=['1', '2', '', '3', '']) - - -def test_rstrip(): - def rstrip(tested: str) -> str: - return tested.rstrip() - - def rstrip_with_chars(tested: str, chars: str) -> str: - return tested.rstrip(chars) - - rstrip_verifier = verifier_for(rstrip) - rstrip_with_chars_verifier = verifier_for(rstrip_with_chars) - - rstrip_verifier.verify(' spacious ', expected_result=' spacious') - rstrip_with_chars_verifier.verify('www.example.com', 'cmowz.', expected_result='www.example') - - -def test_split(): - def split(tested: str) -> list: - return tested.split() - - def split_with_sep(tested: str, sep: str) -> list: - return tested.split(sep) - - def split_with_sep_and_count(tested: str, sep: str, count: int) -> list: - return tested.split(sep, count) - - split_verifier = verifier_for(split) - split_with_sep_verifier = verifier_for(split_with_sep) - split_with_sep_and_count_verifier = verifier_for(split_with_sep_and_count) - - split_verifier.verify('123', expected_result=['123']) - split_verifier.verify('1 2 3', expected_result=['1', '2', '3']) - split_verifier.verify(' 1 2 3 ', expected_result=['1', '2', '3']) - split_verifier.verify('1\n2\n3', expected_result=['1', '2', '3']) - split_verifier.verify('1\t2\t3', expected_result=['1', '2', '3']) - - split_with_sep_verifier.verify('1,2,3', ',', expected_result=['1', '2', '3']) - split_with_sep_verifier.verify('1,2,,3,', ',', expected_result=['1', '2', '', '3', '']) - split_with_sep_verifier.verify(',1,2,,3,', ',', expected_result=['', '1', '2', '', '3', '']) - - split_with_sep_and_count_verifier.verify('1,2,3', ',', 1, expected_result=['1', '2,3']) - split_with_sep_and_count_verifier.verify('1,2,,3,', ',', 1, expected_result=['1', '2,,3,']) - split_with_sep_and_count_verifier.verify('1,2,,3,', ',', 2, expected_result=['1', '2', ',3,']) - split_with_sep_and_count_verifier.verify('1,2,,3,', ',', 3, expected_result=['1', '2', '', '3,']) - split_with_sep_and_count_verifier.verify('1,2,,3,', ',', 4, expected_result=['1', '2', '', '3', '']) - - -def test_splitlines(): - def splitlines(tested: str) -> list: - return tested.splitlines() - - def splitlines_keep_ends(tested: str, keep_ends: bool) -> list: - return tested.splitlines(keep_ends) - - splitlines_verifier = verifier_for(splitlines) - splitlines_keep_ends_verifier = verifier_for(splitlines_keep_ends) - - splitlines_verifier.verify('ab c\n\nde fg\rkl\r\n', expected_result=['ab c', '', 'de fg', 'kl']) - splitlines_verifier.verify('', expected_result=[]) - splitlines_verifier.verify('One line\n', expected_result=['One line']) - - splitlines_keep_ends_verifier.verify('ab c\n\nde fg\rkl\r\n', False, expected_result=['ab c', '', 'de fg', 'kl']) - splitlines_keep_ends_verifier.verify('ab c\n\nde fg\rkl\r\n', True, - expected_result=['ab c\n', '\n', 'de fg\r', 'kl\r\n']) - splitlines_keep_ends_verifier.verify('', True, expected_result=[]) - splitlines_keep_ends_verifier.verify('', False, expected_result=[]) - splitlines_keep_ends_verifier.verify('One line\n', True, expected_result=['One line\n']) - splitlines_keep_ends_verifier.verify('One line\n', False, expected_result=['One line']) - - -def test_startswith(): - def startswith(tested: str, suffix: str) -> bool: - return tested.startswith(suffix) - - def startswith_start(tested: str, suffix: str, start: int) -> bool: - return tested.startswith(suffix, start) - - def startswith_between(tested: str, suffix: str, start: int, end: int) -> bool: - return tested.startswith(suffix, start, end) - - startswith_verifier = verifier_for(startswith) - startswith_start_verifier = verifier_for(startswith_start) - startswith_between_verifier = verifier_for(startswith_between) - - startswith_verifier.verify('hello world', 'hello', expected_result=True) - startswith_verifier.verify('hello world', 'world', expected_result=False) - startswith_verifier.verify('hello', 'hello world', expected_result=False) - startswith_verifier.verify('hello world', 'hello world', expected_result=True) - - startswith_start_verifier.verify('hello world', 'world', 6, expected_result=True) - startswith_start_verifier.verify('hello world', 'hello', 6, expected_result=False) - startswith_start_verifier.verify('hello', 'hello world', 6, expected_result=False) - startswith_start_verifier.verify('hello world', 'hello world', 6, expected_result=False) - - startswith_between_verifier.verify('hello world', 'world', 6, 11, expected_result=True) - startswith_between_verifier.verify('hello world', 'world', 7, 11, expected_result=False) - startswith_between_verifier.verify('hello world', 'hello', 0, 5, expected_result=True) - startswith_between_verifier.verify('hello', 'hello world', 0, 5, expected_result=False) - startswith_between_verifier.verify('hello world', 'hello world', 5, 11, expected_result=False) - - -def test_strip(): - def strip(tested: str) -> str: - return tested.strip() - - def strip_with_chars(tested: str, chars: str) -> str: - return tested.strip(chars) - - strip_verifier = verifier_for(strip) - strip_with_chars_verifier = verifier_for(strip_with_chars) - - strip_verifier.verify(' spacious ', expected_result='spacious') - strip_with_chars_verifier.verify('www.example.com', 'cmowz.', expected_result='example') - - -def test_swapcase(): - def swapcase(tested: str) -> str: - return tested.swapcase() - - swapcase_verifier = verifier_for(swapcase) - - swapcase_verifier.verify('', expected_result='') - - swapcase_verifier.verify('abc', expected_result='ABC') - swapcase_verifier.verify('αβγ', expected_result='ΑΒΓ') - - swapcase_verifier.verify('ABC', expected_result='abc') - swapcase_verifier.verify('ΑΒΓ', expected_result='αβγ') - - swapcase_verifier.verify('NjDzᾩ', expected_result='NjDzᾩ') - - swapcase_verifier.verify('ᐎᐐᐊ', expected_result='ᐎᐐᐊ') - - swapcase_verifier.verify('ʷʰʸ', expected_result='ʷʰʸ') - - swapcase_verifier.verify('123', expected_result='123') - swapcase_verifier.verify('౧౨౩', expected_result='౧౨౩') - - swapcase_verifier.verify('ⅠⅡⅢ', expected_result='ⅰⅱⅲ') - swapcase_verifier.verify('ⅰⅱⅲ', expected_result='ⅠⅡⅢ') - swapcase_verifier.verify('¼½¾', expected_result='¼½¾') - - swapcase_verifier.verify('ABC123', expected_result='abc123') - swapcase_verifier.verify('αβγ౧౨౩', expected_result='ΑΒΓ౧౨౩') - - swapcase_verifier.verify('+', expected_result='+') - swapcase_verifier.verify('±', expected_result='±') - swapcase_verifier.verify('×', expected_result='×') - - swapcase_verifier.verify('©', expected_result='©') - - swapcase_verifier.verify('[]', expected_result='[]') - - swapcase_verifier.verify('︳', expected_result='︳') - - swapcase_verifier.verify('-', expected_result='-') - - swapcase_verifier.verify('%', expected_result='%') - - swapcase_verifier.verify('\n', expected_result='\n') - - swapcase_verifier.verify('\u2029', expected_result='\u2029') # paragraph seperator - - swapcase_verifier.verify('\t', expected_result='\t') - swapcase_verifier.verify(' ', expected_result=' ') - - swapcase_verifier.verify('\U0001f64f', expected_result='\U0001f64f') # Need surrogates in UTF-16 - - # Fails due to https://github.com/jpype-project/jpype/issues/1091 - # swapcase_verifier.verify('\u0000', expected_result='\u0000') - - -def test_title(): - def title(tested: str) -> str: - return tested.title() - - title_verifier = verifier_for(title) - - title_verifier.verify('', expected_result='') - title_verifier.verify('Hello world', expected_result='Hello World') - title_verifier.verify("they're bill's friends from the UK", - expected_result="They'Re Bill'S Friends From The Uk") - - title_verifier.verify('abc', expected_result='Abc') - title_verifier.verify('αβγ', expected_result='Αβγ') - - title_verifier.verify('ABC', expected_result='Abc') - title_verifier.verify('ΑΒΓ', expected_result='Αβγ') - - title_verifier.verify('NjDzᾩ', expected_result='Njdzᾡ') - - title_verifier.verify('ᐎᐐᐊ', expected_result='ᐎᐐᐊ') - - title_verifier.verify('ʷʰʸ', expected_result='ʷʰʸ') - - title_verifier.verify('123', expected_result='123') - title_verifier.verify('౧౨౩', expected_result='౧౨౩') - - title_verifier.verify('ⅠⅡⅢ', expected_result='Ⅰⅱⅲ') - title_verifier.verify('ⅰⅱⅲ', expected_result='Ⅰⅱⅲ') - title_verifier.verify('¼½¾', expected_result='¼½¾') - - title_verifier.verify('ABC123', expected_result='Abc123') - title_verifier.verify('αβγ౧౨౩', expected_result='Αβγ౧౨౩') - - title_verifier.verify('+', expected_result='+') - title_verifier.verify('±', expected_result='±') - title_verifier.verify('×', expected_result='×') - - title_verifier.verify('©', expected_result='©') - - title_verifier.verify('[]', expected_result='[]') - - title_verifier.verify('︳', expected_result='︳') - - title_verifier.verify('-', expected_result='-') - - title_verifier.verify('%', expected_result='%') - - title_verifier.verify('\n', expected_result='\n') - - title_verifier.verify('\u2029', expected_result='\u2029') # paragraph seperator - - title_verifier.verify('\t', expected_result='\t') - title_verifier.verify(' ', expected_result=' ') - - title_verifier.verify('\U0001f64f', expected_result='\U0001f64f') # Need surrogates in UTF-16 - - # Fails due to https://github.com/jpype-project/jpype/issues/1091 - # swapcase_verifier.verify('\u0000', expected_result='\u0000') - - -def test_translate(): - def translate(tested: str, mapping: dict) -> str: - return tested.translate(mapping) - - translate_verifier = verifier_for(translate) - - translate_verifier.verify('hello world', - { - ord('l'): '7', - ord('h'): 'H', - ord('w'): 'WO', - ord('d'): '' - }, expected_result='He77o WOor7') - - -def test_upper(): - def upper(tested: str) -> str: - return tested.upper() - - upper_verifier = verifier_for(upper) - - upper_verifier.verify('', expected_result='') - - upper_verifier.verify('abc', expected_result='ABC') - upper_verifier.verify('αβγ', expected_result='ΑΒΓ') - - upper_verifier.verify('ABC', expected_result='ABC') - upper_verifier.verify('ΑΒΓ', expected_result='ΑΒΓ') - - upper_verifier.verify('NjDzᾩ', expected_result='NJDZὩΙ') - - upper_verifier.verify('ᐎᐐᐊ', expected_result='ᐎᐐᐊ') - - upper_verifier.verify('ʷʰʸ', expected_result='ʷʰʸ') - - upper_verifier.verify('123', expected_result='123') - upper_verifier.verify('౧౨౩', expected_result='౧౨౩') - - upper_verifier.verify('ⅰⅱⅲ', expected_result='ⅠⅡⅢ') - upper_verifier.verify('ⅠⅡⅢ', expected_result='ⅠⅡⅢ') - upper_verifier.verify('¼½¾', expected_result='¼½¾') - - upper_verifier.verify('ABC123', expected_result='ABC123') - upper_verifier.verify('αβγ౧౨౩', expected_result='ΑΒΓ౧౨౩') - - upper_verifier.verify('+', expected_result='+') - upper_verifier.verify('±', expected_result='±') - upper_verifier.verify('×', expected_result='×') - - upper_verifier.verify('©', expected_result='©') - - upper_verifier.verify('[]', expected_result='[]') - - upper_verifier.verify('︳', expected_result='︳') - - upper_verifier.verify('-', expected_result='-') - - upper_verifier.verify('%', expected_result='%') - - upper_verifier.verify('\n', expected_result='\n') - - upper_verifier.verify('\u2029', expected_result='\u2029') # paragraph seperator - - upper_verifier.verify('\t', expected_result='\t') - upper_verifier.verify(' ', expected_result=' ') - - upper_verifier.verify('\U0001f64f', expected_result='\U0001f64f') # Need surrogates in UTF-16 - - # Fails due to https://github.com/jpype-project/jpype/issues/1091 - # lower_verifier.verify('\u0000', expected_result='\u0000') - - -def test_zfill(): - def zfill(tested: str, padding: int) -> str: - return tested.zfill(padding) - - zfill_verifier = verifier_for(zfill) - - zfill_verifier.verify('42', 5, expected_result='00042') - zfill_verifier.verify('-42', 5, expected_result='-0042') - zfill_verifier.verify('+42', 5, expected_result='+0042') - zfill_verifier.verify('42', 1, expected_result='42') - zfill_verifier.verify('-42', 1, expected_result='-42') - zfill_verifier.verify('+42', 1, expected_result='+42') - zfill_verifier.verify('abc', 10, expected_result='0000000abc') - zfill_verifier.verify('ᐎᐐᐊ', 5, expected_result='00ᐎᐐᐊ') - zfill_verifier.verify('+ᐎᐐᐊ', 5, expected_result='+0ᐎᐐᐊ') - zfill_verifier.verify('-ᐎᐐᐊ', 5, expected_result='-0ᐎᐐᐊ') diff --git a/jpyinterpreter/tests/test_traceback.py b/jpyinterpreter/tests/test_traceback.py deleted file mode 100644 index 4ac2ac6c..00000000 --- a/jpyinterpreter/tests/test_traceback.py +++ /dev/null @@ -1,100 +0,0 @@ -from .conftest import verifier_for - -def test_function_traceback(): - def my_function_1(): - my_function_2() - - def my_function_2(): - raise Exception('Message') - - def check_traceback(error: Exception): - from traceback import format_exception - traceback = '\n'.join(format_exception(type(error), error, error.__traceback__)) - if 'test_traceback.py", line 5, in my_function_1\n' not in traceback: - return False - - if 'test_traceback.py", line 8, in my_function_2\n' not in traceback: - return False - - if not traceback.strip().endswith('Exception: Message'): - return False - - if 'File "PythonException.java"' in traceback: - return False - - return True - - verifier = verifier_for(my_function_1) - verifier.verify_error_property(predicate=check_traceback) - - -def test_class_traceback(): - class A: - def my_function(self): - raise ValueError('Message') - - def check_traceback(error: Exception): - from traceback import format_exception - traceback = '\n'.join(format_exception(type(error), error, error.__traceback__)) - if 'test_traceback.py", line 34, in my_function\n' not in traceback: - return False - - if not traceback.strip().endswith('ValueError: Message'): - return False - - if 'File "PythonException.java"' in traceback: - return False - - return True - - def call_class_function(): - return A().my_function() - - verifier = verifier_for(call_class_function) - verifier.verify_error_property(predicate=check_traceback) - - -def test_chained_traceback(): - def first(): - return second() - - def second(): - try: - return third() - except ValueError as e: - raise RuntimeError('Consequence') from e - - def third(): - raise ValueError("Cause") - - def check_traceback(error: Exception): - from traceback import format_exception - traceback = '\n'.join(format_exception(type(error), error, error.__traceback__)) - if 'test_traceback.py", line 59, in first\n' not in traceback: - return False - - if 'test_traceback.py", line 63, in second\n' not in traceback: - return False - - if 'ValueError: Cause' not in traceback: - return False - - if 'The above exception was the direct cause of the following exception:\n' not in traceback: - return False - - if 'test_traceback.py", line 65, in second\n' not in traceback: - return False - - if 'test_traceback.py", line 68, in third\n' not in traceback: - return False - - if not traceback.strip().endswith('RuntimeError: Consequence'): - return False - - if 'File "PythonException.java"' in traceback: - return False - - return True - - verifier = verifier_for(first) - verifier.verify_error_property(predicate=check_traceback) diff --git a/jpyinterpreter/tests/test_try_except.py b/jpyinterpreter/tests/test_try_except.py deleted file mode 100644 index 705d6f29..00000000 --- a/jpyinterpreter/tests/test_try_except.py +++ /dev/null @@ -1,123 +0,0 @@ -from .conftest import verifier_for - - -def test_try_except_with_exception_type(): - def my_function(arg: str) -> int: - try: - if arg == 'ValueError': - raise ValueError - elif arg == 'TypeError': - raise TypeError - return 0 - except ValueError: - return 1 - - function_verifier = verifier_for(my_function) - - function_verifier.verify('Normal', expected_result=0) - function_verifier.verify('ValueError', expected_result=1) - function_verifier.verify('TypeError', expected_error=TypeError) - - -def test_try_except_with_exception_instance(): - def my_function(arg: str) -> object: - try: - if arg == 'ValueError': - raise ValueError('A value error') - elif arg == 'TypeError': - raise TypeError('A type error') - return 0 - except ValueError as e: - return e.args[0] - - function_verifier = verifier_for(my_function) - - function_verifier.verify('Normal', expected_result=0) - function_verifier.verify('ValueError', expected_result='A value error') - function_verifier.verify('TypeError', expected_error=TypeError) - - -def test_nested_try_except(): - def my_function(arg: str) -> int: - try: - if arg == 'ValueError1': - raise ValueError - try: - if arg == 'ValueError2': - raise ValueError - elif arg == 'TypeError': - raise TypeError - elif arg == 'KeyError': - raise KeyError - except ValueError: - return 1 - except ValueError: - return 2 - except TypeError: - return 3 - return 4 - - function_verifier = verifier_for(my_function) - - function_verifier.verify('Normal', expected_result=4) - function_verifier.verify('ValueError1', expected_result=2) - function_verifier.verify('ValueError2', expected_result=1) - function_verifier.verify('TypeError', expected_result=3) - function_verifier.verify('KeyError', expected_error=KeyError) - - -def test_try_except_finally(): - def my_function(arg: str) -> list: - out = [] - try: - try: - if arg == 'ValueError': - raise ValueError - elif arg == 'KeyError': - raise KeyError - out.append('Try') - return out - except ValueError: - out.append('ValueError') - finally: - out.append('Finally') - finally: - return out - - function_verifier = verifier_for(my_function) - - function_verifier.verify('Normal', expected_result=['Try', 'Finally']) - function_verifier.verify('ValueError', expected_result=['ValueError', 'Finally']) - function_verifier.verify('KeyError', expected_result=['Finally']) - - -def test_raise_with_cause(): - def my_function(arg: BaseException) -> str: - try: - raise ValueError from arg - except ValueError as e: - return e.__cause__.args[0] - - function_verifier = verifier_for(my_function) - - function_verifier.verify(ValueError('value message'), expected_result='value message') - function_verifier.verify(TypeError('type message'), expected_result='type message') - function_verifier.verify(KeyError('key message'), expected_result='key message') - - -def test_reraise(): - def my_function(arg: str) -> int: - try: - if arg == 'ValueError': - raise ValueError - elif arg == 'KeyError': - raise KeyError - return 0 - except Exception: - raise - - function_verifier = verifier_for(my_function) - - function_verifier.verify('Normal', expected_result=0) - function_verifier.verify('ValueError', expected_error=ValueError) - function_verifier.verify('KeyError', expected_error=KeyError) diff --git a/jpyinterpreter/tests/test_tuple.py b/jpyinterpreter/tests/test_tuple.py deleted file mode 100644 index 09705630..00000000 --- a/jpyinterpreter/tests/test_tuple.py +++ /dev/null @@ -1,247 +0,0 @@ -from .conftest import verifier_for -from typing import Union - - -def test_membership(): - def membership(tested: tuple, x: object): - return x in tested - - def not_membership(tested: tuple, x: object): - return x not in tested - - membership_verifier = verifier_for(membership) - not_membership_verifier = verifier_for(not_membership) - - membership_verifier.verify((1, 2, 3), 1, expected_result=True) - not_membership_verifier.verify((1, 2, 3), 1, expected_result=False) - - membership_verifier.verify((1, 2, 3), 4, expected_result=False) - not_membership_verifier.verify((1, 2, 3), 4, expected_result=True) - - membership_verifier.verify((1, 2, 3), 3, expected_result=True) - not_membership_verifier.verify((1, 2, 3), 3, expected_result=False) - - -def test_concat(): - def concat(x: tuple, y: tuple): - out = x + y - return out, out is x, out is y - - concat_verifier = verifier_for(concat) - - concat_verifier.verify((1, 2), (3, 4), expected_result=((1, 2, 3, 4), False, False)) - concat_verifier.verify((), (1, 2, 3), expected_result=((1, 2, 3), False, True)) - concat_verifier.verify((1, 2, 3), (), expected_result=((1, 2, 3), True, False)) - concat_verifier.verify((3,), (2, 1), expected_result=((3, 2, 1), False, False)) - - -def test_repeat(): - def repeat_left(x: tuple, y: int): - out = x * y - return out, out is x, out is y - - def repeat_right(x: int, y: tuple): - out = x * y - return out, out is x, out is y - - repeat_left_verifier = verifier_for(repeat_left) - repeat_right_verifier = verifier_for(repeat_right) - - repeat_left_verifier.verify((1, 2, 3), 1, expected_result=((1, 2, 3), True, False)) - repeat_left_verifier.verify((1, 2, 3), 2, expected_result=((1, 2, 3, 1, 2, 3), False, False)) - repeat_left_verifier.verify((1, 2), 4, expected_result=((1, 2, 1, 2, 1, 2, 1, 2), False, False)) - repeat_left_verifier.verify((1, 2, 3), 0, expected_result=((), False, False)) - repeat_left_verifier.verify((1, 2, 3), -1, expected_result=((), False, False)) - repeat_left_verifier.verify((1, 2, 3), -2, expected_result=((), False, False)) - - repeat_right_verifier.verify(1, (1, 2, 3), expected_result=((1, 2, 3), False, True)) - repeat_right_verifier.verify(2, (1, 2, 3), expected_result=((1, 2, 3, 1, 2, 3), False, False)) - repeat_right_verifier.verify(4, (1, 2), expected_result=((1, 2, 1, 2, 1, 2, 1, 2), False, False)) - repeat_right_verifier.verify(0, (1, 2, 3), expected_result=((), False, False)) - repeat_right_verifier.verify(-1, (1, 2, 3), expected_result=((), False, False)) - repeat_right_verifier.verify(-2, (1, 2, 3), expected_result=((), False, False)) - - -def test_get_item(): - def get_item(tested: tuple, index: int): - return tested[index] - - get_item_verifier = verifier_for(get_item) - - get_item_verifier.verify((1, 2, 3), 1, expected_result=2) - get_item_verifier.verify((1, 2, 3), -1, expected_result=3) - get_item_verifier.verify((1, 2, 3, 4), -1, expected_result=4) - get_item_verifier.verify((1, 2, 3, 4), -2, expected_result=3) - get_item_verifier.verify((1, 2, 3, 4), 0, expected_result=1) - get_item_verifier.verify((1, 2, 3), 3, expected_error=IndexError) - get_item_verifier.verify((1, 2, 3), -4, expected_error=IndexError) - - -def test_get_slice(): - def get_slice(tested: tuple, start: Union[int, None], end: Union[int, None]): - return tested[start:end] - - get_slice_verifier = verifier_for(get_slice) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 1, 3, expected_result=(2, 3)) - get_slice_verifier.verify((1, 2, 3, 4, 5), -3, -1, expected_result=(3, 4)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, -2, expected_result=(1, 2, 3)) - get_slice_verifier.verify((1, 2, 3, 4, 5), -3, 4, expected_result=(3, 4)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 3, 1, expected_result=()) - get_slice_verifier.verify((1, 2, 3, 4, 5), -1, -3, expected_result=()) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 100, 1000, expected_result=()) - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, 1000, expected_result=(1, 2, 3, 4, 5)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 1, None, expected_result=(2, 3, 4, 5)) - get_slice_verifier.verify((1, 2, 3, 4, 5), None, 2, expected_result=(1, 2)) - get_slice_verifier.verify((1, 2, 3, 4, 5), None, None, expected_result=(1, 2, 3, 4, 5)) - - -def test_get_slice_with_step(): - def get_slice_with_step(tested: tuple, start: Union[int, None], end: Union[int, None], step: Union[int, None]): - return tested[start:end:step] - - get_slice_verifier = verifier_for(get_slice_with_step) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, None, 2, expected_result=(1, 3, 5)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 1, None, 2, expected_result=(2, 4)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, 5, 2, expected_result=(1, 3, 5)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 1, 5, 2, expected_result=(2, 4)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, -1, 2, expected_result=(1, 3)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 1, -1, 2, expected_result=(2, 4)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 4, None, -2, expected_result=(5, 3, 1)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 3, None, -2, expected_result=(4, 2)) - get_slice_verifier.verify((1, 2, 3, 4, 5), -1, -6, -2, expected_result=(5, 3, 1)) - get_slice_verifier.verify((1, 2, 3, 4, 5), -2, -6, -2, expected_result=(4, 2)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 4, 0, -2, expected_result=(5, 3)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 3, 0, -2, expected_result=(4, 2)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, None, None, expected_result=(1, 2, 3, 4, 5)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 0, 3, None, expected_result=(1, 2, 3)) - - get_slice_verifier.verify((1, 2, 3, 4, 5), 3, 1, -1, expected_result=(4, 3)) - get_slice_verifier.verify((1, 2, 3, 4, 5), -1, -3, -1, expected_result=(5, 4)) - get_slice_verifier.verify((1, 2, 3, 4, 5), 3, 1, 1, expected_result=()) - get_slice_verifier.verify((1, 2, 3, 4, 5), -1, -3, 1, expected_result=()) - - -def test_len(): - def length(tested: tuple): - return len(tested) - - len_verifier = verifier_for(length) - - len_verifier.verify((), expected_result=0) - len_verifier.verify((1,), expected_result=1) - len_verifier.verify((1, 2), expected_result=2) - len_verifier.verify((3, 2, 1), expected_result=3) - - -def test_index(): - def index(tested: tuple, item: object): - return tested.index(item) - - def index_start(tested: tuple, item: object, start: int): - return tested.index(item, start) - - def index_start_end(tested: tuple, item: object, start: int, end: int): - return tested.index(item, start, end) - - index_verifier = verifier_for(index) - index_start_verifier = verifier_for(index_start) - index_start_end_verifier = verifier_for(index_start_end) - - index_verifier.verify((1, 2, 3), 1, expected_result=0) - index_verifier.verify((1, 2, 3), 2, expected_result=1) - index_verifier.verify((1, 2, 3), 5, expected_error=ValueError) - - index_start_verifier.verify((1, 2, 3), 1, 1, expected_error=ValueError) - index_start_verifier.verify((1, 2, 3), 2, 1, expected_result=1) - index_start_verifier.verify((1, 2, 3), 3, 1, expected_result=2) - index_start_verifier.verify((1, 2, 3), 5, 1, expected_error=ValueError) - - index_start_verifier.verify((1, 2, 3), 1, -2, expected_error=ValueError) - index_start_verifier.verify((1, 2, 3), 2, -2, expected_result=1) - index_start_verifier.verify((1, 2, 3), 3, -2, expected_result=2) - index_start_verifier.verify((1, 2, 3), 5, -2, expected_error=ValueError) - - index_start_end_verifier.verify((1, 2, 3), 1, 1, 2, expected_error=ValueError) - index_start_end_verifier.verify((1, 2, 3), 2, 1, 2, expected_result=1) - index_start_end_verifier.verify((1, 2, 3), 3, 1, 2, expected_error=ValueError) - index_start_end_verifier.verify((1, 2, 3), 5, 1, 2, expected_error=ValueError) - - index_start_end_verifier.verify((1, 2, 3), 1, -2, -1, expected_error=ValueError) - index_start_end_verifier.verify((1, 2, 3), 2, -2, -1, expected_result=1) - index_start_end_verifier.verify((1, 2, 3), 3, -2, -1, expected_error=ValueError) - index_start_end_verifier.verify((1, 2, 3), 5, -2, -1, expected_error=ValueError) - - -def test_count(): - def count(tested: tuple, item: object): - return tested.count(item) - - count_verifier = verifier_for(count) - - count_verifier.verify((1, 2, 3), 1, expected_result=1) - count_verifier.verify((1, 2, 3), 2, expected_result=1) - count_verifier.verify((1, 2, 3), 3, expected_result=1) - count_verifier.verify((1, 2, 3), 4, expected_result=0) - - count_verifier.verify((1, 2, 3, 1), 1, expected_result=2) - count_verifier.verify((1, 1, 3, 1), 1, expected_result=3) - count_verifier.verify((), 1, expected_result=0) - - -def test_compare_methods(): - def less_than(a: tuple, b: tuple): - return a < b - - def greater_than(a: tuple, b: tuple): - return a > b - - def less_than_or_equal(a: tuple, b: tuple): - return a <= b - - def greater_than_or_equal(a: tuple, b: tuple): - return a >= b - - less_than_verifier = verifier_for(less_than) - greater_than_verifier = verifier_for(greater_than) - less_than_or_equal_verifier = verifier_for(less_than_or_equal) - greater_than_or_equal_verifier = verifier_for(greater_than_or_equal) - - less_than_verifier.verify((1, 1), (1, 1), expected_result=False) - less_than_verifier.verify((1, 1), (1, 2), expected_result=True) - less_than_verifier.verify((1, 2), (2, 1), expected_result=True) - less_than_verifier.verify((1, 1), (1, 1, 1), expected_result=True) - less_than_verifier.verify((2, 1), (1, 2), expected_result=False) - less_than_verifier.verify((1, 2), (1, 1), expected_result=False) - less_than_verifier.verify((1, 1, 1), (1, 1), expected_result=False) - - greater_than_verifier.verify((1, 1), (1, 1), expected_result=False) - greater_than_verifier.verify((1, 1), (1, 2), expected_result=False) - greater_than_verifier.verify((1, 2), (2, 1), expected_result=False) - greater_than_verifier.verify((1, 1), (1, 1, 1), expected_result=False) - greater_than_verifier.verify((2, 1), (1, 2), expected_result=True) - greater_than_verifier.verify((1, 2), (1, 1), expected_result=True) - greater_than_verifier.verify((1, 1, 1), (1, 1), expected_result=True) - - less_than_or_equal_verifier.verify((1, 1), (1, 1), expected_result=True) - less_than_or_equal_verifier.verify((1, 1), (1, 2), expected_result=True) - less_than_or_equal_verifier.verify((1, 2), (2, 1), expected_result=True) - less_than_or_equal_verifier.verify((1, 1), (1, 1, 1), expected_result=True) - less_than_or_equal_verifier.verify((2, 1), (1, 2), expected_result=False) - less_than_or_equal_verifier.verify((1, 2), (1, 1), expected_result=False) - less_than_or_equal_verifier.verify((1, 1, 1), (1, 1), expected_result=False) - - greater_than_or_equal_verifier.verify((1, 1), (1, 1), expected_result=True) - greater_than_or_equal_verifier.verify((1, 1), (1, 2), expected_result=False) - greater_than_or_equal_verifier.verify((1, 2), (2, 1), expected_result=False) - greater_than_or_equal_verifier.verify((1, 1), (1, 1, 1), expected_result=False) - greater_than_or_equal_verifier.verify((2, 1), (1, 2), expected_result=True) - greater_than_or_equal_verifier.verify((1, 2), (1, 1), expected_result=True) - greater_than_or_equal_verifier.verify((1, 1, 1), (1, 1), expected_result=True) diff --git a/jpyinterpreter/tests/test_version_check.py b/jpyinterpreter/tests/test_version_check.py deleted file mode 100644 index d06ac459..00000000 --- a/jpyinterpreter/tests/test_version_check.py +++ /dev/null @@ -1,16 +0,0 @@ -from jpyinterpreter import is_python_version_supported - - -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 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 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/tests/test_with.py b/jpyinterpreter/tests/test_with.py deleted file mode 100644 index 249f4404..00000000 --- a/jpyinterpreter/tests/test_with.py +++ /dev/null @@ -1,70 +0,0 @@ -from .conftest import verifier_for - - -def test_with(): - class ContextManager: - def __enter__(self): - return 'Context' - - def __exit__(self, exc_type, exc_val, exc_tb): - return None - - def my_function(): - with ContextManager() as ctx: - return ctx - - function_verifier = verifier_for(my_function) - - function_verifier.verify(expected_result='Context') - - -def test_with_exception_unhandled(): - class ContextManager: - def __enter__(self): - return 'Context' - - def __exit__(self, exc_type, exc_val, exc_tb): - return None - - def my_function(): - with ContextManager() as ctx: - raise TypeError - - function_verifier = verifier_for(my_function) - - function_verifier.verify(expected_error=TypeError) - - -def test_with_exception_unhandled_with_result(): - class ContextManager: - def __enter__(self): - return 'Context' - - def __exit__(self, exc_type, exc_val, exc_tb): - return False - - def my_function(): - with ContextManager() as ctx: - raise TypeError - - function_verifier = verifier_for(my_function) - - function_verifier.verify(expected_error=TypeError) - - -def test_with_exception_handled(): - class ContextManager: - def __enter__(self): - return 'Context' - - def __exit__(self, exc_type, exc_val, exc_tb): - return True - - def my_function(): - with ContextManager() as ctx: - raise TypeError - return 10 - - function_verifier = verifier_for(my_function) - - function_verifier.verify(expected_result=10) diff --git a/jpyinterpreter/tox.ini b/jpyinterpreter/tox.ini deleted file mode 100644 index 46a1cba4..00000000 --- a/jpyinterpreter/tox.ini +++ /dev/null @@ -1,22 +0,0 @@ -# tox (https://tox.readthedocs.io/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. - -[tox] -env_list = py310,py311,p312 - -[testenv] -pass_env = * # needed by tox4, to pass JAVA_HOME -deps = - pytest>=8.1.1 - pytest-cov>=4.1.0 - coverage>=7.4.3 - JPype1>=1.5.0 -commands = - pytest --import-mode=importlib {posargs} - -[coverage:paths] -source = - src/main/python - **/jpyinterpreter diff --git a/jreleaser.yml b/jreleaser.yml deleted file mode 100644 index 366a7630..00000000 --- a/jreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -project: - name: timefold-solver-python - -signing: - active: ALWAYS - armored: true - -release: - github: - commitAuthor: - name: "Timefold Release Bot" - email: "release@timefold.ai" - releaseName: "Timefold Solver Community Edition for Python {{projectVersion}}" - draft: true - overwrite: false - sign: true - milestone: - close: true - name: "v{{projectVersion}}" - changelog: - formatted: ALWAYS - preset: "conventional-commits" - contentTemplate: ".github/workflows/release-changelog-template.md" - contributors: - format: "- {{contributorName}}{{#contributorUsernameAsLink}} ({{.}}){{/contributorUsernameAsLink}}" - hide: - uncategorized: true - categories: - - build - - ci - contributors: - - "Timefold Release Bot" - -files: - globs: - - pattern: "dist/**/*.whl" - - pattern: "dist/**/*.tar.gz" \ No newline at end of file diff --git a/mvnw b/mvnw deleted file mode 100755 index 633bbb74..00000000 --- a/mvnw +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -(CYGWIN*|MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ] ; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v java)" || : - JAVACCMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v javac)" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ] ; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - h=$(( ( h * 31 + $(LC_CTYPE=C printf %d "'$str") ) % 4294967296 )) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl="${value-}" ;; - distributionSha256Sum) distributionSha256Sum="${value-}" ;; - esac -done < "${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - - -case "${distributionUrl##*/}" in -(maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - (*AMD64:CYGWIN*|*AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - (:Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - (:Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - (:Linux*x86_64*) distributionPlatform=linux-amd64 ;; - (*) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -(maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -(*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -(*?-bin.zip|*?maven-mvnd-?*-?*.zip) ;; -(*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget > /dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl > /dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat > "$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( new java.net.URL( args[0] ).openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum > /dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c > /dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c > /dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip > /dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" -fi -printf %s\\n "$distributionUrl" > "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index dd02e16f..00000000 --- a/mvnw.cmd +++ /dev/null @@ -1,145 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml deleted file mode 100644 index e4ea042e..00000000 --- a/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - ai.timefold.solver - timefold-solver-build-parent - 999-SNAPSHOT - - - 4.0.0 - timefold-solver-python-parent - pom - - - UTF-8 - 4.4 - 17 - ${project.groupId}:${project.artifactId} - src/main/java,src/main/python - src/test/java,tests - 3.10,3.11,3.12 - target/coverage.xml,jpyinterpreter/target/coverage.xml - - - - - - ai.timefold.solver - jpyinterpreter - ${project.version} - - - org.apache.commons - commons-collections4 - ${version.apache.collections4} - - - - - - jpyinterpreter - timefold-solver-python-core - - - diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 66a5388f..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = [ - "setuptools>=69.1.1", - "stubgenj>=0.2.5", - "JPype1>=1.5.0", - "wheel" -] -build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py deleted file mode 100644 index e9087d73..00000000 --- a/setup.py +++ /dev/null @@ -1,154 +0,0 @@ -from setuptools import setup -from setuptools.command.build_py import build_py -import glob -import os -import platform -import subprocess -from pathlib import Path -from shutil import copyfile -import sys - -include_java_stubs = os.environ.get('INCLUDE_JAVA_STUBS', 'false').lower() == 'true' -""" -If Java type stubs should be generated. -These are for our own type checker; users should never need -to use them (since they should be using the Python classes). - -Not included in the default build since IDE's will see multiple -packages that define a class (the one from `ai.timefold...` -and the one from `timefold.solver...`), which can lead to the -wrong one being imported. -""" - - -class FetchDependencies(build_py): - """ - A command class that fetch Java Dependencies and - add them as files within a python package - """ - def create_stubs(self, project_root, command): - working_directory = project_root / 'timefold-solver-python-core' - subprocess.run([str((project_root / command).absolute()), 'dependency:copy-dependencies'], - cwd=working_directory, check=True) - subprocess.run([str((project_root / command).absolute()), 'dependency:copy-dependencies', - '-Dclassifier=javadoc'], cwd=working_directory, check=True) - subprocess.run([sys.executable, str((project_root / 'create-stubs.py').absolute())], - cwd=working_directory, check=True) - target_dir = self.build_lib - for file_name in find_stub_files(str(working_directory / 'java-stubs')): - path = file_name[len(str(working_directory)) + 1:] - os.makedirs(os.path.dirname(os.path.join(target_dir, path)), exist_ok=True) - copyfile(os.path.join(str(working_directory), path), os.path.join(target_dir, path)) - for file_name in find_stub_files(str(working_directory / 'jpype-stubs')): - path = file_name[len(str(working_directory)) + 1:] - os.makedirs(os.path.dirname(os.path.join(target_dir, path)), exist_ok=True) - copyfile(os.path.join(str(working_directory), path), os.path.join(target_dir, path)) - for file_name in find_stub_files(str(working_directory / 'ai-stubs')): - path = file_name[len(str(working_directory)) + 1:] - os.makedirs(os.path.dirname(os.path.join(target_dir, path)), exist_ok=True) - copyfile(os.path.join(str(working_directory), path), os.path.join(target_dir, path)) - - def run(self): - if not self.dry_run: - project_root = Path(__file__).parent - # Do a mvn clean install - # which is configured to add dependency jars to 'target/dependency' - command = 'mvnw' - if platform.system() == 'Windows': - command = 'mvnw.cmd' - - subprocess.run([str((project_root / command).absolute()), 'clean', 'install'], - cwd=project_root, check=True) - - if include_java_stubs: - self.create_stubs(project_root, command) - - classpath_jars = [] - # Add the main artifact - classpath_jars.extend(glob.glob(os.path.join(project_root, 'timefold-solver-python-core', 'target', '*.jar'))) - # Add the main artifact's dependencies - classpath_jars.extend(glob.glob(os.path.join(project_root, 'timefold-solver-python-core', 'target', 'dependency', '*.jar'))) - - self.mkpath(os.path.join(self.build_lib, 'timefold', 'solver', 'jars')) - - # Copy classpath jars to timefold.solver.jars - for file in classpath_jars: - copyfile(file, os.path.join(self.build_lib, 'timefold', 'solver', 'jars', os.path.basename(file))) - - # Make timefold.solver.jars a Python module - fp = open(os.path.join(self.build_lib, 'timefold', 'solver', 'jars', '__init__.py'), 'w') - fp.close() - build_py.run(self) - - -def find_stub_files(stub_root: str): - for root, dirs, files in os.walk(stub_root): - for file in files: - if file.endswith(".pyi"): - yield os.path.join(root, file) - - -packages = [ - 'timefold.solver', - 'timefold.solver.config', - 'timefold.solver.domain', - 'timefold.solver.heuristic', - 'timefold.solver.score', - 'timefold.solver.test', - '_jpyinterpreter', - ] - -package_dir = { - 'timefold.solver': 'timefold-solver-python-core/src/main/python', - '_jpyinterpreter': 'jpyinterpreter/src/main/python', -} - -if include_java_stubs: - packages += [ - 'java-stubs', - 'jpype-stubs', - 'ai-stubs', - ] - package_dir.update({ - 'java-stubs': 'timefold-solver-python-core/src/main/resources', - 'jpype-stubs': 'timefold-solver-python-core/src/main/resources', - 'ai-stubs': 'timefold-solver-python-core/src/main/resources', - }) - -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() -timefold_solver_python_version = '999-dev0' - -setup( - name='timefold', - version=timefold_solver_python_version, - license='Apache License Version 2.0', - license_file='LICENSE.txt', - description='An AI constraint solver that optimizes planning and scheduling problems', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/TimefoldAI/timefold-solver-python', - project_urls={ - 'Timefold Solver Documentation': 'https://timefold.ai/docs/timefold-solver/latest', - 'Timefold Homepage': 'https://timefold.ai', - }, - classifiers=[ - 'Development Status :: 4 - Beta', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - 'Topic :: Software Development :: Libraries :: Java Libraries', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent' - ], - packages=packages, - package_dir=package_dir, - test_suite='tests', - python_requires='>=3.10', - install_requires=[ - 'JPype1>=1.5.0' - ], - cmdclass={'build_py': FetchDependencies}, - package_data={ - 'timefold.solver.jars': ['*.jar'], - }, -) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 2038d6e1..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,30 +0,0 @@ -import timefold.solver - - -def pytest_addoption(parser): - """ - Allows adding command line options to pytest - """ - parser.addoption('--jacoco-agent', action='store', default='') - parser.addoption('--jacoco-output', action='store', default='target/jacoco.exec') - parser.addoption('--output-generated-classes', action='store', default='false') - - -def pytest_sessionstart(session): - """ - Called after the Session object has been created and - before performing collection and entering the run test loop. - """ - import pathlib - import sys - - jacoco_agent = session.config.getoption('--jacoco-agent') - if jacoco_agent != '': - jacoco_output = session.config.getoption('--jacoco-output') - timefold.solver.init(f'-javaagent:{jacoco_agent}=destfile={jacoco_output}') - else: - timefold.solver.init() - - if session.config.getoption('--output-generated-classes') != 'false': - timefold.solver.set_class_output_directory(pathlib.Path('target', 'tox-generated-classes', 'python', - f'{sys.version_info[0]}.{sys.version_info[1]}')) diff --git a/tests/solverConfig-redefined.xml b/tests/solverConfig-redefined.xml deleted file mode 100644 index c395f1e0..00000000 --- a/tests/solverConfig-redefined.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - tests.test_solver_config.test_reload_from_solver_config_file.<locals>.RedefinedSolution - \ No newline at end of file diff --git a/tests/solverConfig-simple.xml b/tests/solverConfig-simple.xml deleted file mode 100644 index 8903327f..00000000 --- a/tests/solverConfig-simple.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - tests.test_solver_config.Solution - tests.test_solver_config.Entity - - - tests.test_solver_config.my_constraints - - - - 0hard/0soft - - \ No newline at end of file diff --git a/tests/test_anchors.py b/tests/test_anchors.py deleted file mode 100644 index 671ea54b..00000000 --- a/tests/test_anchors.py +++ /dev/null @@ -1,111 +0,0 @@ -# import timefold.solver -# import timefold.solver.score -# import timefold.solver.config -# import timefold.solver.constraint -# from timefold.solver.types import PlanningVariableGraphType -# -# -# @timefold.solver.problem_fact -# class ChainedObject: -# pass -# -# -# @timefold.solver.problem_fact -# class ChainedAnchor(ChainedObject): -# def __init__(self, code): -# self.code = code -# -# -# @timefold.solver.planning_entity -# class ChainedEntity(ChainedObject): -# def __init__(self, code, value=None, anchor=None): -# self.code = code -# self.value = value -# self.anchor = anchor -# -# @timefold.solver.planning_variable(ChainedObject, value_range_provider_refs=['chained_anchor_range', -# 'chained_entity_range'], -# graph_type=PlanningVariableGraphType.CHAINED) -# def get_value(self): -# return self.value -# -# def set_value(self, value): -# self.value = value -# -# @timefold.solver.anchor_shadow_variable(ChainedAnchor, source_variable_name='value') -# def get_anchor(self): -# return self.anchor -# -# def set_anchor(self, anchor): -# self.anchor = anchor -# -# def __str__(self): -# return f'ChainedEntity(code={self.code}, value={self.value}, anchor={self.anchor})' -# -# -# @timefold.solver.planning_solution -# class ChainedSolution: -# def __init__(self, anchors, entities, score=None): -# self.anchors = anchors -# self.entities = entities -# self.score = score -# -# @timefold.solver.problem_fact_collection_property(ChainedAnchor) -# @timefold.solver.value_range_provider('chained_anchor_range') -# def get_anchors(self): -# return self.anchors -# -# @timefold.solver.planning_entity_collection_property(ChainedEntity) -# @timefold.solver.value_range_provider('chained_entity_range') -# def get_entities(self): -# return self.entities -# -# @timefold.solver.planning_score(timefold.solver.score.SimpleScore) -# def get_score(self): -# return self.score -# -# def set_score(self, score): -# self.score = score -# -# -# @timefold.solver.constraint_provider -# def chained_constraints(constraint_factory: timefold.solver.constraint.ConstraintFactory): -# return [ -# constraint_factory.for_each(ChainedEntity) -# .group_by(lambda entity: entity.anchor, timefold.solver.constraint.ConstraintCollectors.count()) -# .reward('Maximize chain length', timefold.solver.score.SimpleScore.ONE, -# lambda anchor, count: count * count) -# ] -# -# -# def test_chained(): -# termination = timefold.solver.config.solver.termination.TerminationConfig() -# termination.setBestScoreLimit('9') -# solver_config = timefold.solver.config.solver.SolverConfig() \ -# .withSolutionClass(ChainedSolution) \ -# .withEntityClasses(ChainedEntity) \ -# .withConstraintProviderClass(chained_constraints) \ -# .withTerminationConfig(termination) -# solver = timefold.solver.solver_factory_create(solver_config).buildSolver() -# solution = solver.solve(ChainedSolution( -# [ -# ChainedAnchor('A'), -# ChainedAnchor('B'), -# ChainedAnchor('C') -# ], -# [ -# ChainedEntity('1'), -# ChainedEntity('2'), -# ChainedEntity('3'), -# ] -# )) -# assert solution.score.score == 9 -# anchor = solution.entities[0].anchor -# assert anchor is not None -# anchor_value_count = 0 -# for entity in solution.entities: -# if entity.value == anchor: -# anchor_value_count += 1 -# assert anchor_value_count == 1 -# for entity in solution.entities: -# assert entity.anchor == anchor diff --git a/tests/test_collectors.py b/tests/test_collectors.py deleted file mode 100644 index 7075e4ce..00000000 --- a/tests/test_collectors.py +++ /dev/null @@ -1,601 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.score import * -from timefold.solver.config import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -@dataclass -class Value: - number: int - - -@planning_entity -@dataclass -class Entity: - code: str - value: Annotated[Value, PlanningVariable] = field(default=None) - - -@planning_solution -@dataclass -class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, - PlanningScore] = field(default=None) - - -def create_score_manager(constraint_provider) -> SolutionManager: - return SolutionManager.create(SolverFactory.create( - SolverConfig(solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=constraint_provider - )))) - - -def test_min(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.min(lambda entity: entity.value.number)) - .reward(SimpleScore.ONE, lambda min_value: min_value) - .as_constraint('Min value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_max(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.max(lambda entity: entity.value.number)) - .reward(SimpleScore.ONE, lambda max_value: max_value) - .as_constraint('Max value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_sum(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.sum(lambda entity: entity.value.number)) - .reward(SimpleScore.ONE, lambda sum_value: sum_value) - .as_constraint('Sum value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(3) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(4) - - -def test_average(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.average(lambda entity: entity.value.number)) - .reward(SimpleScore.ONE, lambda average_value: int(10 * average_value)) - .as_constraint('Average value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(10) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(15) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(20) - - -def test_count(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda entity: entity.code[0] == 'A') - .group_by(ConstraintCollectors.count()) - .reward(SimpleScore.ONE, lambda count: count) - .as_constraint('Count value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a1: Entity = Entity('A1') - entity_a2: Entity = Entity('A2') - entity_b: Entity = Entity('B1') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a1, entity_a2, entity_b], [value_1, value_2]) - entity_a1.value = value_1 - entity_a2.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_count_distinct(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.count_distinct(lambda entity: entity.value)) - .reward(SimpleScore.ONE, lambda count: count) - .as_constraint('Count distinct value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - -def test_to_consecutive_sequences(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.to_consecutive_sequences( - lambda entity: entity.value.number)) - .flatten_last(lambda sequences: sequences.getConsecutiveSequences()) - .reward(SimpleScore.ONE, lambda sequence: sequence.getCount() ** 2) - .as_constraint('squared sequence length') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - entity_c: Entity = Entity('C') - entity_d: Entity = Entity('D') - entity_e: Entity = Entity('E') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - value_4 = Value(4) - value_5 = Value(5) - value_6 = Value(6) - value_7 = Value(7) - value_8 = Value(8) - value_9 = Value(9) - - problem = Solution([entity_a, entity_b, entity_c, entity_d, entity_e], - [value_1, value_2, value_3, value_4, value_5, - value_6, value_7, value_8, value_9]) - - entity_a.value = value_1 - entity_b.value = value_3 - entity_c.value = value_5 - entity_d.value = value_7 - entity_e.value = value_9 - - assert score_manager.explain(problem).score.score == 5 - - entity_a.value = value_1 - entity_b.value = value_2 - entity_c.value = value_3 - entity_d.value = value_4 - entity_e.value = value_5 - - assert score_manager.explain(problem).score.score == 25 - - entity_a.value = value_1 - entity_b.value = value_2 - entity_c.value = value_3 - entity_d.value = value_5 - entity_e.value = value_6 - - assert score_manager.explain(problem).score.score == 13 - - -def test_to_list(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.to_list(lambda entity: entity.value)) - .reward(SimpleScore.ONE, lambda values: len(values)) - .as_constraint('list size') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_to_set(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.to_set(lambda entity: entity.value)) - .reward(SimpleScore.ONE, lambda values: len(values)) - .as_constraint('set size') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - -def test_to_map(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.to_map(lambda entity: entity.code, lambda entity: entity.value.number)) - .filter(lambda entity_map: next(iter(entity_map['A'])) == 1) - .reward(SimpleScore.ONE, lambda entity_map: next(iter(entity_map['B']))) - .as_constraint('map at B') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(0) - - -def test_to_sorted_set(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.to_sorted_set(lambda entity: entity.value.number)) - .reward(SimpleScore.ONE, lambda values: next(iter(values))) - .as_constraint('min') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_to_sorted_map(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by( - ConstraintCollectors.to_sorted_map(lambda entity: entity.code, lambda entity: entity.value.number)) - .filter(lambda entity_map: next(iter(entity_map['B'])) == 1) - .reward(SimpleScore.ONE, lambda entity_map: next(iter(entity_map['A']))) - .as_constraint('map at A') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(1) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(0) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(0) - - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_conditionally(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.conditionally(lambda entity: entity.code[0] == 'A', - ConstraintCollectors.count())) - .reward(SimpleScore.ONE, lambda count: count) - .as_constraint('Conditionally count value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a1: Entity = Entity('A1') - entity_a2: Entity = Entity('A2') - entity_b: Entity = Entity('B1') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a1, entity_a2, entity_b], [value_1, value_2]) - entity_a1.value = value_1 - entity_a2.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - -def test_compose(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.compose( - ConstraintCollectors.min(lambda entity: entity.value.number), - ConstraintCollectors.max(lambda entity: entity.value.number), - lambda a, b: (a, b) - )) - .reward(SimpleScore.ONE, lambda min_max: min_max[0] + min_max[1] * 10) - .as_constraint('Max value') - # min is in lower digit; max in upper digit - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(11) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(21) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(22) - - -def test_collect_and_then(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.collect_and_then( - ConstraintCollectors.min(lambda entity: entity.value.number), - lambda a: 2 * a - )) - .reward(SimpleScore.ONE, lambda twice_min: twice_min) - .as_constraint('Double min value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - entity_a.value = value_1 - entity_b.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_a.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(2) - - entity_b.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(4) - - -def test_load_balance(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .group_by(ConstraintCollectors.load_balance( - lambda entity: entity.value - )) - .reward(SimpleScore.ONE, - lambda balance: round(balance.unfairness() * 1000)) - .as_constraint('Balanced value') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - entity_c: Entity = Entity('C') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1]) - entity_a.value = value_1 - entity_b.value = value_1 - entity_c.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(0) - - problem = Solution([entity_a, entity_b, entity_c], [value_1, value_2]) - - assert score_manager.explain(problem).score == SimpleScore.of(0) - - entity_c.value = value_2 - - assert score_manager.explain(problem).score == SimpleScore.of(707) diff --git a/tests/test_constraint_streams.py b/tests/test_constraint_streams.py deleted file mode 100644 index 056fc91b..00000000 --- a/tests/test_constraint_streams.py +++ /dev/null @@ -1,1113 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.score import * -from timefold.solver.config import * - -import inspect -import re -from dataclasses import dataclass, field -from decimal import Decimal -from typing import Annotated, List -from ai.timefold.solver.core.api.score.stream import Joiners as JavaJoiners, \ - ConstraintCollectors as JavaConstraintCollectors, ConstraintFactory as JavaConstraintFactory -from ai.timefold.solver.core.api.score.stream.uni import (UniConstraintStream as JavaUniConstraintStream, - UniConstraintBuilder as JavaUniConstraintBuilder) -from ai.timefold.solver.core.api.score.stream.bi import (BiConstraintStream as JavaBiConstraintStream, - BiConstraintBuilder as JavaBiConstraintBuilder) -from ai.timefold.solver.core.api.score.stream.tri import (TriConstraintStream as JavaTriConstraintStream, - TriConstraintBuilder as JavaTriConstraintBuilder) -from ai.timefold.solver.core.api.score.stream.quad import (QuadConstraintStream as JavaQuadConstraintStream, - QuadConstraintBuilder as JavaQuadConstraintBuilder) - - -@dataclass -class Value: - def __init__(self, number): - self.number = number - - -@planning_entity -@dataclass -class Entity: - code: str - value: Annotated[Value, PlanningVariable] = field(default=None) - - -@planning_solution -@dataclass -class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - -@planning_solution -@dataclass -class DecimalSolution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleDecimalScore, PlanningScore] = field(default=None) - - -def create_score_manager(constraint_provider, solution_class: type = Solution, entity_classes: list[type] = (Entity,)): - return SolutionManager.create(SolverFactory.create( - SolverConfig(solution_class=solution_class, - entity_class_list=entity_classes, - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=constraint_provider - )))) - - -def test_for_each(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - - problem = Solution([entity_a, entity_b], [value_1]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_1 - - assert score_manager.explain(problem).score.score == 2 - - -def test_filter_uni(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda e: e.value.number == 1) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_2 - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_1 - assert score_manager.explain(problem).score.score == 2 - - -def test_filter_bi(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Entity) - .filter(lambda e1, e2: e1.value.number == 1 and e2.value.number == 2) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - entity_b.value = value_1 - assert score_manager.explain(problem).score.score == 0 - - entity_b.value = value_2 - assert score_manager.explain(problem).score.score == 1 - - -def test_filter_tri(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Entity) - .join(Entity) - .filter(lambda e1, e2, e3: e1.value.number == 1 and e2.value.number == 2 and e3.value.number == 3) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - entity_c: Entity = Entity('C') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - - problem = Solution([entity_a, entity_b, entity_c], [value_1, value_2, value_3]) - - assert score_manager.explain(problem).score.score == 0 - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - entity_b.value = value_2 - assert score_manager.explain(problem).score.score == 0 - - entity_c.value = value_1 - assert score_manager.explain(problem).score.score == 0 - - entity_c.value = value_3 - assert score_manager.explain(problem).score.score == 1 - - -def test_filter_quad(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Entity) - .join(Entity) - .join(Entity) - .filter(lambda e1, e2, e3, e4: e1.value.number == 1 and e2.value.number == 2 and e3.value.number == 3 - and e4.value.number == 4) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - entity_c: Entity = Entity('C') - entity_d: Entity = Entity('D') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - value_4 = Value(4) - - problem = Solution([entity_a, entity_b, entity_c, entity_d], [value_1, value_2, value_3, value_4]) - - assert score_manager.explain(problem).score.score == 0 - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - entity_b.value = value_2 - assert score_manager.explain(problem).score.score == 0 - - entity_c.value = value_3 - assert score_manager.explain(problem).score.score == 0 - - entity_d.value = value_1 - assert score_manager.explain(problem).score.score == 0 - - entity_d.value = value_4 - assert score_manager.explain(problem).score.score == 1 - - -def test_flatten_last(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .map(lambda entity: (1, 2, 3)) - .flatten_last(lambda the_tuple: the_tuple) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - - entity_a: Entity = Entity('A') - - value_1 = Value(1) - - problem = Solution([entity_a], [value_1]) - entity_a.value = value_1 - - assert score_manager.explain(problem).score == SimpleScore.of(3) - - -def test_join_uni(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Entity, Joiners.equal(lambda entity: entity.code)) - .filter(lambda e1, e2: e1 is not e2) - .reward(SimpleScore.ONE, lambda e1, e2: e1.value.number * e2.value.number) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a1: Entity = Entity('A') - entity_a2: Entity = Entity('A') - entity_b1: Entity = Entity('B') - entity_b2: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a1, entity_a2, entity_b1, entity_b2], [value_1, value_2]) - - entity_a1.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - entity_a1.value = value_1 - entity_a2.value = value_1 - - entity_b1.value = value_2 - entity_b2.value = value_2 - - # 1 * 1 + 1 * 1 + 2 * 2 + 2 * 2 - assert score_manager.explain(problem).score.score == 10 - - entity_a1.value = value_2 - entity_b1.value = value_1 - - # 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 - assert score_manager.explain(problem).score.score == 8 - - -def test_if_exists_uni(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .if_exists(Entity, Joiners.equal(lambda entity: entity.code)) - .reward(SimpleScore.ONE, lambda e1: e1.value.number) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a1: Entity = Entity('A') - entity_a2: Entity = Entity('A') - entity_b1: Entity = Entity('B') - entity_b2: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a1, entity_a2, entity_b1, entity_b2], [value_1, value_2]) - - entity_a1.value = value_1 - - # With itself - assert score_manager.explain(problem).score.score == 1 - - entity_a1.value = value_1 - entity_a2.value = value_1 - - entity_b1.value = value_2 - entity_b2.value = value_2 - - # 1 + 2 + 1 + 2 - assert score_manager.explain(problem).score.score == 6 - - entity_a1.value = value_2 - entity_b1.value = value_1 - - # 1 + 2 + 1 + 2 - assert score_manager.explain(problem).score.score == 6 - - -def test_if_not_exists_uni(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .if_not_exists(Entity, Joiners.equal(lambda entity: entity.code)) - .reward(SimpleScore.ONE, lambda e1: e1.value.number) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a1: Entity = Entity('A') - entity_a2: Entity = Entity('A') - entity_b1: Entity = Entity('B') - entity_b2: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a1, entity_a2, entity_b1, entity_b2], [value_1, value_2]) - - entity_a1.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - entity_a1.value = value_1 - entity_a2.value = value_1 - - entity_b1.value = value_2 - entity_b2.value = value_2 - - assert score_manager.explain(problem).score.score == 0 - - entity_a1.value = value_2 - entity_b1.value = value_1 - - assert score_manager.explain(problem).score.score == 0 - - -def test_map(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .map(lambda e: e.value.number) - .reward(SimpleScore.ONE, lambda v: v) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_1 - - assert score_manager.explain(problem).score.score == 2 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 3 - - -def test_multi_map(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .map(lambda e: e.code, lambda e: e.value.number) - .reward(SimpleScore.ONE, lambda c, v: len(c) + v) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('BB') - - value_1 = Value(10) - value_2 = Value(20) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 11 - - entity_b.value = value_1 - - assert score_manager.explain(problem).score.score == 23 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 33 - - -def test_expand(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .expand(lambda e: e.value.number) - .reward(SimpleScore.ONE, lambda e, v: v) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_1 - - assert score_manager.explain(problem).score.score == 2 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 3 - - -def test_multi_expand(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .expand(lambda e: e.code, lambda e: e.value.number) - .reward(SimpleScore.ONE, lambda e, c, v: len(c) + v) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('BB') - - value_1 = Value(10) - value_2 = Value(20) - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 11 - - entity_b.value = value_1 - - assert score_manager.explain(problem).score.score == 23 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 33 - - -def test_concat(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda e: e.value.number == 1) - .concat(constraint_factory.for_each(Entity).filter(lambda e: e.value.number == 2)) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - - problem = Solution([entity_a, entity_b], [value_1, value_2, value_3]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 2 - - entity_b.value = value_3 - - assert score_manager.explain(problem).score.score == 1 - -def test_complement(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda e: e.value.number == 1) - .complement(Entity) - .reward(SimpleScore.ONE) - .as_constraint('Count') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - - problem = Solution([entity_a, entity_b], [value_1, value_2, value_3]) - - assert score_manager.explain(problem).score.score == 0 - - entity_a.value = value_1 - - assert score_manager.explain(problem).score.score == 1 - - entity_b.value = value_2 - - assert score_manager.explain(problem).score.score == 2 - - entity_b.value = value_3 - - assert score_manager.explain(problem).score.score == 2 - - -def test_custom_indictments(): - @dataclass(unsafe_hash=True) - class MyIndictment: - code: str - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda e: e.value.number) - .indict_with(lambda e: [MyIndictment(e.code), e.value.number]) - .as_constraint('my_package', 'Maximize value') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - - entity_a.value = value_1 - entity_b.value = value_1 - - problem = Solution([entity_a, entity_b], [value_1, value_2, value_3]) - - indictments = score_manager.explain(problem).indictment_map - a_indictment = indictments[MyIndictment('A')] - b_indictment = indictments[MyIndictment('B')] - value_indictment = indictments[1] - - assert a_indictment.indicted_object == MyIndictment('A') - assert a_indictment.score.score == 1 - assert a_indictment.constraint_match_count == 1 - assert a_indictment.constraint_match_set == { - ConstraintMatch(constraint_ref=ConstraintRef(package_name='my_package', constraint_name='Maximize value'), - justification=DefaultConstraintJustification( - facts=(entity_a,), - impact=a_indictment.score - ), - indicted_objects=(MyIndictment('A'), 1), - score=a_indictment.score) - } - - assert b_indictment.indicted_object == MyIndictment('B') - assert b_indictment.score.score == 1 - assert b_indictment.constraint_match_count == 1 - assert b_indictment.constraint_match_set == { - ConstraintMatch(constraint_ref=ConstraintRef(package_name='my_package', constraint_name='Maximize value'), - justification=DefaultConstraintJustification( - facts=(entity_b,), - impact=b_indictment.score - ), - indicted_objects=(MyIndictment('B'), 1), - score=b_indictment.score) - } - - assert value_indictment.indicted_object == 1 - assert value_indictment.score.score == 2 - assert value_indictment.constraint_match_count == 2 - assert value_indictment.constraint_match_set == { - ConstraintMatch(constraint_ref=ConstraintRef(package_name='my_package', constraint_name='Maximize value'), - justification=DefaultConstraintJustification( - facts=(entity_a,), - impact=a_indictment.score - ), - indicted_objects=(MyIndictment('A'), 1), - score=a_indictment.score), - ConstraintMatch(constraint_ref=ConstraintRef(package_name='my_package', constraint_name='Maximize value'), - justification=DefaultConstraintJustification( - facts=(entity_b,), - impact=b_indictment.score - ), - indicted_objects=(MyIndictment('B'), 1), - score=b_indictment.score) - } - - -def test_custom_justifications(): - @dataclass(unsafe_hash=True) - class MyJustification(ConstraintJustification): - code: str - score: SimpleScore - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda e: e.value.number) - .justify_with(lambda e, score: MyJustification(e.code, score)) - .as_constraint('my_package', 'Maximize value') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - value_2 = Value(2) - value_3 = Value(3) - - entity_a.value = value_1 - entity_b.value = value_3 - - problem = Solution([entity_a, entity_b], [value_1, value_2, value_3]) - - justifications = score_manager.explain(problem).get_justification_list() - assert len(justifications) == 2 - assert MyJustification('A', SimpleScore.of(1)) in justifications - assert MyJustification('B', SimpleScore.of(3)) in justifications - - justifications = score_manager.explain(problem).get_justification_list(MyJustification) - assert len(justifications) == 2 - assert MyJustification('A', SimpleScore.of(1)) in justifications - assert MyJustification('B', SimpleScore.of(3)) in justifications - - justifications = score_manager.explain(problem).get_justification_list(DefaultConstraintJustification) - assert len(justifications) == 0 - - -def test_long_scores(): - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda e: e.value.number) - .as_constraint('Maximize value') - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - # Overflow an int - value_1 = Value(3_000_000_000) - value_2 = Value(6_000_000_000) - - entity_a.value = value_1 - entity_b.value = value_2 - - problem = Solution([entity_a, entity_b], [value_1, value_2]) - - assert score_manager.explain(problem).score == SimpleScore.of(9_000_000_000) - - -def test_sanity(): - int_impact_functions = [ - None, - lambda a: a.value.number, - lambda a, b: a.value.number, - lambda a, b, c: a.value.number, - lambda a, b, c, d: a.value.number, - ] - - i = 0 - - def build_stream(constraint_factory: ConstraintFactory, - method: str, - cardinality: int, - has_impact_function: bool) -> Constraint: - nonlocal i - i += 1 - - def expander(x): - return None - - expanders = [expander] * (cardinality - 1) - current = constraint_factory.for_each(Entity) - if expanders: - current = current.expand(*expanders) - - impact_method = getattr(current, method) - - if has_impact_function: - return (impact_method(SimpleScore.ONE, int_impact_functions[cardinality]) - .as_constraint(f'Constraint {i}')) - else: - return (impact_method(SimpleScore.ONE) - .as_constraint(f'Constraint {i}')) - - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - build_stream(constraint_factory, method, cardinality, - use_impact_function) - for method in ['penalize', 'reward', 'impact'] - for cardinality in [1, 2, 3, 4] - for use_impact_function in [True, False] - ] - - score_manager = create_score_manager(define_constraints) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - - entity_a.value = value_1 - entity_b.value = value_1 - - problem = Solution([entity_a, entity_b], [value_1]) - - # 3 positive method + 1 negative methods = 2 positive - # 4 cardinalities - # 1 impact + 1 non-impact = 2 - # 2 * 4 * 2 = 16 - assert score_manager.explain(problem).score == SimpleScore.of(16) - - -def test_sanity_decimal(): - decimal_impact_functions = [ - None, - lambda a: a.value.number, - lambda a, b: a.value.number, - lambda a, b, c: a.value.number, - lambda a, b, c, d: a.value.number, - ] - - i = 0 - - def build_stream(constraint_factory: ConstraintFactory, - method: str, - cardinality: int, - has_impact_function: bool) -> Constraint: - nonlocal i - i += 1 - - def expander(x): - return None - - expanders = [expander] * (cardinality - 1) - current = constraint_factory.for_each(Entity) - if expanders: - current = current.expand(*expanders) - - impact_method = getattr(current, method) - - if has_impact_function: - return (impact_method(SimpleDecimalScore.ONE, decimal_impact_functions[cardinality]) - .as_constraint(f'Constraint {i}')) - else: - return (impact_method(SimpleDecimalScore.ONE) - .as_constraint(f'Constraint {i}')) - - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - build_stream(constraint_factory, method, cardinality, - use_impact_function) - for method in ['penalize_decimal', 'reward_decimal', 'impact_decimal'] - for cardinality in [1, 2, 3, 4] - for use_impact_function in [True, False] - ] - - score_manager = create_score_manager(define_constraints, solution_class=DecimalSolution) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(Decimal(1)) - - entity_a.value = value_1 - entity_b.value = value_1 - - problem = DecimalSolution([entity_a, entity_b], [value_1]) - - # 3 positive method + 1 negative methods = 2 positive - # 4 cardinalities - # 1 impact + 1 non-impact = 2 - # 2 * 4 * 2 = 16 - assert score_manager.explain(problem).score == SimpleDecimalScore.of(Decimal(16)) - - -def test_sanity_configurable(): - class ConstraintConfiguration: - pass - - for i in range(3 * 4 * 2): - weight_name = f'w{i + 1}' - weight_annotation = Annotated[SimpleScore, ConstraintWeight(f'Constraint {i + 1}', - constraint_package='pkg')] - weight_value = field(default=SimpleScore.ONE) - setattr(ConstraintConfiguration, weight_name, weight_value) - ConstraintConfiguration.__annotations__[weight_name] = weight_annotation - - ConstraintConfiguration = constraint_configuration(dataclass(ConstraintConfiguration)) - - @planning_solution - @dataclass - class ConfigurationSolution: - configuration: Annotated[ConstraintConfiguration, ConstraintConfigurationProvider] - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - - int_impact_functions = [ - None, - lambda a: a.value.number, - lambda a, b: a.value.number, - lambda a, b, c: a.value.number, - lambda a, b, c, d: a.value.number, - ] - - i = 0 - - def build_stream(constraint_factory: ConstraintFactory, - method: str, - cardinality: int, - has_impact_function: bool) -> Constraint: - nonlocal i - i += 1 - - def expander(x): - return None - - expanders = [expander] * (cardinality - 1) - current = constraint_factory.for_each(Entity) - if expanders: - current = current.expand(*expanders) - - impact_method = getattr(current, method) - - if has_impact_function: - return (impact_method(int_impact_functions[cardinality]) - .as_constraint('pkg', f'Constraint {i}')) - else: - return (impact_method() - .as_constraint('pkg', f'Constraint {i}')) - - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - build_stream(constraint_factory, method, cardinality, - use_impact_function) - for method in ['penalize_configurable', 'reward_configurable', 'impact_configurable'] - for cardinality in [1, 2, 3, 4] - for use_impact_function in [True, False] - ] - - score_manager = create_score_manager(define_constraints, solution_class=ConfigurationSolution) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(1) - - entity_a.value = value_1 - entity_b.value = value_1 - - problem = ConfigurationSolution(ConstraintConfiguration(), [entity_a, entity_b], [value_1]) - - # 3 positive method + 1 negative methods = 2 positive - # 4 cardinalities - # 1 impact + 1 non-impact = 2 - # 2 * 4 * 2 = 16 - assert score_manager.explain(problem).score == SimpleScore.of(16) - - -def test_sanity_configurable_decimal(): - class ConstraintConfiguration: - pass - - for i in range(3 * 4 * 2): - weight_name = f'w{i + 1}' - weight_annotation = Annotated[SimpleDecimalScore, ConstraintWeight(f'Constraint {i + 1}', - constraint_package='pkg')] - weight_value = field(default=SimpleDecimalScore.ONE) - setattr(ConstraintConfiguration, weight_name, weight_value) - ConstraintConfiguration.__annotations__[weight_name] = weight_annotation - - ConstraintConfiguration = constraint_configuration(dataclass(ConstraintConfiguration)) - - @planning_solution - @dataclass - class ConfigurationSolution: - configuration: Annotated[ConstraintConfiguration, ConstraintConfigurationProvider] - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleDecimalScore, PlanningScore] = field(default=None) - - - decimal_impact_functions = [ - None, - lambda a: a.value.number, - lambda a, b: a.value.number, - lambda a, b, c: a.value.number, - lambda a, b, c, d: a.value.number, - ] - - i = 0 - - def build_stream(constraint_factory: ConstraintFactory, - method: str, - cardinality: int, - has_impact_function: bool) -> Constraint: - nonlocal i - i += 1 - - def expander(x): - return None - - expanders = [expander] * (cardinality - 1) - current = constraint_factory.for_each(Entity) - if expanders: - current = current.expand(*expanders) - - impact_method = getattr(current, method) - - if has_impact_function: - return (impact_method(decimal_impact_functions[cardinality]) - .as_constraint('pkg', f'Constraint {i}')) - else: - return (impact_method() - .as_constraint('pkg', f'Constraint {i}')) - - - @constraint_provider - def define_constraints(constraint_factory: ConstraintFactory): - return [ - build_stream(constraint_factory, method, cardinality, - use_impact_function) - for method in ['penalize_configurable_decimal', - 'reward_configurable_decimal', - 'impact_configurable_decimal'] - for cardinality in [1, 2, 3, 4] - for use_impact_function in [True, False] - ] - - score_manager = create_score_manager(define_constraints, solution_class=ConfigurationSolution) - entity_a: Entity = Entity('A') - entity_b: Entity = Entity('B') - - value_1 = Value(Decimal(1)) - - entity_a.value = value_1 - entity_b.value = value_1 - - problem = ConfigurationSolution(ConstraintConfiguration(), [entity_a, entity_b], [value_1]) - - # 3 positive method + 1 negative methods = 2 positive - # 4 cardinalities - # 1 impact + 1 non-impact = 2 - # 2 * 4 * 2 = 16 - assert score_manager.explain(problem).score == SimpleDecimalScore.of(Decimal(16)) - - -ignored_python_functions = { - '_call_comparison_java_joiner', - '__init__', - 'from_', # ignored since the camelcase version is from, which is a keyword in Python -} - -ignored_java_functions = { - 'equals', - 'getClass', - 'hashCode', - 'notify', - 'notifyAll', - 'toString', - 'wait', - 'countLongBi', # Python has no concept of Long (everything a BigInteger) - 'countLongQuad', - 'countLongTri', - 'impactBigDecimal', - 'impactConfigurableBigDecimal', - 'impactConfigurableLong', - 'impactLong', - 'penalizeBigDecimal', - 'penalizeConfigurableBigDecimal', - 'penalizeConfigurableLong', - 'penalizeLong', - 'rewardBigDecimal', - 'rewardConfigurableBigDecimal', - 'rewardConfigurableLong', - 'rewardLong', - '_handler', # JPype handler field should be ignored - # Unimplemented - 'toConnectedRanges', - 'toConnectedTemporalRanges', - # These methods are deprecated - 'from_', - 'fromUnfiltered', - 'fromUniquePair', - 'forEachIncludingNullVars', - 'ifExistsIncludingNullVars', - 'ifNotExistsIncludingNullVars', - 'ifExistsOtherIncludingNullVars', - 'ifNotExistsOtherIncludingNullVars', - 'toCollection', -} - - -def test_has_all_methods(): - missing = [] - for python_type, java_type in ((UniConstraintStream, JavaUniConstraintStream), - (BiConstraintStream, JavaBiConstraintStream), - (TriConstraintStream, JavaTriConstraintStream), - (QuadConstraintStream, JavaQuadConstraintStream), - (UniConstraintBuilder, JavaUniConstraintBuilder), - (BiConstraintBuilder, JavaBiConstraintBuilder), - (TriConstraintBuilder, JavaTriConstraintBuilder), - (QuadConstraintBuilder, JavaQuadConstraintBuilder), - (Joiners, JavaJoiners), - (ConstraintCollectors, JavaConstraintCollectors), - (ConstraintFactory, JavaConstraintFactory)): - for function_name, function_impl in inspect.getmembers(java_type, inspect.isfunction): - if function_name in ignored_java_functions: - continue - if python_type is ConstraintCollectors and function_name.endswith(('Long', 'BigInteger', 'Duration', - 'BigDecimal', 'Period')): - continue # Python only has a single integer type (= BigInteger) and does not support Java Durations - # or Period - - snake_case_name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', function_name) - # change h_t_t_p -> http - snake_case_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake_case_name).lower() - if not hasattr(python_type, snake_case_name): - missing.append((java_type, python_type, snake_case_name)) - - if missing: - assertion_msg = '' - for java_type, python_type, snake_case_name in missing: - assertion_msg += f'{python_type} is missing a method ({snake_case_name}) from java_type ({java_type}).)\n' - raise AssertionError(assertion_msg) diff --git a/tests/test_constraint_verifier.py b/tests/test_constraint_verifier.py deleted file mode 100644 index e0c300e9..00000000 --- a/tests/test_constraint_verifier.py +++ /dev/null @@ -1,415 +0,0 @@ -import pytest - -from timefold.solver.domain import * -from timefold.solver.score import * -from timefold.solver.config import * -from timefold.solver.test import * - -import inspect -import re -from typing import Annotated, List -from dataclasses import dataclass, field - -from ai.timefold.solver.test.api.score.stream import (ConstraintVerifier as JavaConstraintVerifier, - SingleConstraintAssertion as JavaSingleConstraintAssertion, - SingleConstraintVerification as JavaSingleConstraintVerification, - MultiConstraintAssertion as JavaMultiConstraintAssertion, - MultiConstraintVerification as JavaMultiConstraintVerification) - -def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one, - EntityValueIndictment, EntityValueJustification, EntityValuePairJustification, - solution, e1, e2, e3, v1, v2, v3): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(0) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .rewards() - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes() - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(1) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .indicts_with_exactly(EntityValueIndictment(e1, v1)) - - e1.value = v1 - e2.value = v1 - e3.value = v1 - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(1) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(1) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .indicts_with(EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, v1)) \ - .penalizes_by(1) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .indicts_with_exactly(EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, v1)) \ - .penalizes_by(1) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .indicts_with(EntityValueIndictment(e1, v1)) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .justifies_with(EntityValuePairJustification((e1, e2), v1, SimpleScore(-1))) \ - .penalizes_by(1) - - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .justifies_with_exactly(EntityValuePairJustification((e1, e2), v1, SimpleScore(-1))) \ - .penalizes_by(1) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .indicts_with_exactly(EntityValueIndictment(e1, v1)) - - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .justifies_with(EntityValuePairJustification((e1, e2), v1, SimpleScore(1))) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .justifies_with_exactly(EntityValuePairJustification((e1, e2), v1, SimpleScore(1))) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .rewards(1) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(0) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2) \ - .penalizes(2) - - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes(3) - - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes_more_than(2) - - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes_less_than(4) - - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes() - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes_more_than(3) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes_less_than(3) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .rewards(3) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes(2) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given(e1, e2, e3) \ - .penalizes(4) - - verifier.verify_that(same_value) \ - .given_solution(solution) \ - .penalizes(3) - - verifier.verify_that(same_value) \ - .given_solution(solution) \ - .penalizes() - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given_solution(solution) \ - .rewards(3) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given_solution(solution) \ - .penalizes(2) - - with pytest.raises(AssertionError): - verifier.verify_that(same_value) \ - .given_solution(solution) \ - .penalizes(4) - - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .rewards(3) - - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .rewards() - - with pytest.raises(AssertionError): - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .penalizes() - - with pytest.raises(AssertionError): - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .penalizes(3) - - with pytest.raises(AssertionError): - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .rewards(2) - - with pytest.raises(AssertionError): - verifier.verify_that(is_value_one) \ - .given(e1, e2, e3) \ - .rewards(4) - - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(0)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(1)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(-1)) - - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(0)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(1)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(-1)) - - e1.value = v1 - e2.value = v2 - e3.value = v3 - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(1)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(2)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given(e1, e2, e3) \ - .scores(SimpleScore.of(0)) - - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(1)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(2)) - - with pytest.raises(AssertionError): - verifier.verify_that() \ - .given_solution(solution) \ - .scores(SimpleScore.of(0)) - - -def test_constraint_verifier_create(): - @dataclass(unsafe_hash=True) - class Value: - code: str - - def __str__(self): - return f'Value({self.code})' - - @planning_entity - @dataclass(unsafe_hash=True) - class Entity: - code: str - value: Annotated[Value | None, PlanningVariable] = field(default=None) - - def __str__(self): - return f'Entity({self.code}, {self.value})' - - @dataclass(unsafe_hash=True) - class EntityValueIndictment: - entity: Entity - value: Value - - def __str__(self): - return f'EntityValueIndictment({self.entity}, {self.value})' - - @dataclass(unsafe_hash=True) - class EntityValueJustification(ConstraintJustification): - entity: Entity - value: Value - score: SimpleScore - - def __str__(self): - return f'EntityValueJustification({self.entity}, {self.value}, {self.score})' - - @dataclass(unsafe_hash=True) - class EntityValuePairJustification(ConstraintJustification): - entities: tuple[Entity] - value: Value - score: SimpleScore - - def __str__(self): - return f'EntityValuePairJustification({self.entities}, {self.value}, {self.score})' - - def same_value(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each(Entity) - .join(Entity, Joiners.less_than(lambda e: e.code), - Joiners.equal(lambda e: e.value)) - .penalize(SimpleScore.ONE) - .indict_with(lambda e1, e2: [EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, e2.value)]) - .justify_with(lambda e1, e2, score: EntityValuePairJustification((e1, e2), e1.value, score)) - .as_constraint('Same Value') - ) - - def is_value_one(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each(Entity) - .filter(lambda e: e.value.code == 'v1') - .reward(SimpleScore.ONE) - .indict_with(lambda e: [EntityValueIndictment(e, e.value)]) - .justify_with(lambda e, score: EntityValueJustification(e, e.value, score)) - .as_constraint('Value 1') - ) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - same_value(constraint_factory), - is_value_one(constraint_factory) - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ) - ) - - verifier = ConstraintVerifier.create(solver_config) - - e1 = Entity('e1') - e2 = Entity('e2') - e3 = Entity('e3') - - v1 = Value('v1') - v2 = Value('v2') - v3 = Value('v3') - - solution = Solution([e1, e2, e3], [v1, v2, v3]) - - verifier_suite(verifier, same_value, is_value_one, - EntityValueIndictment, EntityValueJustification, EntityValuePairJustification, - solution, e1, e2, e3, v1, v2, v3) - - verifier = ConstraintVerifier.build(my_constraints, Solution, Entity) - - e1 = Entity('e1') - e2 = Entity('e2') - e3 = Entity('e3') - - v1 = Value('v1') - v2 = Value('v2') - v3 = Value('v3') - - solution = Solution([e1, e2, e3], [v1, v2, v3]) - - verifier_suite(verifier, same_value, is_value_one, - EntityValueIndictment, EntityValueJustification, EntityValuePairJustification, - solution, e1, e2, e3, v1, v2, v3) - - -ignored_java_functions = { - 'equals', - 'getClass', - 'hashCode', - 'notify', - 'notifyAll', - 'toString', - 'wait', - 'withConstraintStreamImplType' -} - - -def test_has_all_methods(): - missing = [] - for python_type, java_type in ((ConstraintVerifier, JavaConstraintVerifier), - (SingleConstraintAssertion, JavaSingleConstraintAssertion), - (SingleConstraintVerification, JavaSingleConstraintVerification), - (MultiConstraintAssertion, JavaMultiConstraintAssertion), - (MultiConstraintVerification, JavaMultiConstraintVerification)): - for function_name, function_impl in inspect.getmembers(java_type, inspect.isfunction): - if function_name in ignored_java_functions: - continue - snake_case_name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', function_name) - # change h_t_t_p -> http - snake_case_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake_case_name).lower() - if not hasattr(python_type, snake_case_name): - missing.append((java_type, python_type, snake_case_name)) - - if missing: - assertion_msg = '' - for java_type, python_type, snake_case_name in missing: - assertion_msg += f'{python_type} is missing a method ({snake_case_name}) from java_type ({java_type}).)\n' - raise AssertionError(assertion_msg) - diff --git a/tests/test_custom_shadow_variables.py b/tests/test_custom_shadow_variables.py deleted file mode 100644 index a4ab9cc9..00000000 --- a/tests/test_custom_shadow_variables.py +++ /dev/null @@ -1,121 +0,0 @@ -from typing import Annotated, Optional, List -from dataclasses import dataclass, field - -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - - -def test_custom_shadow_variable(): - class MyVariableListener(VariableListener): - def after_variable_changed(self, score_director: ScoreDirector, entity): - score_director.before_variable_changed(entity, 'value_squared') - if entity.value is None: - entity.value_squared = None - else: - entity.value_squared = entity.value ** 2 - score_director.after_variable_changed(entity, 'value_squared') - - @planning_entity - @dataclass - class MyPlanningEntity: - value: Annotated[Optional[int], PlanningVariable] \ - = field(default=None) - value_squared: Annotated[Optional[int], ShadowVariable(variable_listener_class=MyVariableListener, - source_variable_name='value')] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(MyPlanningEntity) - .filter(lambda entity: entity.value * 2 == entity.value_squared) - .reward(SimpleScore.ONE) - .as_constraint('Double value is value squared') - ] - - @planning_solution - @dataclass - class MySolution: - entity_list: Annotated[List[MyPlanningEntity], PlanningEntityCollectionProperty] - value_list: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=MySolution, - entity_class_list=[MyPlanningEntity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='1' - ) - ) - - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver() - problem = MySolution([MyPlanningEntity()], [1, 2, 3]) - solution: MySolution = solver.solve(problem) - assert solution.score.score == 1 - assert solution.entity_list[0].value == 2 - assert solution.entity_list[0].value_squared == 4 - - -def test_custom_shadow_variable_with_variable_listener_ref(): - class MyVariableListener(VariableListener): - def after_variable_changed(self, score_director: ScoreDirector, entity): - score_director.before_variable_changed(entity, 'twice_value') - score_director.before_variable_changed(entity, 'value_squared') - if entity.value is None: - entity.twice_value = None - entity.value_squared = None - else: - entity.twice_value = 2 * entity.value - entity.value_squared = entity.value ** 2 - score_director.after_variable_changed(entity, 'value_squared') - score_director.after_variable_changed(entity, 'twice_value') - - @planning_entity - @dataclass - class MyPlanningEntity: - value: Annotated[Optional[int], PlanningVariable] = \ - field(default=None) - value_squared: Annotated[Optional[int], ShadowVariable( - variable_listener_class=MyVariableListener, source_variable_name='value')] = field(default=None) - twice_value: Annotated[Optional[int], PiggybackShadowVariable(shadow_variable_name='value_squared')] = ( - field(default=None)) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(MyPlanningEntity) - .filter(lambda entity: entity.twice_value == entity.value_squared) - .reward(SimpleScore.ONE) - .as_constraint('Double value is value squared') - ] - - @planning_solution - @dataclass - class MySolution: - entity_list: Annotated[List[MyPlanningEntity], PlanningEntityCollectionProperty] - value_list: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=MySolution, - entity_class_list=[MyPlanningEntity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='1' - ) - ) - - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver() - problem = MySolution([MyPlanningEntity()], [1, 2, 3]) - solution: MySolution = solver.solve(problem) - assert solution.score.score == 1 - assert solution.entity_list[0].value == 2 - assert solution.entity_list[0].value_squared == 4 diff --git a/tests/test_domain.py b/tests/test_domain.py deleted file mode 100644 index 690af30e..00000000 --- a/tests/test_domain.py +++ /dev/null @@ -1,956 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, Optional, List - - -def test_solve_partial(): - @dataclass - class Code: - value: str - - @dataclass - class Value: - code: Code - - @planning_entity - @dataclass - class Entity: - code: Code - value: Annotated[Value, PlanningVariable] = field(default=None) - - def is_value_one(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each(Entity) - .filter(lambda e: e.value.code.value == 'v1') - .reward(SimpleScore.ONE) - .as_constraint('Value 1') - ) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - is_value_one(constraint_factory) - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='3' - ) - ) - - e1 = Entity(Code('e1')) - e2 = Entity(Code('e2')) - e3 = Entity(Code('e3')) - - v1 = Value(Code('v1')) - v2 = Value(Code('v2')) - v3 = Value(Code('v3')) - - e1.value = v1 - e2.value = v2 - e3.value = v3 - - problem = Solution([e1, e2, e3], [v1, v2, v3]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - - assert solution.score.score == 3 - assert solution.entities[0].value == v1 - assert solution.entities[1].value == v1 - assert solution.entities[2].value == v1 - - -def test_solve_nullable(): - @dataclass - class Code: - value: str - - @dataclass - class Value: - code: Code - - @planning_entity - @dataclass - class Entity: - code: Code - value: Annotated[Optional[Value], PlanningVariable(allows_unassigned=True, - value_range_provider_refs=['value_range'])] = ( - field(default=None)) - - def at_least_one_null(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each_including_unassigned(Entity) - .filter(lambda e: e.value is None) - .group_by(ConstraintCollectors.count()) - .filter(lambda count: count >= 1) - .reward(HardSoftScore.ONE_SOFT) - .as_constraint('At least one null variable') - ) - - def assign_to_v1(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each_including_unassigned(Entity) - .filter(lambda e: e.value is not None and e.value.code.value == 'v1') - .group_by(ConstraintCollectors.count()) - .filter(lambda count: count >= 1) - .reward(HardSoftScore.ONE_HARD) - .as_constraint('At least one v1') - ) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - at_least_one_null(constraint_factory), - assign_to_v1(constraint_factory) - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider(id='value_range')] - score: Annotated[HardSoftScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='1hard/1soft' - ) - ) - - e1 = Entity(Code('e1')) - e2 = Entity(Code('e2')) - - v1 = Value(Code('v1')) - v2 = Value(Code('v2')) - - problem = Solution([e1, e2], [v1, v2]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - - assert solution.score.is_feasible - assert solution.score.init_score == 0 - assert solution.score.hard_score == 1 - assert solution.score.soft_score == 1 - assert solution.entities[0].value == v1 or solution.entities[0].value is None - assert solution.entities[1].value == v1 or solution.entities[1].value is None - - -def test_solve_typed(): - @dataclass - class Code: - value: str - - @dataclass - class Value: - code: Code - - @planning_entity - @dataclass - class Entity: - code: Code - value: Annotated[Value, PlanningVariable] = field(default=None) - - def assign_to_v1(constraint_factory: ConstraintFactory): - return (constraint_factory.for_each(Entity) - .filter(lambda e: e.value.code.value == 'v1') - .reward(SimpleScore.ONE) - .as_constraint('assign to v1') - ) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - assign_to_v1(constraint_factory) - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='2' - ) - ) - - e1 = Entity(Code('e1')) - e2 = Entity(Code('e2')) - - v1 = Value(Code('v1')) - v2 = Value(Code('v2')) - - problem = Solution([e1, e2], [v1, v2]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - - assert solution.score.score == 2 - assert solution.entities[0].value == v1 - assert solution.entities[1].value == v1 - - -def test_solve_complex_problem_facts(): - from abc import abstractmethod - - class BaseValue: - @abstractmethod - def get_id(self) -> str: - raise NotImplementedError('Calling function on abstract base class') - - @abstractmethod - def __str__(self) -> str: - raise NotImplementedError('Calling function on abstract base class') - - @dataclass - class ExpiringValue(BaseValue): - name: str - id: Annotated[str, PlanningId] - expiration_date: float - - def get_id(self) -> str: - return self.id - - def __str__(self) -> str: - return f'ExpiringValue(id={self.id}, name={self.name})' - - @dataclass - class SimpleValue(BaseValue): - name: str - id: Annotated[str, PlanningId] - - def get_id(self) -> str: - return self.id - - def __str__(self) -> str: - return f'SimpleValue(id={str(self.id)}, name={str(self.name)})' - - class NullValue(BaseValue): - name: str - id: Annotated[str, PlanningId] - - def get_id(self) -> str: - return self.id - - def __str__(self) -> str: - return f'NullValue(id={str(self.id)}, name={str(self.name)})' - - @planning_entity - @dataclass - class Entity: - id: Annotated[str, PlanningId] - list_of_suitable_values: List[BaseValue] - start_time: int - end_time: int - value: Annotated[Optional[BaseValue], PlanningVariable(value_range_provider_refs=['value_range'])] = ( - field(default=None)) - - def get_allowable_values(self) -> Annotated[List[BaseValue], ValueRangeProvider(id='value_range')]: - return self.list_of_suitable_values - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - score: Annotated[HardSoftScore, PlanningScore] = field(default=None) - - def is_present(r: Optional[BaseValue]) -> bool: - if isinstance(r, NullValue): - return False - elif isinstance(r, BaseValue): - return True - else: - return False - - def simultaneous_values(constraint_factory): - return ( - constraint_factory.for_each_unique_pair( - Entity, - # ... if they support overlapping times - (Joiners.overlapping(lambda entity: entity.start_time, - lambda entity: entity.end_time)), - ) - .filter( - lambda entity_1, entity_2: (is_present(entity_1.value) - and is_present(entity_2.value) - and entity_1.value.get_id() == entity_2.value.get_id()) - ) - # Then penalize it! - .penalize(HardSoftScore.ONE_HARD) - .as_constraint("Simultaneous values") - ) - - def empty_value(constraint_factory): - return ( - constraint_factory.for_each(Entity) - .filter(lambda entity: not is_present(entity.value)) - .penalize(HardSoftScore.ONE_SOFT) - .as_constraint("Prefer present value") - ) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - simultaneous_values(constraint_factory), - empty_value(constraint_factory) - ] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0hard/0soft' - ) - ) - - v1 = ExpiringValue('expiring', '0', 2.0) - v2 = SimpleValue('simple', '1') - v3 = NullValue() - - e1 = Entity('e1', [v1, v2], 0, 2) - e2 = Entity('e2', [v1, v3], 1, 3) - - problem = Solution([e1, e2]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - - assert solution.score.hard_score == 0 - assert solution.score.soft_score == 0 - assert solution.entity_list[0].value == v2 - assert solution.entity_list[1].value == v1 - - -def test_single_property(): - @dataclass - class Value: - code: str - - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[str, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Value, - Joiners.equal(lambda entity: entity.value, - lambda value: value.code)) - .reward(SimpleScore.ONE) - .as_constraint('Same as value'), - ] - - @planning_solution - @dataclass - class Solution: - entity: Annotated[Entity, PlanningEntityProperty] - value: Annotated[Value, ProblemFactProperty] - value_range: Annotated[List[str], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='1' - ) - ) - - problem: Solution = Solution(Entity('A'), Value('1'), ['1', '2', '3']) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 1 - assert solution.entity.value == '1' - - -def test_constraint_stream_in_join(): - @dataclass - class Value: - code: int - - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda e: e.code == 'A') - .join(constraint_factory.for_each(Entity).filter(lambda e: e.code == 'B')) - .join(constraint_factory.for_each(Entity).filter(lambda e: e.code == 'C')) - .join(constraint_factory.for_each(Entity).filter(lambda e: e.code == 'D')) - .group_by(ConstraintCollectors.sum(lambda a, b, c, d: a.value.code + b.value.code + - c.value.code + d.value.code)) - .reward(SimpleScore.ONE, lambda the_sum: the_sum) - .as_constraint('First Four Entities'), - ] - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ) - ) - - entity_1, entity_2, entity_3, entity_4, entity_5 = Entity('A'), Entity('B'), Entity('C'), Entity('D'), Entity('E') - value_1, value_2, value_3 = Value(1), Value(2), Value(3) - problem = Solution([entity_1, entity_2, entity_3, entity_4, entity_5], [value_1, value_2, value_3]) - score_manager = SolutionManager.create(SolverFactory.create(solver_config)) - - entity_1.value = value_1 - entity_2.value = value_1 - entity_3.value = value_1 - entity_4.value = value_1 - entity_5.value = value_1 - - assert score_manager.update(problem).score == 4 - - entity_5.value = value_2 - - assert score_manager.update(problem).score == 4 - - entity_1.value = value_2 - assert score_manager.update(problem).score == 5 - - entity_2.value = value_2 - assert score_manager.update(problem).score == 6 - - entity_3.value = value_2 - assert score_manager.update(problem).score == 7 - - entity_4.value = value_2 - assert score_manager.update(problem).score == 8 - - entity_1.value = value_3 - assert score_manager.update(problem).score == 9 - - -def test_tuple_group_by_key(): - @dataclass(eq=False) - class Value: - code: str - - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[str, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Value, - Joiners.equal(lambda entity: entity.value, - lambda value: value.code)) - .group_by(lambda entity, value: (0, value), ConstraintCollectors.count_bi()) - .reward(SimpleScore.ONE, lambda _, count: count) - .as_constraint('Same as value'), - ] - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], ProblemFactCollectionProperty] - value_range: Annotated[List[str], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - entity_list = [Entity('A0'), Entity('B0'), Entity('C0'), - Entity('A1'), Entity('B1'), Entity('C1'), - Entity('A2'), Entity('B2'), Entity('C2'), - Entity('A3'), Entity('B3'), Entity('C3'), - Entity('A4'), Entity('B4'), Entity('C4'), - Entity('A5'), Entity('B5'), Entity('C5'), - Entity('A6'), Entity('B6'), Entity('C6'), - Entity('A7'), Entity('B7'), Entity('C7'), - Entity('A8'), Entity('B8'), Entity('C8'), - Entity('A9'), Entity('B9'), Entity('C9')] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit=str(len(entity_list)) - ) - ) - - problem: Solution = Solution(entity_list, - [Value('1')], - ['1', '2', '3']) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == len(entity_list) - for entity in solution.entity_list: - assert entity.value == '1' - - -def test_python_object(): - import ctypes - pointer1 = ctypes.c_void_p(1) - pointer2 = ctypes.c_void_p(2) - pointer3 = ctypes.c_void_p(3) - - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[ctypes.c_void_p, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda entity: entity.value == pointer1) - .reward(SimpleScore.ONE) - .as_constraint('Same as value'), - constraint_factory.for_each(Entity) - .group_by(lambda entity: entity.value.value, ConstraintCollectors.count()) - .reward(SimpleScore.ONE, lambda value, count: count * count) - .as_constraint('Entity have same value'), - constraint_factory.for_each(Entity) - .group_by(lambda entity: (entity.code, entity.value.value)) - .join(Entity, - Joiners.equal(lambda pair: pair[0], lambda entity: entity.code), - Joiners.equal(lambda pair: pair[1], lambda entity: entity.value.value)) - .reward(SimpleScore.ONE) - .as_constraint('Entity for pair'), - ] - - @planning_solution - @dataclass - class Solution: - entity: Annotated[Entity, PlanningEntityProperty] - value_range: Annotated[List[ctypes.c_void_p], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='3' - ) - ) - problem: Solution = Solution(Entity('A'), [pointer1, pointer2, pointer3]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 3 - assert solution.entity.value is pointer1 - - -def test_custom_planning_id(): - from uuid import UUID, uuid4 - id_1 = uuid4() - id_2 = uuid4() - id_3 = uuid4() - - @dataclass(unsafe_hash=True) - class Value: - code: str - - @planning_entity - @dataclass - class Entity: - code: Annotated[UUID, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each_unique_pair(Entity, - Joiners.equal(lambda entity: entity.value)) - .penalize(SimpleScore.ONE) - .as_constraint('Same value'), - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - - entity_1 = Entity(id_1) - entity_2 = Entity(id_2) - entity_3 = Entity(id_3) - - value_1 = Value('A') - value_2 = Value('B') - value_3 = Value('C') - problem: Solution = Solution([ - entity_1, - entity_2, - entity_3 - ], [ - value_1, - value_2, - value_3 - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - - encountered = set() - for entity in solution.entities: - assert entity.value not in encountered - encountered.add(entity.value) - - -def test_custom_comparator(): - @dataclass(order=True, unsafe_hash=True) - class Value: - code: str - - @planning_entity - @dataclass - class Entity: - code: Annotated[int, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - # use less_than_or_equal and greater_than_or_equal since they require Comparable instances - .if_exists_other(Entity, Joiners.less_than_or_equal(lambda entity: entity.value), - Joiners.greater_than_or_equal(lambda entity: entity.value)) - .penalize(SimpleScore.ONE) - .as_constraint('Same value'), - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - - entity_1 = Entity(0) - entity_2 = Entity(1) - entity_3 = Entity(2) - - value_1 = Value('A') - value_2 = Value('B') - value_3 = Value('C') - problem: Solution = Solution([ - entity_1, - entity_2, - entity_3 - ], [ - value_1, - value_2, - value_3 - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - - encountered = set() - for entity in solution.entities: - assert entity.value not in encountered - encountered.add(entity.value) - - -def test_custom_equals(): - @dataclass(eq=True, unsafe_hash=True) - class Code: - code: str - - @dataclass - class Value: - code: Code - - @planning_entity - @dataclass - class Entity: - code: Annotated[int, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each_unique_pair(Entity, - Joiners.equal(lambda entity: entity.value.code)) - .penalize(SimpleScore.ONE) - .as_constraint('Same value') - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - values: Annotated[List[Value], ProblemFactCollectionProperty, ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='-1' - ) - ) - - value_1a = Value(Code('A')) - value_1b = Value(Code('A')) - value_2a = Value(Code('B')) - - entity_1 = Entity(0, value_1a) - entity_2 = Entity(1, value_1b) - entity_3 = Entity(2, value_2a) - problem: Solution = Solution([ - entity_1, - entity_2, - entity_3 - ], [ - value_1a, - value_1b, - value_2a, - ]) - score_manager = SolutionManager.create(SolverFactory.create(solver_config)) - score = score_manager.update(problem) - assert score.score == -1 - - -def test_entity_value_range_provider(): - @dataclass(unsafe_hash=True) - class Value: - code: str - - @planning_entity - @dataclass - class Entity: - code: Annotated[str, PlanningId] - possible_values: List[Value] - value: Annotated[Value, PlanningVariable(value_range_provider_refs=['value_range'])] = ( - field(default=None)) - - def get_possible_values(self) -> Annotated[List[Value], ValueRangeProvider(id='value_range')]: - return self.possible_values - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each_unique_pair(Entity, - Joiners.equal(lambda entity: entity.value)) - .reward(SimpleScore.ONE) - .as_constraint('Same value'), - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - - value_1 = Value('A') - value_2 = Value('B') - value_3 = Value('C') - - entity_1 = Entity('1', [value_1]) - entity_2 = Entity('2', [value_2]) - entity_3 = Entity('3', [value_3]) - - problem: Solution = Solution([ - entity_1, - entity_2, - entity_3 - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - - encountered = set() - for entity in solution.entities: - assert entity.value not in encountered - encountered.add(entity.value) - - -def test_int_value_range_provider(): - @planning_entity - @dataclass - class Entity: - code: Annotated[str, PlanningId] - actual_value: int - value: Annotated[int, PlanningVariable(value_range_provider_refs=['value_range'])] \ - = field(default=None) - possible_values: Annotated[CountableValueRange, ValueRangeProvider(id='value_range')] = field(init=False) - - def __post_init__(self): - self.possible_values = ValueRangeFactory.create_int_value_range(self.actual_value, - self.actual_value + 1) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each_unique_pair(Entity, - Joiners.equal(lambda entity: entity.value)) - .reward(SimpleScore.ONE) - .as_constraint('Same value'), - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - - entity_1 = Entity('1', 1) - entity_2 = Entity('2', 2) - entity_3 = Entity('3', 3) - - problem: Solution = Solution([ - entity_1, - entity_2, - entity_3 - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - - encountered = set() - for entity in solution.entities: - assert entity.value not in encountered - encountered.add(entity.value) - - -def test_list_variable(): - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[List[int], PlanningListVariable] = field(default_factory=list) - - def count_mismatches(entity): - mismatches = 0 - for index in range(len(entity.value)): - if entity.value[index] != index + 1: - mismatches += 1 - return mismatches - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda entity: any(entity.value[index] != index + 1 for index in range(len(entity.value)))) - .penalize(SimpleScore.ONE, count_mismatches) - .as_constraint('Value is not the same as index'), - ] - - @planning_solution - @dataclass - class Solution: - entity: Annotated[Entity, PlanningEntityProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - problem: Solution = Solution(Entity('A'), [1, 2, 3]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - assert solution.entity.value == [1, 2, 3] diff --git a/tests/test_easy_score_calculator.py b/tests/test_easy_score_calculator.py deleted file mode 100644 index 15f5d031..00000000 --- a/tests/test_easy_score_calculator.py +++ /dev/null @@ -1,47 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from typing import Annotated, List -from dataclasses import dataclass, field - - -def test_easy_score_calculator(): - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[int, PlanningVariable] = field(default=None) - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - @easy_score_calculator - def my_score_calculator(solution: Solution) -> SimpleScore: - total_score = 0 - for entity in solution.entity_list: - total_score += 0 if entity.value is None else entity.value - return SimpleScore.of(total_score) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - easy_score_calculator_function=my_score_calculator - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - problem: Solution = Solution([Entity('A'), Entity('B'), Entity('C')], [1, 2, 3]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 9 - assert solution.entity_list[0].value == 3 - assert solution.entity_list[1].value == 3 - assert solution.entity_list[2].value == 3 diff --git a/tests/test_incremental_score_calculator.py b/tests/test_incremental_score_calculator.py deleted file mode 100644 index d648aaa3..00000000 --- a/tests/test_incremental_score_calculator.py +++ /dev/null @@ -1,330 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -import pytest -from dataclasses import dataclass, field -from typing import Annotated, List, Optional - - -@planning_entity -@dataclass -class Queen: - code: str - column: int - row: Annotated[Optional[int], PlanningVariable] = field(default=None) - - def get_column_index(self): - return self.column - - def get_row_index(self): - if self.row is None: - return -1 - return self.row - - def get_ascending_diagonal_index(self): - return self.get_column_index() + self.get_row_index() - - def get_descending_diagonal_index(self): - return self.get_column_index() - self.get_row_index() - - def __eq__(self, other): - return self.code == other.code - - def __hash__(self): - return hash(self.code) - - -@planning_solution -@dataclass -class Solution: - n: int - queen_list: Annotated[List[Queen], PlanningEntityCollectionProperty] - column_list: List[int] - row_list: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - -def test_constraint_match_disabled_incremental_score_calculator(): - class NQueensIncrementalScoreCalculator(IncrementalScoreCalculator): - score: int - row_index_map: dict - ascending_diagonal_index_map: dict - descending_diagonal_index_map: dict - - def reset_working_solution(self, working_solution: Solution): - n = working_solution.n - self.row_index_map = dict() - self.ascending_diagonal_index_map = dict() - self.descending_diagonal_index_map = dict() - for i in range(n): - self.row_index_map[i] = list() - self.ascending_diagonal_index_map[i] = list() - self.descending_diagonal_index_map[i] = list() - if i != 0: - self.ascending_diagonal_index_map[n - 1 + i] = list() - self.descending_diagonal_index_map[-i] = list() - self.score = 0 - for queen in working_solution.queen_list: - self.insert(queen) - - def before_entity_added(self, entity: any): - pass - - def after_entity_added(self, entity: any): - self.insert(entity) - - def before_variable_changed(self, entity: any, variableName: str): - self.retract(entity) - - def after_variable_changed(self, entity: any, variableName: str): - self.insert(entity) - - def before_entity_removed(self, entity: any): - self.retract(entity) - - def after_entity_removed(self, entity: any): - pass - - def insert(self, queen: Queen): - if queen.row is not None: - row_index = queen.row - row_index_list = self.row_index_map[row_index] - self.score -= len(row_index_list) - row_index_list.append(queen) - ascending_diagonal_index_list = self.ascending_diagonal_index_map[queen.get_ascending_diagonal_index()] - self.score -= len(ascending_diagonal_index_list) - ascending_diagonal_index_list.append(queen) - descending_diagonal_index_list = self.descending_diagonal_index_map[queen.get_descending_diagonal_index()] - self.score -= len(descending_diagonal_index_list) - descending_diagonal_index_list.append(queen) - - def retract(self, queen: Queen): - if queen.row is not None: - row_index = queen.row - row_index_list = self.row_index_map[row_index] - row_index_list.remove(queen) - self.score += len(row_index_list) - ascending_diagonal_index_list = self.ascending_diagonal_index_map[queen.get_ascending_diagonal_index()] - ascending_diagonal_index_list.remove(queen) - self.score += len(ascending_diagonal_index_list) - descending_diagonal_index_list = self.descending_diagonal_index_map[queen.get_descending_diagonal_index()] - descending_diagonal_index_list.remove(queen) - self.score += len(descending_diagonal_index_list) - - def calculate_score(self) -> SimpleScore: - return SimpleScore.of(self.score) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Queen], - score_director_factory_config=ScoreDirectorFactoryConfig( - incremental_score_calculator_class=NQueensIncrementalScoreCalculator - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - problem: Solution = Solution(4, - [Queen('A', 0), Queen('B', 1), Queen('C', 2), Queen('D', 3)], - [0, 1, 2, 3], - [0, 1, 2, 3]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - for i in range(4): - for j in range(i + 1, 4): - left_queen = solution.queen_list[i] - right_queen = solution.queen_list[j] - assert left_queen.row is not None and right_queen.row is not None - assert left_queen.row != right_queen.row - assert left_queen.get_ascending_diagonal_index() != right_queen.get_ascending_diagonal_index() - assert left_queen.get_descending_diagonal_index() != right_queen.get_descending_diagonal_index() - - -@pytest.mark.skip(reason="Special case where you want to convert all items of the list before returning." - "Doing this for all conversions would be expensive." - "This feature is not that important, so skipping for now.") -def test_constraint_match_enabled_incremental_score_calculator(): - @incremental_score_calculator - class NQueensIncrementalScoreCalculator(ConstraintMatchAwareIncrementalScoreCalculator): - score: int - row_index_map: dict - ascending_diagonal_index_map: dict - descending_diagonal_index_map: dict - - def reset_working_solution(self, working_solution: Solution, constraint_match_enabled=False): - n = working_solution.n - self.row_index_map = dict() - self.ascending_diagonal_index_map = dict() - self.descending_diagonal_index_map = dict() - for i in range(n): - self.row_index_map[i] = list() - self.ascending_diagonal_index_map[i] = list() - self.descending_diagonal_index_map[i] = list() - if i != 0: - self.ascending_diagonal_index_map[n - 1 + i] = list() - self.descending_diagonal_index_map[-i] = list() - self.score = 0 - for queen in working_solution.queen_list: - self.insert(queen) - - def before_entity_added(self, entity: any): - pass - - def after_entity_added(self, entity: any): - self.insert(entity) - - def before_variable_changed(self, entity: any, variableName: str): - self.retract(entity) - - def after_variable_changed(self, entity: any, variableName: str): - self.insert(entity) - - def before_entity_removed(self, entity: any): - self.retract(entity) - - def after_entity_removed(self, entity: any): - pass - - def insert(self, queen: Queen): - row = queen.row - if row is not None: - row_index = queen.row - row_index_list = self.row_index_map[row_index] - self.score -= len(row_index_list) - row_index_list.append(queen) - ascending_diagonal_index_list = self.ascending_diagonal_index_map[queen.get_ascending_diagonal_index()] - self.score -= len(ascending_diagonal_index_list) - ascending_diagonal_index_list.append(queen) - descending_diagonal_index_list = self.descending_diagonal_index_map[queen.get_descending_diagonal_index()] - self.score -= len(descending_diagonal_index_list) - descending_diagonal_index_list.append(queen) - - def retract(self, queen: Queen): - row = queen.row - if row is not None: - row_index = queen.row - row_index_list = self.row_index_map[row_index] - row_index_list.remove(queen) - self.score += len(row_index_list) - ascending_diagonal_index_list = self.ascending_diagonal_index_map[queen.get_ascending_diagonal_index()] - ascending_diagonal_index_list.remove(queen) - self.score += len(ascending_diagonal_index_list) - descending_diagonal_index_list = self.descending_diagonal_index_map[queen.get_descending_diagonal_index()] - descending_diagonal_index_list.remove(queen) - self.score += len(descending_diagonal_index_list) - - def calculate_score(self) -> SimpleScore: - return SimpleScore.of(self.score) - - def get_constraint_match_totals(self): - row_conflict_constraint_match_total = DefaultConstraintMatchTotal( - 'NQueens', - 'Row Conflict', - SimpleScore.ONE) - ascending_diagonal_constraint_match_total = DefaultConstraintMatchTotal( - 'NQueens', - 'Ascending Diagonal Conflict', - SimpleScore.ONE) - descending_diagonal_constraint_match_total = DefaultConstraintMatchTotal( - 'NQueens', - 'Descending Diagonal Conflict', - SimpleScore.ONE) - for row, queens in self.row_index_map.items(): - if len(queens) > 1: - row_conflict_constraint_match_total.addConstraintMatch(queens, - SimpleScore.of( - -len(queens) + 1)) - for row, queens in self.ascending_diagonal_index_map.items(): - if len(queens) > 1: - ascending_diagonal_constraint_match_total.addConstraintMatch(queens, - SimpleScore.of( - -len(queens) + 1)) - for row, queens in self.descending_diagonal_index_map.items(): - if len(queens) > 1: - descending_diagonal_constraint_match_total.addConstraintMatch(queens, - SimpleScore.of( - -len(queens) + 1)) - return [ - row_conflict_constraint_match_total, - ascending_diagonal_constraint_match_total, - descending_diagonal_constraint_match_total - ] - - def get_indictment_map(self): - return None - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Queen], - score_director_factory_config=ScoreDirectorFactoryConfig( - incremental_score_calculator_class=NQueensIncrementalScoreCalculator - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - problem: Solution = Solution(4, - [Queen('A', 0), Queen('B', 1), Queen('C', 2), Queen('D', 3)], - [0, 1, 2, 3], - [0, 1, 2, 3]) - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver() - solution = solver.solve(problem) - assert solution.score.score == 0 - for i in range(4): - for j in range(i + 1, 4): - left_queen = solution.queen_list[i] - right_queen = solution.queen_list[j] - assert left_queen.row is not None and right_queen.row is not None - assert left_queen.row != right_queen.row - assert left_queen.get_ascending_diagonal_index() != right_queen.get_ascending_diagonal_index() - assert left_queen.get_descending_diagonal_index() != right_queen.get_descending_diagonal_index() - - score_manager = SolutionManager.create(solver_factory) - constraint_match_total_map = score_manager.explain(solution).constraint_match_total_map - row_conflict = constraint_match_total_map.get('NQueens/Row Conflict') - ascending_diagonal_conflict = constraint_match_total_map.get('NQueens/Ascending Diagonal Conflict') - descending_diagonal_conflict = constraint_match_total_map.get('NQueens/Descending Diagonal Conflict') - assert row_conflict.score.score == 0 - assert ascending_diagonal_conflict.score.score == 0 - assert descending_diagonal_conflict.score.score == 0 - - bad_solution = Solution(4, - [Queen('A', 0, 0), Queen('B', 1, 1), Queen('C', 2, 0), Queen('D', 3, 1)], - [0, 1, 2, 3], - [0, 1, 2, 3]) - score_explanation = score_manager.explain(bad_solution) - assert score_explanation.score.score == -5 - constraint_match_total_map = score_explanation.constraint_match_total_map - row_conflict = constraint_match_total_map.get('NQueens/Row Conflict') - ascending_diagonal_conflict = constraint_match_total_map.get('NQueens/Ascending Diagonal Conflict') - descending_diagonal_conflict = constraint_match_total_map.get('NQueens/Descending Diagonal Conflict') - assert row_conflict.score.score == -2 # (A, C), (B, D) - assert ascending_diagonal_conflict.score.score == -1 # (B, C) - assert descending_diagonal_conflict.score.score == -2 # (A, B), (C, D) - indictment_map = score_explanation.indictment_map - assert indictment_map.get(bad_solution.queen_list[0]).constraint_match_count == 2 - assert indictment_map.get(bad_solution.queen_list[1]).constraint_match_count == 3 - assert indictment_map.get(bad_solution.queen_list[2]).constraint_match_count == 3 - assert indictment_map.get(bad_solution.queen_list[3]).constraint_match_count == 2 - - -def test_error_message_for_missing_methods(): - with pytest.raises(TypeError): # Exact error message from ABC changes between versions - class IncrementalScoreCalculatorMissingMethods(IncrementalScoreCalculator): - def before_entity_added(self, entity): - pass - - def after_entity_added(self, entity): - pass - - def before_variable_changed(self, entity, variable_name: str): - pass - - def after_variable_changed(self, entity, variable_name: str): - pass - - score_calculator = IncrementalScoreCalculatorMissingMethods() diff --git a/tests/test_inverse_relation.py b/tests/test_inverse_relation.py deleted file mode 100644 index b5e6d83b..00000000 --- a/tests/test_inverse_relation.py +++ /dev/null @@ -1,85 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -class BaseClass: - pass - - -@planning_entity -@dataclass -class InverseRelationEntity: - code: str - value: Annotated[BaseClass, PlanningVariable(value_range_provider_refs=['value_range'])] = \ - field(default=None) - - def __hash__(self): - return hash(self.code) - - -@planning_entity -@dataclass -class InverseRelationValue(BaseClass): - code: str - entities: Annotated[List[InverseRelationEntity], - InverseRelationShadowVariable(source_variable_name='value')] = \ - field(default_factory=list) - - -@planning_solution -@dataclass -class InverseRelationSolution: - values: Annotated[List[InverseRelationValue], - PlanningEntityCollectionProperty, - ValueRangeProvider(id='value_range')] - entities: Annotated[List[InverseRelationEntity], - PlanningEntityCollectionProperty] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - -@constraint_provider -def inverse_relation_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(InverseRelationValue) - .filter(lambda value: len(value.entities) > 1) - .penalize(SimpleScore.ONE) - .as_constraint('Only one entity per value') - ] - - -def test_inverse_relation(): - solver_config = SolverConfig( - solution_class=InverseRelationSolution, - entity_class_list=[InverseRelationEntity, InverseRelationValue], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=inverse_relation_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0' - ) - ) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(InverseRelationSolution( - [ - InverseRelationValue('A'), - InverseRelationValue('B'), - InverseRelationValue('C') - ], - [ - InverseRelationEntity('1'), - InverseRelationEntity('2'), - InverseRelationEntity('3'), - ] - )) - assert solution.score.score == 0 - visited_set = set() - for value in solution.values: - assert len(value.entities) == 1 - assert value.entities[0] is not None - assert value.entities[0] not in visited_set - visited_set.add(value.entities[0]) diff --git a/tests/test_logging.py b/tests/test_logging.py deleted file mode 100644 index d13a94b0..00000000 --- a/tests/test_logging.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -@planning_entity -@dataclass -class Entity: - code: Annotated[str, PlanningId] - value: Annotated[int, PlanningVariable] = field(default=None, compare=False) - - -@constraint_provider -def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('Maximize value'), - ] - - -@planning_solution -@dataclass -class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - def __str__(self) -> str: - return str(self.entities) - - -def assert_in_logs(log_messages: list[str], message: str): - for log_message in log_messages: - if message in log_message: - return - raise AssertionError(f'Expected message {message} in {log_messages}, but it was not found.') - -def test_log_events_are_forwarded(caplog): - with caplog.at_level(logging.INFO, logger="timefold.solver"): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver(SolverConfigOverride( - termination_config=TerminationConfig( - best_score_limit='3' - ) - )) - problem = Solution([Entity('A')], [1, 2, 3]) - solver.solve(problem) - log_messages = [rec.message for rec in caplog.records] - # INFO - assert_in_logs(log_messages, 'Solving started') - # INFO - assert_in_logs(log_messages, 'Solving ended') - - -def test_support_changing_log_levels(caplog): - with caplog.at_level(logging.DEBUG, logger="timefold.solver"): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver(SolverConfigOverride( - termination_config=TerminationConfig( - best_score_limit='3' - ) - )) - problem = Solution([Entity('A')], [1, 2, 3]) - solver.solve(problem) - log_messages = [rec.message for rec in caplog.records] - # INFO - assert_in_logs(log_messages, 'Solving started') - # DEBUG - assert_in_logs(log_messages, 'CH step (0)') - # INFO - assert_in_logs(log_messages, 'Solving ended') - - -def test_support_trace_logs(caplog): - # Python has no logging.TRACE, logging.DEBUG = 10, so TRACE = 5 - with caplog.at_level(5, logger="timefold.solver"): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver(SolverConfigOverride( - termination_config=TerminationConfig( - best_score_limit='3' - ) - )) - problem = Solution([Entity('A')], [1, 2, 3]) - solver.solve(problem) - log_messages = [rec.message for rec in caplog.records] - # INFO - assert_in_logs(log_messages, 'Solving started') - # DEBUG - assert_in_logs(log_messages, 'CH step (0)') - # TRACE/5 - assert_in_logs(log_messages, 'Move index (1)') - # INFO - assert_in_logs(log_messages, 'Solving ended') diff --git a/tests/test_pinning.py b/tests/test_pinning.py deleted file mode 100644 index a201467b..00000000 --- a/tests/test_pinning.py +++ /dev/null @@ -1,96 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -def test_pinning_filter(): - def is_entity_pinned(_, entity): - return entity.is_pinned - - @planning_entity(pinning_filter=is_entity_pinned) - @dataclass - class Point: - value: Annotated[int, PlanningVariable] - is_pinned: bool = field(default=False) - - @planning_solution - @dataclass - class Solution: - values: Annotated[List[int], ValueRangeProvider] - points: Annotated[List[Point], PlanningEntityCollectionProperty] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Point) - .penalize(SimpleScore.ONE, lambda point: point.value) - .as_constraint("Minimize Value") - ] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Point], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - unimproved_spent_limit=Duration(milliseconds=100) - ) - ) - problem: Solution = Solution([0, 1, 2], - [ - Point(0), - Point(1), - Point(2, is_pinned=True) - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == -2 - - -def test_planning_pin(): - @planning_entity - @dataclass - class Point: - value: Annotated[int, PlanningVariable] - is_pinned: Annotated[bool, PlanningPin] = field(default=False) - - @planning_solution - @dataclass - class Solution: - values: Annotated[List[int], ValueRangeProvider] - points: Annotated[List[Point], PlanningEntityCollectionProperty] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Point) - .penalize(SimpleScore.ONE, lambda point: point.value) - .as_constraint('Minimize Value') - ] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Point], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - unimproved_spent_limit=Duration(milliseconds=100) - ) - ) - problem: Solution = Solution([0, 1, 2], - [ - Point(0), - Point(1), - Point(2, is_pinned=True) - ]) - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - assert solution.score.score == -2 diff --git a/tests/test_score.py b/tests/test_score.py deleted file mode 100644 index 822df64a..00000000 --- a/tests/test_score.py +++ /dev/null @@ -1,196 +0,0 @@ -from dataclasses import dataclass, field -from decimal import Decimal -from timefold.solver import * -from timefold.solver.config import * -from timefold.solver.domain import * -from timefold.solver.score import * -from typing import Annotated - -def test_simple_score(): - uninit_score = SimpleScore(10, init_score=-2) - score = SimpleScore.of(10) - - assert str(uninit_score) == '-2init/10' - assert str(score) == '10' - - assert SimpleScore.parse('-2init/10') == uninit_score - assert SimpleScore.parse('10') == score - - -def test_hard_soft_score(): - uninit_score = HardSoftScore(100, 20, init_score=-3) - score = HardSoftScore.of(100, 20) - - assert str(uninit_score) == '-3init/100hard/20soft' - assert str(score) == '100hard/20soft' - - assert HardSoftScore.parse('-3init/100hard/20soft') == uninit_score - assert HardSoftScore.parse('100hard/20soft') == score - - -def test_hard_medium_soft_score(): - uninit_score = HardMediumSoftScore(1000, 200, 30, init_score=-4) - score = HardMediumSoftScore.of(1000, 200, 30) - - assert str(uninit_score) == '-4init/1000hard/200medium/30soft' - assert str(score) == '1000hard/200medium/30soft' - - assert HardMediumSoftScore.parse('-4init/1000hard/200medium/30soft') == uninit_score - assert HardMediumSoftScore.parse('1000hard/200medium/30soft') == score - - -def test_bendable_score(): - uninit_score = BendableScore((1, -2, 3), (-30, 40), init_score=-500) - score = BendableScore.of((1, -2, 3), (-30, 40)) - - assert str(uninit_score) == '-500init/[1/-2/3]hard/[-30/40]soft' - assert str(score) == '[1/-2/3]hard/[-30/40]soft' - - assert BendableScore.parse('-500init/[1/-2/3]hard/[-30/40]soft') == uninit_score - assert BendableScore.parse('[1/-2/3]hard/[-30/40]soft') == score - - -def test_simple_decimal_score(): - uninit_score = SimpleDecimalScore(Decimal('10.1'), init_score=-2) - score = SimpleDecimalScore.of(Decimal('10.1')) - - assert str(uninit_score) == '-2init/10.1' - assert str(score) == '10.1' - - assert SimpleDecimalScore.parse('-2init/10.1') == uninit_score - assert SimpleDecimalScore.parse('10.1') == score - - -def test_hard_soft_decimal_score(): - uninit_score = HardSoftDecimalScore(Decimal('100.1'), Decimal('20.2'), init_score=-3) - score = HardSoftDecimalScore.of(Decimal('100.1'), Decimal('20.2')) - - assert str(uninit_score) == '-3init/100.1hard/20.2soft' - assert str(score) == '100.1hard/20.2soft' - - assert HardSoftDecimalScore.parse('-3init/100.1hard/20.2soft') == uninit_score - assert HardSoftDecimalScore.parse('100.1hard/20.2soft') == score - - -def test_hard_medium_soft_decimal_score(): - uninit_score = HardMediumSoftDecimalScore(Decimal('1000.1'), Decimal('200.2'), Decimal('30.3'), init_score=-4) - score = HardMediumSoftDecimalScore.of(Decimal('1000.1'), Decimal('200.2'), Decimal('30.3')) - - assert str(uninit_score) == '-4init/1000.1hard/200.2medium/30.3soft' - assert str(score) == '1000.1hard/200.2medium/30.3soft' - - assert HardMediumSoftDecimalScore.parse('-4init/1000.1hard/200.2medium/30.3soft') == uninit_score - assert HardMediumSoftDecimalScore.parse('1000.1hard/200.2medium/30.3soft') == score - - -def test_bendable_decimal_score(): - uninit_score = BendableDecimalScore((Decimal('1.1'), Decimal('-2.2'), Decimal('3.3')), - (Decimal('-30.3'), Decimal('40.4')), init_score=-500) - score = BendableDecimalScore.of((Decimal('1.1'), Decimal('-2.2'), Decimal('3.3')), - (Decimal('-30.3'), Decimal('40.4'))) - - print(str(uninit_score)) - assert str(uninit_score) == '-500init/[1.1/-2.2/3.3]hard/[-30.3/40.4]soft' - assert str(score) == '[1.1/-2.2/3.3]hard/[-30.3/40.4]soft' - - assert BendableDecimalScore.parse('-500init/[1.1/-2.2/3.3]hard/[-30.3/40.4]soft') == uninit_score - assert BendableDecimalScore.parse('[1.1/-2.2/3.3]hard/[-30.3/40.4]soft') == score - - -def test_sanity_score_type(): - @planning_entity - @dataclass - class Entity: - value: Annotated[int | None, PlanningVariable] = field(default=None) - - for score_type, score_value in ( - (SimpleScore, SimpleScore.ONE), - (HardSoftScore, HardSoftScore.ONE_HARD), - (HardMediumSoftScore, HardMediumSoftScore.ONE_HARD), - (BendableScore, BendableScore.of((1, ), (0, ))), - (SimpleDecimalScore, SimpleDecimalScore.ONE), - (HardSoftDecimalScore, HardSoftDecimalScore.ONE_HARD), - (HardMediumSoftDecimalScore, HardMediumSoftDecimalScore.ONE_HARD), - (BendableDecimalScore, BendableDecimalScore.of((Decimal(1), ), (Decimal(0), ))) - ): - score_annotation = PlanningScore - if score_type == BendableScore or score_type == BendableDecimalScore: - score_annotation = PlanningScore(bendable_hard_levels_size=1, - bendable_soft_levels_size=1) - - @planning_solution - @dataclass - class Solution: - entities: Annotated[list[Entity], PlanningEntityCollectionProperty] - values: Annotated[list[int], ValueRangeProvider] - score: Annotated[score_type | None, score_annotation] = field(default=None) - - @constraint_provider - def constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(score_value) - .as_constraint('Minimize value') - ] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=constraints - ), - termination_config=TerminationConfig( - best_score_limit=str(score_value) - ) - ) - - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver() - problem = Solution(entities=[Entity()], - values=[1]) - solution = solver.solve(problem) - assert solution.entities[0].value == 1 - assert solution.score == score_value - - -def test_simple_decimal_score_domain(): - @planning_entity - @dataclass - class Entity: - value: Annotated[Decimal | None, PlanningVariable] = field(default=None) - - @planning_solution - @dataclass - class Solution: - entities: Annotated[list[Entity], PlanningEntityCollectionProperty] - values: Annotated[list[Decimal], ValueRangeProvider] - score: Annotated[SimpleDecimalScore | None, PlanningScore] = field(default=None) - - - @constraint_provider - def constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .penalize_decimal(SimpleDecimalScore.of(Decimal('0.1')), lambda e: e.value) - .as_constraint('Minimize value') - ] - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=constraints - ), - termination_config=TerminationConfig( - best_score_limit='-0.2' - ) - ) - - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver() - problem = Solution(entities=[Entity() for i in range(2)], - values=[Decimal(1), Decimal(2), Decimal(3)]) - solution = solver.solve(problem) - assert solution.entities[0].value == 1 - assert solution.entities[1].value == 1 - assert solution.score == SimpleDecimalScore.of(Decimal('-0.2')) diff --git a/tests/test_solution_manager.py b/tests/test_solution_manager.py deleted file mode 100644 index 82784bf5..00000000 --- a/tests/test_solution_manager.py +++ /dev/null @@ -1,311 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -import inspect -import re - -from ai.timefold.solver.core.api.score import ScoreExplanation as JavaScoreExplanation -from ai.timefold.solver.core.api.score.analysis import ( - ConstraintAnalysis as JavaConstraintAnalysis, - MatchAnalysis as JavaMatchAnalysis, - ScoreAnalysis as JavaScoreAnalysis) -from ai.timefold.solver.core.api.score.constraint import Indictment as JavaIndictment -from ai.timefold.solver.core.api.score.constraint import (ConstraintRef as JavaConstraintRef, - ConstraintMatch as JavaConstraintMatch, - ConstraintMatchTotal as JavaConstraintMatchTotal) - -from dataclasses import dataclass, field -from typing import Annotated, List - - -@planning_entity -@dataclass(eq=True, unsafe_hash=True) -class Entity: - code: Annotated[str, PlanningId] = field(hash=True) - value: Annotated[int, PlanningVariable] = field(default=None, hash=False, compare=False) - - -@constraint_provider -def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('package', 'Maximize Value'), - ] - - -@planning_solution -@dataclass -class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - -solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ) -) - - -def assert_score_explanation(problem: Solution, - score_explanation: ScoreExplanation[Solution]): - assert score_explanation.solution is problem - assert score_explanation.score.score == 3 - - constraint_ref = ConstraintRef(package_name='package', constraint_name='Maximize Value') - constraint_match_total_map = score_explanation.constraint_match_total_map - assert constraint_match_total_map == { - constraint_ref.constraint_id: ConstraintMatchTotal( - constraint_ref=constraint_ref, - constraint_match_count=3, - constraint_weight=SimpleScore.ONE, - score=SimpleScore.of(3), - constraint_match_set={ - ConstraintMatch( - constraint_ref=constraint_ref, - justification=DefaultConstraintJustification( - facts=(entity,), - impact=SimpleScore.ONE - ), - indicted_objects=(entity,), - score=SimpleScore.ONE - ) for entity in problem.entity_list - } - ) - } - - indictment_map = score_explanation.indictment_map - for entity in problem.entity_list: - indictment = indictment_map[entity] - assert indictment.indicted_object is entity - assert indictment.score == SimpleScore.ONE - assert indictment.constraint_match_count == 1 - assert indictment.constraint_match_set == { - ConstraintMatch( - constraint_ref=constraint_ref, - justification=DefaultConstraintJustification( - facts=(entity,), - impact=SimpleScore.ONE - ), - indicted_objects=(entity,), - score=SimpleScore.ONE - ) - } - - assert constraint_ref.constraint_name in score_explanation.summary - assert 'Entity' in score_explanation.summary - - -def assert_constraint_analysis(problem: Solution, constraint_analysis: ConstraintAnalysis): - constraint_ref = ConstraintRef(package_name='package', constraint_name='Maximize Value') - assert constraint_analysis.score.score == 3 - assert constraint_analysis.weight.score == 1 - assert constraint_analysis.constraint_name == constraint_ref.constraint_name - assert constraint_analysis.constraint_package == constraint_ref.package_name - assert constraint_analysis.constraint_ref == constraint_ref - - matches = constraint_analysis.matches - assert len(matches) == 3 - for entity in problem.entity_list: - for match in constraint_analysis.matches: - if match.justification.facts[0] is entity: - assert match.score == SimpleScore.ONE - assert match.constraint_ref == constraint_ref - assert match.justification.facts == (entity,) - assert match.justification.impact == SimpleScore.ONE - break - else: - raise AssertionError(f'Entity {entity} does not have a match') - - -def assert_score_analysis(problem: Solution, score_analysis: ScoreAnalysis): - constraint_ref = ConstraintRef(package_name='package', constraint_name='Maximize Value') - assert score_analysis.score.score == 3 - - constraint_map = score_analysis.constraint_map - assert len(constraint_map) == 1 - - constraint_analysis = score_analysis.constraint_map[constraint_ref] - assert_constraint_analysis(problem, constraint_analysis) - - constraint_analyses = score_analysis.constraint_analyses - assert len(constraint_analyses) == 1 - constraint_analysis = constraint_analyses[0] - assert_constraint_analysis(problem, constraint_analysis) - - -def assert_score_analysis_summary(score_analysis: ScoreAnalysis): - summary = score_analysis.summary - assert "Explanation of score (3):" in summary - assert "Constraint matches:" in summary - assert "3: constraint (Maximize Value) has 3 matches:" in summary - assert "1: justified with" in summary - - summary_str = str(score_analysis) - assert summary == summary_str - - match = score_analysis.constraint_analyses[0] - match_summary = match.summary - assert "Explanation of score (3):" in match_summary - assert "Constraint matches:" in match_summary - assert "3: constraint (Maximize Value) has 3 matches:" in match_summary - assert "1: justified with" in match_summary - - match_summary_str = str(match) - assert match_summary == match_summary_str - - -def assert_solution_manager(solution_manager: SolutionManager[Solution]): - problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1)], [1, 2, 3]) - assert problem.score is None - score = solution_manager.update(problem) - assert score.score == 3 - assert problem.score.score == 3 - - score_explanation = solution_manager.explain(problem) - assert_score_explanation(problem, score_explanation) - - score_analysis = solution_manager.analyze(problem) - assert_score_analysis(problem, score_analysis) - - score_analysis = solution_manager.analyze(problem) - assert_score_analysis_summary(score_analysis) - - -def test_solver_manager_score_manager(): - with SolverManager.create(SolverFactory.create(solver_config)) as solver_manager: - assert_solution_manager(SolutionManager.create(solver_manager)) - - -def test_solver_factory_score_manager(): - assert_solution_manager(SolutionManager.create(SolverFactory.create(solver_config))) - - -def test_score_manager_solution_initialization(): - solution_manager = SolutionManager.create(SolverFactory.create(solver_config)) - problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1)], [1, 2, 3]) - score_analysis = solution_manager.analyze(problem) - assert score_analysis.is_solution_initialized - - second_problem: Solution = Solution([Entity('A', None), Entity('B', None), Entity('C', None)], [1, 2, 3]) - second_score_analysis = solution_manager.analyze(second_problem) - assert not second_score_analysis.is_solution_initialized - - -def test_score_manager_diff(): - solution_manager = SolutionManager.create(SolverFactory.create(solver_config)) - problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1)], [1, 2, 3]) - score_analysis = solution_manager.analyze(problem) - second_problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1), Entity('D', 1)], [1, 2, 3]) - second_score_analysis = solution_manager.analyze(second_problem) - diff = score_analysis.diff(second_score_analysis) - assert diff.score.score == -1 - - diff_operation = score_analysis - second_score_analysis - assert diff_operation.score.score == -1 - - constraint_analyses = score_analysis.constraint_analyses - assert len(constraint_analyses) == 1 - - -def test_score_manager_constraint_analysis_map(): - solution_manager = SolutionManager.create(SolverFactory.create(solver_config)) - problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1)], [1, 2, 3]) - score_analysis = solution_manager.analyze(problem) - constraints = score_analysis.constraint_analyses - assert len(constraints) == 1 - - constraint_analysis = score_analysis.constraint_analysis('package', 'Maximize Value') - assert constraint_analysis.constraint_name == 'Maximize Value' - - constraint_analysis = score_analysis.constraint_analysis(ConstraintRef('package', 'Maximize Value')) - assert constraint_analysis.constraint_name == 'Maximize Value' - assert constraint_analysis.match_count == 3 - - -def test_score_manager_constraint_ref(): - constraint_ref = ConstraintRef.parse_id('package/Maximize Value') - - assert constraint_ref.package_name == 'package' - assert constraint_ref.constraint_name == 'Maximize Value' - - -ignored_java_functions = { - 'equals', - 'getClass', - 'hashCode', - 'notify', - 'notifyAll', - 'toString', - 'wait', - 'compareTo', -} - -ignored_java_functions_per_class = { - 'Indictment': {'getJustification'}, # deprecated - 'ConstraintRef': {'of', 'packageName', 'constraintName'}, # built-in constructor and properties with @dataclass - 'ConstraintAnalysis': {'summarize'}, # using summary instead - 'ScoreAnalysis': {'summarize'}, # using summary instead - 'ConstraintMatch': { - 'getConstraintRef', # built-in constructor and properties with @dataclass - 'getConstraintPackage', # deprecated - 'getConstraintName', # deprecated - 'getConstraintId', # deprecated - 'getJustificationList', # deprecated - 'getJustification', # built-in constructor and properties with @dataclass - 'getScore', # built-in constructor and properties with @dataclass - 'getIndictedObjectList', # built-in constructor and properties with @dataclass - }, - 'ConstraintMatchTotal': { - 'getConstraintRef', # built-in constructor and properties with @dataclass - 'composeConstraintId', # deprecated - 'getConstraintPackage', # deprecated - 'getConstraintName', # deprecated - 'getConstraintId', # deprecated - 'getConstraintMatchCount', # built-in constructor and properties with @dataclass - 'getConstraintMatchSet', # built-in constructor and properties with @dataclass - 'getConstraintWeight', # built-in constructor and properties with @dataclass - 'getScore', # built-in constructor and properties with @dataclass - }, -} - - -def test_has_all_methods(): - missing = [] - for python_type, java_type in ((ScoreExplanation, JavaScoreExplanation), - (ScoreAnalysis, JavaScoreAnalysis), - (ConstraintAnalysis, JavaConstraintAnalysis), - (ScoreExplanation, JavaScoreExplanation), - (ConstraintMatch, JavaConstraintMatch), - (ConstraintMatchTotal, JavaConstraintMatchTotal), - (ConstraintRef, JavaConstraintRef), - (Indictment, JavaIndictment)): - type_name = python_type.__name__ - ignored_java_functions_type = ignored_java_functions_per_class[ - type_name] if type_name in ignored_java_functions_per_class else {} - - for function_name, function_impl in inspect.getmembers(java_type, inspect.isfunction): - if function_name in ignored_java_functions or function_name in ignored_java_functions_type: - continue - - snake_case_name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', function_name) - snake_case_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake_case_name).lower() - snake_case_name_without_prefix = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', - function_name[3:] if function_name.startswith( - "get") else function_name) - snake_case_name_without_prefix = re.sub('([a-z0-9])([A-Z])', r'\1_\2', - snake_case_name_without_prefix).lower() - if not hasattr(python_type, snake_case_name) and not hasattr(python_type, snake_case_name_without_prefix): - missing.append((java_type, python_type, snake_case_name)) - - if missing: - assertion_msg = '' - for java_type, python_type, snake_case_name in missing: - assertion_msg += f'{python_type} is missing a method ({snake_case_name}) from java_type ({java_type}).)\n' - raise AssertionError(assertion_msg) diff --git a/tests/test_solver_config.py b/tests/test_solver_config.py deleted file mode 100644 index 5020b51f..00000000 --- a/tests/test_solver_config.py +++ /dev/null @@ -1,88 +0,0 @@ -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -import pathlib -import pytest -import re - -from dataclasses import dataclass, field -from typing import Annotated, List - - -class Value: - def __init__(self, code): - self.code = code - - -@planning_entity -@dataclass -class Entity: - code: str - value: Annotated[str, PlanningVariable] = field(default=None) - - -@constraint_provider -def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .join(Value, - Joiners.equal(lambda entity: entity.value, - lambda value: value.code)) - .reward(SimpleScore.ONE) - .as_constraint('Same as value'), - ] - - -@planning_solution -class Solution: - entity: Annotated[Entity, PlanningEntityProperty] - value: Annotated[Value, ProblemFactProperty] - value_range: Annotated[List[str], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] - - -def get_java_solver_config(path: pathlib.Path): - return SolverConfig.create_from_xml_resource(path)._to_java_solver_config() - - -def test_load_from_solver_config_file(): - from _jpyinterpreter import get_java_type_for_python_type - solver_config = get_java_solver_config(pathlib.Path('tests', 'solverConfig-simple.xml')) - assert solver_config.getSolutionClass() == get_java_type_for_python_type(Solution).getJavaClass() - entity_class_list = solver_config.getEntityClassList() - assert entity_class_list.size() == 1 - assert entity_class_list.get(0) == get_java_type_for_python_type(Entity).getJavaClass() - assert solver_config.getScoreDirectorFactoryConfig().getConstraintProviderClass() == \ - my_constraints._timefold_java_class # noqa - assert solver_config.getTerminationConfig().getBestScoreLimit() == '0hard/0soft' - - -def test_reload_from_solver_config_file(): - from _jpyinterpreter import get_java_type_for_python_type - - @planning_solution - class RedefinedSolution: - ... - - RedefinedSolution1 = RedefinedSolution - solver_config_1 = get_java_solver_config(pathlib.Path('tests', 'solverConfig-redefined.xml')) - - @planning_solution - class RedefinedSolution: - ... - - RedefinedSolution2 = RedefinedSolution - solver_config_2 = get_java_solver_config(pathlib.Path('tests', 'solverConfig-redefined.xml')) - - assert solver_config_1.getSolutionClass() == get_java_type_for_python_type(RedefinedSolution1).getJavaClass() - assert solver_config_2.getSolutionClass() == get_java_type_for_python_type(RedefinedSolution2).getJavaClass() - - -def test_cannot_find_solver_config_file(): - from java.lang import Thread - current_thread = Thread.currentThread() - thread_class_loader = current_thread.getContextClassLoader() - with pytest.raises(FileNotFoundError, match=re.escape("The solverConfigFile (does-not-exist.xml) was not found.")): - get_java_solver_config(pathlib.Path('does-not-exist.xml')) - assert current_thread.getContextClassLoader() == thread_class_loader diff --git a/tests/test_solver_configuration.py b/tests/test_solver_configuration.py deleted file mode 100644 index c875979a..00000000 --- a/tests/test_solver_configuration.py +++ /dev/null @@ -1,57 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -def test_solver_configuration(): - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[int, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward_configurable(lambda entity: entity.value) - .as_constraint('Maximize value'), - ] - - @constraint_configuration - @dataclass - class ConstraintConfiguration: - maximize_value: Annotated[SimpleScore, ConstraintWeight('Maximize value')] = field(default=SimpleScore.ONE) - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - configuration: Annotated[ConstraintConfiguration, ConstraintConfigurationProvider] = ( - field(default_factory=ConstraintConfiguration)) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='6' - ) - ) - - problem: Solution = Solution([Entity('A')], [1, 2, 3], - configuration=ConstraintConfiguration(maximize_value=SimpleScore.of(2))) - - solver = SolverFactory.create(solver_config).build_solver() - solution = solver.solve(problem) - - assert solution.score.score == 6 - assert solution.entities[0].value == 3 diff --git a/tests/test_solver_events.py b/tests/test_solver_events.py deleted file mode 100644 index 40e35fb7..00000000 --- a/tests/test_solver_events.py +++ /dev/null @@ -1,63 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -def test_solver_events(): - @planning_entity - @dataclass - class Entity: - code: str - value: Annotated[int, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('Maximize value'), - ] - - @planning_solution - @dataclass - class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='6' - ) - ) - - problem: Solution = Solution([Entity('A'), Entity('B')], [1, 2, 3]) - score_list = [] - solution_list = [] - - def on_best_solution_changed(event): - solution_list.append(event.new_best_solution) - score_list.append(event.new_best_score) - - solver = SolverFactory.create(solver_config).build_solver() - solver.add_event_listener(on_best_solution_changed) - solution = solver.solve(problem) - - assert solution.score.score == 6 - assert solution.entities[0].value == 3 - assert solution.entities[1].value == 3 - assert len(score_list) == len(solution_list) - assert len(solution_list) == 1 - assert score_list[0].score == 6 - assert solution_list[0].score.score == 6 - assert solution_list[0].entities[0].value == 3 - assert solution_list[0].entities[1].value == 3 diff --git a/tests/test_solver_factory.py b/tests/test_solver_factory.py deleted file mode 100644 index 28d146b6..00000000 --- a/tests/test_solver_factory.py +++ /dev/null @@ -1,56 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List - - -@planning_entity -@dataclass -class Entity: - code: Annotated[str, PlanningId] - value: Annotated[int, PlanningVariable] = field(default=None, compare=False) - - -@constraint_provider -def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('Maximize value'), - ] - - -@planning_solution -@dataclass -class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - def __str__(self) -> str: - return str(self.entities) - - -def test_solver_config_override(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - solver_factory = SolverFactory.create(solver_config) - solver = solver_factory.build_solver(SolverConfigOverride( - termination_config=TerminationConfig( - best_score_limit='3' - ) - )) - problem = Solution([Entity('A')], [1, 2, 3]) - solution = solver.solve(problem) - assert solution.score.score == 3 diff --git a/tests/test_solver_manager.py b/tests/test_solver_manager.py deleted file mode 100644 index 6c6c9020..00000000 --- a/tests/test_solver_manager.py +++ /dev/null @@ -1,300 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -import pytest -from dataclasses import dataclass, field -from typing import Annotated, List - - -@pytest.mark.xfail(reason='Flaky test') -def test_solve(): - from threading import Lock, Semaphore - lock = Lock() - - def get_lock(entity): - lock.acquire() - lock.release() - return False - - @dataclass - class Value: - value: Annotated[int, PlanningId] - - @planning_entity - @dataclass - class Entity: - code: Annotated[str, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None, compare=False) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(get_lock) - .reward(SimpleScore.ONE) - .as_constraint('Wait for lock'), - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value.value) - .as_constraint('Maximize Value'), - constraint_factory.for_each_unique_pair(Entity, - Joiners.equal(lambda entity: entity.value.value)) - .penalize(SimpleScore.of(12)) - .as_constraint('Same Value'), - ] - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], - DeepPlanningClone, - ProblemFactCollectionProperty, - ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - class UseOnlyEntityAndValueProblemChange(ProblemChange[Solution]): - def __init__(self, entity, value): - self.entity = entity - self.value = value - - def do_change(self, solution: Solution, problem_change_director: ProblemChangeDirector): - problem_facts_to_remove = solution.value_list.copy() - entities_to_remove = solution.entity_list.copy() - for problem_fact in problem_facts_to_remove: - problem_change_director.remove_problem_fact(problem_fact, - lambda value: solution.value_list.remove(value)) - for removed_entity in entities_to_remove: - problem_change_director.remove_entity(removed_entity, - lambda entity: solution.entity_list.remove(entity)) - problem_change_director.add_entity(self.entity, lambda entity: solution.entity_list.append(entity)) - problem_change_director.add_problem_fact(self.value, lambda value: solution.value_list.append(value)) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='6' - ) - ) - problem: Solution = Solution([Entity('A'), Entity('B'), Entity('C')], [Value(1), Value(2), Value(3)], - SimpleScore.ONE) - - def assert_solver_run(solver_manager, solver_job): - assert solver_manager.get_solver_status(1) != SolverStatus.NOT_SOLVING - lock.release() - solution = solver_job.get_final_best_solution() - assert solution.score.score == 6 - value_list = [entity.value.value for entity in solution.entity_list] - assert 1 in value_list - assert 2 in value_list - assert 3 in value_list - assert solver_manager.get_solver_status(1) == SolverStatus.NOT_SOLVING - - def assert_problem_change_solver_run(solver_manager, solver_job): - assert solver_manager.get_solver_status(1) != SolverStatus.NOT_SOLVING - solver_manager.add_problem_change(1, UseOnlyEntityAndValueProblemChange(Entity('D'), Value(6))) - lock.release() - solution = solver_job.get_final_best_solution() - assert solution.score.score == 6 - assert len(solution.entity_list) == 1 - assert len(solution.value_list) == 1 - assert solution.entity_list[0].code == 'D' - assert solution.entity_list[0].value.value == 6 - assert solution.value_list[0].value == 6 - assert solver_manager.get_solver_status(1) == SolverStatus.NOT_SOLVING - - - with SolverManager.create(solver_config, SolverManagerConfig(parallel_solver_count='AUTO')) as solver_manager: - lock.acquire() - solver_job = solver_manager.solve(1, problem) - assert_solver_run(solver_manager, solver_job) - - lock.acquire() - solver_job = solver_manager.solve(1, problem) - assert_problem_change_solver_run(solver_manager, solver_job) - - def get_problem(problem_id): - assert problem_id == 1 - return problem - - lock.acquire() - solver_job = (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem_finder(get_problem)).run() - assert_solver_run(solver_manager, solver_job) - - solution_list = [] - semaphore = Semaphore(0) - - def on_best_solution_changed(solution): - solution_list.append(solution) - semaphore.release() - - lock.acquire() - solver_job = (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem_finder(get_problem) - .with_best_solution_consumer(on_best_solution_changed) - ).run() - assert_solver_run(solver_manager, solver_job) - assert semaphore.acquire(timeout=1) - assert len(solution_list) == 1 - - solution_list = [] - lock.acquire() - solver_job = (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem_finder(get_problem) - .with_best_solution_consumer(on_best_solution_changed) - .with_final_best_solution_consumer(on_best_solution_changed) - ).run() - assert_solver_run(solver_manager, solver_job) - - # Wait for 2 acquires, one for best solution consumer, - # another for final best solution consumer - assert semaphore.acquire(timeout=1) - assert semaphore.acquire(timeout=1) - assert len(solution_list) == 2 - - -@pytest.mark.filterwarnings("ignore:.*Exception in thread.*:pytest.PytestUnhandledThreadExceptionWarning") -def test_error(): - @dataclass - class Value: - value: Annotated[int, PlanningId] - - @planning_entity - @dataclass - class Entity: - code: Annotated[str, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .filter(lambda e: e.missing_attribute == 1) - .reward(SimpleScore.ONE, lambda entity: entity.value.value) - .as_constraint('Maximize Value') - ] - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], - DeepPlanningClone, - ProblemFactCollectionProperty, - ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='6' - ) - ) - problem: Solution = Solution([Entity('A'), Entity('B'), Entity('C')], [Value(1), Value(2), Value(3)], - SimpleScore.ONE) - with SolverManager.create(SolverFactory.create(solver_config)) as solver_manager: - the_problem_id = None - the_exception = None - - def my_exception_handler(problem_id, exception): - nonlocal the_problem_id - nonlocal the_exception - the_problem_id = problem_id - the_exception = exception - - try: - (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem(problem) - .with_exception_handler(my_exception_handler) - .run().get_final_best_solution()) - except: - pass - - assert the_problem_id == 1 - assert the_exception is not None - - the_problem_id = None - the_exception = None - - try: - (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem(problem) - .with_best_solution_consumer(lambda solution: None) - .with_exception_handler(my_exception_handler) - .run().get_final_best_solution()) - except: - pass - - assert the_problem_id == 1 - assert the_exception is not None - - -def test_solver_config_override(): - @dataclass - class Value: - value: Annotated[int, PlanningId] - - @planning_entity - @dataclass - class Entity: - code: Annotated[str, PlanningId] - value: Annotated[Value, PlanningVariable] = field(default=None) - - @constraint_provider - def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value.value) - .as_constraint('Maximize Value') - ] - - @planning_solution - @dataclass - class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[Value], - DeepPlanningClone, - ProblemFactCollectionProperty, - ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - best_score_limit='9' - ) - ) - problem: Solution = Solution([Entity('A')], [Value(1), Value(2), Value(3)], - SimpleScore.ONE) - with SolverManager.create(SolverFactory.create(solver_config)) as solver_manager: - solver_job = (solver_manager.solve_builder() - .with_problem_id(1) - .with_problem(problem) - .with_config_override(SolverConfigOverride( - termination_config=TerminationConfig( - best_score_limit='3' - ) - )) - .run()) - - solution = solver_job.get_final_best_solution() - assert solution.score.score == 3 diff --git a/tests/test_solver_problem_change.py b/tests/test_solver_problem_change.py deleted file mode 100644 index 888c4a70..00000000 --- a/tests/test_solver_problem_change.py +++ /dev/null @@ -1,134 +0,0 @@ -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from dataclasses import dataclass, field -from typing import Annotated, List -from threading import Thread - - -@planning_entity -@dataclass -class Entity: - code: Annotated[str, PlanningId] - value: Annotated[int, PlanningVariable] = field(default=None, compare=False) - - -@constraint_provider -def maximize_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .reward(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('Maximize value'), - ] - - -@constraint_provider -def minimize_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .penalize(SimpleScore.ONE, lambda entity: entity.value) - .as_constraint('Minimize value'), - ] - - -@planning_solution -@dataclass -class Solution: - entities: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_range: Annotated[List[int], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - def __str__(self) -> str: - return str(self.entities) - - -class AddEntity(ProblemChange[Solution]): - entity: Entity - - def __init__(self, entity: Entity): - self.entity = entity - - def do_change(self, working_solution: Solution, problem_change_director: ProblemChangeDirector): - problem_change_director.add_entity(self.entity, - lambda working_entity: working_solution.entities.append(working_entity)) - - -class RemoveEntity(ProblemChange[Solution]): - entity: Entity - - def __init__(self, entity: Entity): - self.entity = entity - - def do_change(self, working_solution: Solution, problem_change_director: ProblemChangeDirector): - problem_change_director.remove_entity(self.entity, - lambda working_entity: working_solution.entities.remove(working_entity)) - - -def test_add_entity(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=maximize_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='6' - ) - ) - - problem: Solution = Solution([Entity('A')], [1, 2, 3]) - solver = SolverFactory.create(solver_config).build_solver() - result: Solution | None = None - - def do_solve(problem: Solution): - nonlocal solver, result - result = solver.solve(problem) - - thread = Thread(target=do_solve, args=(problem,), daemon=True) - - thread.start() - solver.add_problem_change(AddEntity(Entity('B'))) - thread.join(timeout=1) - - if thread.is_alive(): - raise AssertionError(f'Thread {thread} did not finish after 5 seconds') - - assert result is not None - assert len(result.entities) == 2 - assert result.score.score == 6 - - -def test_remove_entity(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=minimize_constraints, - ), - termination_config=TerminationConfig( - best_score_limit='-1' - ) - ) - - problem: Solution = Solution([Entity('A'), Entity('B')], [1, 2, 3]) - solver = SolverFactory.create(solver_config).build_solver() - result: Solution | None = None - - def do_solve(problem: Solution): - nonlocal solver, result - result = solver.solve(problem) - - thread = Thread(target=do_solve, args=(problem,), daemon=True) - - thread.start() - solver.add_problem_change(RemoveEntity(Entity('B'))) - thread.join(timeout=1) - - if thread.is_alive(): - raise AssertionError(f'Thread {thread} did not finish after 5 seconds') - - assert result is not None - assert len(result.entities) == 1 - assert result.score.score == -1 diff --git a/tests/test_user_error.py b/tests/test_user_error.py deleted file mode 100644 index dcf7eaf6..00000000 --- a/tests/test_user_error.py +++ /dev/null @@ -1,122 +0,0 @@ -from timefold.solver import * -from timefold.solver.config import * -from timefold.solver.domain import * -from timefold.solver.heuristic import * -from timefold.solver.score import * - -import pytest -import re -from typing import Annotated, List -from dataclasses import dataclass, field - - -@planning_entity -@dataclass -class Entity: - value: Annotated[str, PlanningVariable] = field(default=None) - - -@planning_solution -@dataclass -class Solution: - entity_list: Annotated[List[Entity], PlanningEntityCollectionProperty] - value_list: Annotated[List[str], ValueRangeProvider] - score: Annotated[SimpleScore, PlanningScore] = field(default=None) - - -@constraint_provider -def my_constraints(constraint_factory: ConstraintFactory): - return [ - constraint_factory.for_each(Entity) - .penalize(SimpleScore.ONE, lambda entity: 'TEN') # noqa - .as_constraint('Penalize each entity') - ] - - -def test_non_planning_solution_being_passed_to_solve(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ) - ) - solver = SolverFactory.create(solver_config).build_solver() - with pytest.raises(ValueError, match=re.escape( - f'The problem ({10}) is not an instance of the @planning_solution class' - )): - solver.solve(10) - - -def test_none_passed_to_solve(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ) - ) - solver = SolverFactory.create(solver_config).build_solver() - with pytest.raises(ValueError, match=re.escape( - f'The problem ({None}) is not an instance of the @planning_solution class' - )): - solver.solve(None) - - -def test_bad_return_type(): - solver_config = SolverConfig( - solution_class=Solution, - entity_class_list=[Entity], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=my_constraints - ), - termination_config=TerminationConfig( - spent_limit=Duration(milliseconds=100) - ) - ) - - problem = Solution([Entity()], ['1', '2', '3']) - solver = SolverFactory.create(solver_config).build_solver() - with pytest.raises(RuntimeError): - solver.solve(problem) - - -def test_non_proxied_class_passed(): - class NonProxied: - pass - - with pytest.raises(TypeError, match=re.escape( - f'is not a @planning_solution class' - )): - solver_config = SolverConfig( - solution_class=NonProxied - )._to_java_solver_config() - - -def test_non_proxied_function_passed(): - def not_proxied(): - pass - - with pytest.raises(TypeError, match=re.escape( - f'is not a @constraint_provider function')): - solver_config = SolverConfig( - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=not_proxied # noqa - ) - )._to_java_solver_config() - - -def test_missing_enterprise(): - with pytest.raises(RequiresEnterpriseError, match=re.escape('multithreaded solving')): - solver_config = SolverConfig( - move_thread_count=MoveThreadCount.AUTO - )._to_java_solver_config() - - @nearby_distance_meter - def my_distance_meter(entity: Entity, value: str) -> float: - return 0.0 - - with pytest.raises(RequiresEnterpriseError, match=re.escape('nearby selection')): - solver_config = SolverConfig( - nearby_distance_meter_function=my_distance_meter - )._to_java_solver_config() diff --git a/tests/test_vehicle_routing.py b/tests/test_vehicle_routing.py deleted file mode 100644 index eb367381..00000000 --- a/tests/test_vehicle_routing.py +++ /dev/null @@ -1,305 +0,0 @@ -from datetime import datetime, timedelta - -from timefold.solver import * -from timefold.solver.domain import * -from timefold.solver.config import * -from timefold.solver.score import * - -from typing import Annotated, List, Optional -from dataclasses import dataclass, field - - -@dataclass -class Location: - latitude: float - longitude: float - driving_time_seconds: dict[int, int] = field(default_factory=dict) - - def driving_time_to(self, other: 'Location') -> int: - return self.driving_time_seconds[id(other)] - - -class ArrivalTimeUpdatingVariableListener(VariableListener): - def after_variable_changed(self, score_director: ScoreDirector, visit: 'Visit') -> None: - if visit.vehicle is None: - if visit.arrival_time is not None: - score_director.before_variable_changed(visit, 'arrival_time') - visit.arrival_time = None - score_director.after_variable_changed(visit, 'arrival_time') - return - previous_visit = visit.previous_visit - departure_time = visit.vehicle.departure_time if previous_visit is None else previous_visit.departure_time() - next_visit = visit - arrival_time = ArrivalTimeUpdatingVariableListener.calculate_arrival_time(next_visit, departure_time) - while next_visit is not None and next_visit.arrival_time != arrival_time: - score_director.before_variable_changed(next_visit, 'arrival_time') - next_visit.arrival_time = arrival_time - score_director.after_variable_changed(next_visit, 'arrival_time') - departure_time = next_visit.departure_time() - next_visit = next_visit.next_visit - arrival_time = ArrivalTimeUpdatingVariableListener.calculate_arrival_time(next_visit, departure_time) - - @staticmethod - def calculate_arrival_time(visit: Optional['Visit'], previous_departure_time: Optional[datetime]) \ - -> datetime | None: - if visit is None or previous_departure_time is None: - return None - return previous_departure_time + timedelta(seconds=visit.driving_time_seconds_from_previous_standstill()) - - -@planning_entity -@dataclass -class Visit: - id: Annotated[str, PlanningId] - name: str - location: Location - demand: int - min_start_time: datetime - max_end_time: datetime - service_duration: timedelta - vehicle: Annotated[Optional['Vehicle'], InverseRelationShadowVariable(source_variable_name='visits')] = ( - field(default=None)) - previous_visit: Annotated[Optional['Visit'], PreviousElementShadowVariable(source_variable_name='visits')] = ( - field(default=None)) - next_visit: Annotated[Optional['Visit'], - NextElementShadowVariable(source_variable_name='visits')] = field(default=None) - arrival_time: Annotated[Optional[datetime], - ShadowVariable(variable_listener_class=ArrivalTimeUpdatingVariableListener, - source_variable_name='vehicle'), - ShadowVariable(variable_listener_class=ArrivalTimeUpdatingVariableListener, - source_variable_name='previous_visit')] = field(default=None) - - def departure_time(self) -> Optional[datetime]: - if self.arrival_time is None: - return None - - return self.arrival_time + self.service_duration - - def start_service_time(self) -> Optional[datetime]: - if self.arrival_time is None: - return None - return self.min_start_time if (self.min_start_time < self.arrival_time) else self.arrival_time - - def is_service_finished_after_max_end_time(self) -> bool: - return self.arrival_time is not None and self.departure_time() > self.max_end_time - - def service_finished_delay_in_minutes(self) -> int: - if self.arrival_time is None: - return 0 - return (self.max_end_time - self.departure_time()).seconds // 60 - - def driving_time_seconds_from_previous_standstill(self) -> int: - if self.vehicle is None: - raise ValueError("This method must not be called when the shadow variables are not initialized yet.") - - if self.previous_visit is None: - return self.vehicle.home_location.driving_time_to(self.location) - else: - return self.previous_visit.location.driving_time_to(self.location) - - def driving_time_seconds_from_previous_standstill_or_none(self) -> Optional[int]: - if self.vehicle is None: - return None - return self.driving_time_seconds_from_previous_standstill() - - def __str__(self): - return self.id - - -@planning_entity -@dataclass -class Vehicle: - id: Annotated[str, PlanningId] - capacity: int - home_location: Location - departure_time: datetime - visits: Annotated[list[Visit], PlanningListVariable] = field(default_factory=list) - - def total_demand(self) -> int: - total_demand = 0 - for visit in self.visits: - total_demand += visit.demand - return total_demand - - def total_driving_time_seconds(self) -> int: - if len(self.visits) == 0: - return 0 - total_driving_time_seconds = 0 - previous_location = self.home_location - - for visit in self.visits: - total_driving_time_seconds += previous_location.driving_time_to(visit.location) - previous_location = visit.location - - total_driving_time_seconds += previous_location.driving_time_to(self.home_location) - return total_driving_time_seconds - - def arrival_time(self): - if len(self.visits) == 0: - return self.departure_time - - last_visit = self.visits[-1] - return (last_visit.departure_time() + - timedelta(seconds=last_visit.location.driving_time_to(self.home_location))) - - -@planning_solution -@dataclass -class VehicleRoutePlan: - vehicles: Annotated[list[Vehicle], PlanningEntityCollectionProperty] - visits: Annotated[list[Visit], PlanningEntityCollectionProperty, ValueRangeProvider] - score: Annotated[HardSoftScore, PlanningScore] = field(default=None) - - -@constraint_provider -def vehicle_routing_constraints(factory: ConstraintFactory): - return [ - vehicle_capacity(factory), - service_finished_after_max_end_time(factory), - minimize_travel_time(factory) - ] - -############################################## -# Hard constraints -############################################## - - -def vehicle_capacity(factory: ConstraintFactory): - return (factory.for_each(Vehicle) - .filter(lambda vehicle: vehicle.total_demand() > vehicle.capacity) - .penalize(HardSoftScore.ONE_HARD, - lambda vehicle: vehicle.total_demand() - vehicle.capacity) - .as_constraint('VEHICLE_CAPACITY') - ) - - -def service_finished_after_max_end_time(factory: ConstraintFactory): - return (factory.for_each(Visit) - .filter(lambda visit: visit.is_service_finished_after_max_end_time()) - .penalize(HardSoftScore.ONE_HARD, - lambda visit: visit.service_finished_delay_in_minutes()) - .as_constraint('SERVICE_FINISHED_AFTER_MAX_END_TIME') - ) - -############################################## -# Soft constraints -############################################## - - -def minimize_travel_time(factory: ConstraintFactory): - return ( - factory.for_each(Vehicle) - .penalize(HardSoftScore.ONE_SOFT, - lambda vehicle: vehicle.total_driving_time_seconds()) - .as_constraint('MINIMIZE_TRAVEL_TIME') - ) - - -def test_vrp(): - solver_config = SolverConfig( - solution_class=VehicleRoutePlan, - entity_class_list=[Vehicle, Visit], - score_director_factory_config=ScoreDirectorFactoryConfig( - constraint_provider_function=vehicle_routing_constraints - ), - termination_config=TerminationConfig( - best_score_limit='0hard/-300soft' - ) - ) - - solver = SolverFactory.create(solver_config).build_solver() - l1 = Location(1, 1) - l2 = Location(2, 2) - l3 = Location(3, 3) - l4 = Location(4, 4) - l5 = Location(5, 5) - - l1.driving_time_seconds = { - id(l1): 0, - id(l2): 60, - id(l3): 60 * 60, - id(l4): 60 * 60, - id(l5): 60 * 60 - } - - l2.driving_time_seconds = { - id(l1): 60 * 60, - id(l2): 0, - id(l3): 60, - id(l4): 60 * 60, - id(l5): 60 * 60 - } - - l3.driving_time_seconds = { - id(l1): 60, - id(l2): 60 * 60, - id(l3): 0, - id(l4): 60 * 60, - id(l5): 60 * 60 - } - - l4.driving_time_seconds = { - id(l1): 60 * 60, - id(l2): 60 * 60, - id(l3): 60 * 60, - id(l4): 0, - id(l5): 60 - } - - l5.driving_time_seconds = { - id(l1): 60 * 60, - id(l2): 60 * 60, - id(l3): 60 * 60, - id(l4): 60, - id(l5): 0 - } - - problem = VehicleRoutePlan( - vehicles=[ - Vehicle( - id='A', - capacity=3, - home_location=l1, - departure_time=datetime(2020, 1, 1), - ), - Vehicle( - id='B', - capacity=3, - home_location=l4, - departure_time=datetime(2020, 1, 1), - ), - ], - visits=[ - Visit( - id='1', - name='1', - location=l2, - demand=1, - min_start_time=datetime(2020, 1, 1), - max_end_time=datetime(2020, 1, 1, hour=10), - service_duration=timedelta(hours=1), - ), - Visit( - id='2', - name='2', - location=l3, - demand=1, - min_start_time=datetime(2020, 1, 1), - max_end_time=datetime(2020, 1, 1, hour=10), - service_duration=timedelta(hours=1), - ), - Visit( - id='3', - name='3', - location=l5, - demand=1, - min_start_time=datetime(2020, 1, 1), - max_end_time=datetime(2020, 1, 1, hour=10), - service_duration=timedelta(hours=1), - ), - ] - ) - solution = solver.solve(problem) - - assert [visit.id for visit in solution.vehicles[0].visits] == ['1', '2'] - assert [visit.id for visit in solution.vehicles[1].visits] == ['3'] diff --git a/timefold-solver-python-core/.gitignore b/timefold-solver-python-core/.gitignore deleted file mode 100644 index cc3c0ab1..00000000 --- a/timefold-solver-python-core/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/dist -/*.egg-info -/target -/*-stubs - -# Eclipse, Netbeans and IntelliJ files -/.* -!.gitignore -!.dockerignore -!.mvn -/nbproject -/*.ipr -/*.iws -/*.iml diff --git a/timefold-solver-python-core/LICENSE b/timefold-solver-python-core/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/timefold-solver-python-core/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/timefold-solver-python-core/pom.xml b/timefold-solver-python-core/pom.xml deleted file mode 100644 index 977e3058..00000000 --- a/timefold-solver-python-core/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - ai.timefold.solver - timefold-solver-python-parent - 999-SNAPSHOT - ../pom.xml - - 4.0.0 - timefold-solver-python-core - - - ai.timefold.solver.python - - - - - ai.timefold.solver - timefold-solver-core - - - ai.timefold.solver - timefold-solver-test - - - ai.timefold.solver - jpyinterpreter - - - io.quarkus.gizmo - gizmo - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-core - - - org.slf4j - slf4j-api - - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-dependency-plugin - - - ai.timefold.solver:timefold-solver-core,ai.timefold.solver:timefold-solver-test - - ai.timefold.solver:timefold-solver-core-impl - - - - copy-dependencies - - copy-dependencies - - - - dependency-classpath - - build-classpath - - - target/classpath.txt - - - - - - - diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/DaemonThreadFactory.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/DaemonThreadFactory.java deleted file mode 100644 index 7becd345..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/DaemonThreadFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.python; - -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - * There a Catch-22 that occurs on shutdown: - *

- * - In order for Python to free its variables, it must be terminated. - * - In order for Python to be terminated, the JVM must be terminated. - * - In order for the JVM to be terminated, all its non-daemon threads must be terminated. - * - Executors keep all its threads alive until it is freed/have no more references. - * - In order for the Executor to be freed/have no more references, it cannot have a reference in Python. - * - To not have a reference in Python means Python must free its variables, creating the Catch-22 - *

- * Thus, if non-daemon threads are used, and a {@link ai.timefold.solver.core.api.solver.SolverManager} - * solves at least one problem (creating a keep-alive thread in its {@link java.util.concurrent.ThreadPoolExecutor}), - * Python cannot shut down gracefully and will become unresponsive when interrupted. - *

- * This class uses {@link Executors#defaultThreadFactory()} to create a new thread, but sets the created - * thread to daemon mode so Python can shut down gracefully. - */ -public class DaemonThreadFactory implements ThreadFactory { - private static final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable runnable) { - Thread out = THREAD_FACTORY.newThread(runnable); - out.setDaemon(true); - return out; - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonValueRangeFactory.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonValueRangeFactory.java deleted file mode 100644 index 7e9032d6..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonValueRangeFactory.java +++ /dev/null @@ -1,100 +0,0 @@ -package ai.timefold.solver.python; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Iterator; -import java.util.Random; -import java.util.function.Function; - -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeFactory; - -@SuppressWarnings("unused") -public class PythonValueRangeFactory { - private PythonValueRangeFactory() { - } - - private record IteratorMapper(Iterator sourceIterator, Function valueConvertor) - implements - Iterator { - - @Override - public boolean hasNext() { - return sourceIterator.hasNext(); - } - - @Override - public To_ next() { - return valueConvertor.apply(sourceIterator.next()); - } - } - - private record ValueRangeMapper(CountableValueRange sourceValueRange, - Function valueConvertor, Function inverseValueConvertor) - implements - CountableValueRange { - - @Override - public long getSize() { - return sourceValueRange.getSize(); - } - - @Override - public To_ get(long index) { - return valueConvertor.apply(sourceValueRange.get(index)); - } - - @Override - public Iterator createOriginalIterator() { - return new IteratorMapper<>(sourceValueRange.createOriginalIterator(), valueConvertor); - } - - @Override - public boolean isEmpty() { - return sourceValueRange.isEmpty(); - } - - @Override - public boolean contains(To_ value) { - return sourceValueRange.contains(inverseValueConvertor.apply(value)); - } - - @Override - public Iterator createRandomIterator(Random random) { - return new IteratorMapper<>(sourceValueRange.createRandomIterator(random), valueConvertor); - } - } - - public static CountableValueRange createIntValueRange(BigInteger from, BigInteger to) { - return new ValueRangeMapper<>(ValueRangeFactory.createBigIntegerValueRange(from, to), - PythonInteger::valueOf, - pythonInteger -> pythonInteger.value); - } - - public static CountableValueRange createIntValueRange(BigInteger from, BigInteger to, BigInteger step) { - return new ValueRangeMapper<>(ValueRangeFactory.createBigIntegerValueRange(from, to, step), - PythonInteger::valueOf, - pythonInteger -> pythonInteger.value); - } - - public static CountableValueRange createFloatValueRange(BigDecimal from, BigDecimal to) { - return new ValueRangeMapper<>(ValueRangeFactory.createBigDecimalValueRange(from, to), - decimal -> PythonFloat.valueOf(decimal.doubleValue()), - pythonFloat -> BigDecimal.valueOf(pythonFloat.value)); - } - - public static CountableValueRange createFloatValueRange(BigDecimal from, BigDecimal to, BigDecimal step) { - return new ValueRangeMapper<>(ValueRangeFactory.createBigDecimalValueRange(from, to, step), - decimal -> PythonFloat.valueOf(decimal.doubleValue()), - pythonFloat -> BigDecimal.valueOf(pythonFloat.value)); - } - - public static CountableValueRange createBooleanValueRange() { - return new ValueRangeMapper<>(ValueRangeFactory.createBooleanValueRange(), - PythonBoolean::valueOf, - PythonBoolean::getBooleanValue); - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonWrapperGenerator.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonWrapperGenerator.java deleted file mode 100644 index 43cab29b..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/PythonWrapperGenerator.java +++ /dev/null @@ -1,326 +0,0 @@ -package ai.timefold.solver.python; - -import static ai.timefold.jpyinterpreter.PythonBytecodeToJavaBytecodeTranslator.writeClassOutput; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.asmClassLoader; -import static ai.timefold.jpyinterpreter.types.BuiltinTypes.classNameToBytecode; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import ai.timefold.solver.core.api.domain.variable.VariableListener; -import ai.timefold.solver.core.api.score.calculator.ConstraintMatchAwareIncrementalScoreCalculator; -import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; -import ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; - -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; - -public class PythonWrapperGenerator { - @SuppressWarnings("unused") - public static ClassLoader getClassLoaderForAliasMap(Map> aliasMap) { - return new ClassLoader() { - // getName() is an abstract method in Java 11 but not in Java 8 - public String getName() { - return "Timefold Alias Map ClassLoader"; - } - - @Override - public Class findClass(String name) throws ClassNotFoundException { - if (aliasMap.containsKey(name)) { - // Gizmo generated class - return aliasMap.get(name); - } else { - // Not a Gizmo generated class; load from parent class loader - return asmClassLoader.loadClass(name); - } - } - }; - } - - private static ClassOutput getClassOutput(AtomicReference bytesReference) { - return (path, byteCode) -> { - bytesReference.set(byteCode); - }; - } - - /** - * Creates a class that looks like this: - * - * class JavaWrapper implements NaryFunction { - * public static NaryFunction delegate; - * - * #64;Override - * public AN apply(A0 arg0, A1 arg1, ..., A(N-1) finalArg) { - * return delegate.apply(arg0,arg1,...,finalArg); - * } - * } - * - * @param className The simple name of the generated class - * @param baseInterface the base interface - * @param delegate The Python function to delegate to - * @return never null - */ - @SuppressWarnings({ "unused", "unchecked" }) - public static Class defineWrapperFunction(String className, Class baseInterface, - Object delegate) { - Method[] interfaceMethods = baseInterface.getMethods(); - if (interfaceMethods.length != 1) { - throw new IllegalArgumentException("Can only call this function for functional interfaces (only 1 method)"); - } - if (classNameToBytecode.containsKey(className)) { - try { - return (Class) asmClassLoader.loadClass(className); - } catch (ClassNotFoundException e) { - throw new IllegalStateException( - "Impossible State: the class (" + className + ") should exists since it was created"); - } - } - AtomicReference classBytecodeHolder = new AtomicReference<>(); - ClassOutput classOutput = getClassOutput(classBytecodeHolder); - - // holds the delegate (static; same one is reused; should be stateless) - FieldDescriptor delegateField; - try (ClassCreator classCreator = ClassCreator.builder() - .className(className) - .interfaces(baseInterface) - .classOutput(classOutput) - .build()) { - delegateField = classCreator.getFieldCreator("delegate", baseInterface) - .setModifiers(Modifier.STATIC | Modifier.PUBLIC) - .getFieldDescriptor(); - MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.ofMethod(interfaceMethods[0])); - - ResultHandle pythonProxy = methodCreator.readStaticField(delegateField); - ResultHandle[] args = new ResultHandle[interfaceMethods[0].getParameterCount()]; - for (int i = 0; i < args.length; i++) { - args[i] = methodCreator.getMethodParam(i); - } - ResultHandle constraints = methodCreator.invokeInterfaceMethod( - MethodDescriptor.ofMethod(interfaceMethods[0]), - pythonProxy, args); - methodCreator.returnValue(constraints); - } catch (Exception e) { - throw new IllegalStateException(e); - } - writeClassOutput(classNameToBytecode, className, classBytecodeHolder.get()); - try { - // Now that the class created, we need to set it static field to the delegate function - Class out = (Class) asmClassLoader.loadClass(className); - out.getField(delegateField.getName()).set(null, delegate); - return out; - } catch (Exception e) { - throw new IllegalStateException( - "Impossible State: the class (" + className + ") should exists since it was just created"); - } - } - - /** - * Creates a class that looks like this: - * - * class JavaWrapper implements SomeInterface { - * public static Supplier<SomeInterface> supplier; - * - * private SomeInterface delegate; - * - * public JavaWrapper() { - * delegate = supplier.get(); classNameToBytecode.put(className, classBytecodeHolder.get()); - * } - * - * #64;Override - * public Result interfaceMethod1(A0 arg0, A1 arg1, ..., A(N-1) finalArg) { - * return delegate.interfaceMethod1(arg0,arg1,...,finalArg); - * } - * - * #64;Override - * public Result interfaceMethod2(A0 arg0, A1 arg1, ..., A(N-1) finalArg) { - * return delegate.interfaceMethod2(arg0,arg1,...,finalArg); - * } - * } - * - * @param className The simple name of the generated class - * @param baseInterface the base interface - * @param delegateSupplier The Python class to delegate to - * @return never null - */ - @SuppressWarnings({ "unused", "unchecked" }) - public static Class defineWrapperClass(String className, Class baseInterface, - Supplier delegateSupplier) { - Method[] interfaceMethods = baseInterface.getMethods(); - if (classNameToBytecode.containsKey(className)) { - try { - return (Class) asmClassLoader.loadClass(className); - } catch (ClassNotFoundException e) { - throw new IllegalStateException( - "Impossible State: the class (" + className + ") should exists since it was created"); - } - } - AtomicReference classBytecodeHolder = new AtomicReference<>(); - ClassOutput classOutput = getClassOutput(classBytecodeHolder); - - // holds the supplier of the delegate (static) - FieldDescriptor supplierField; - - // holds the delegate (instance; new one created for each instance) - FieldDescriptor delegateField; - try (ClassCreator classCreator = ClassCreator.builder() - .className(className) - .interfaces(baseInterface) - .classOutput(classOutput) - .build()) { - supplierField = classCreator.getFieldCreator("delegateSupplier", Supplier.class) - .setModifiers(Modifier.STATIC | Modifier.PUBLIC) - .getFieldDescriptor(); - delegateField = classCreator.getFieldCreator("delegate", baseInterface) - .setModifiers(Modifier.PUBLIC | Modifier.FINAL) - .getFieldDescriptor(); - - MethodCreator constructorCreator = - classCreator.getMethodCreator(MethodDescriptor.ofConstructor(classCreator.getClassName())); - constructorCreator.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructorCreator.getThis()); - constructorCreator.writeInstanceField(delegateField, constructorCreator.getThis(), - constructorCreator.invokeInterfaceMethod(MethodDescriptor.ofMethod(Supplier.class, "get", Object.class), - constructorCreator.readStaticField(supplierField))); - constructorCreator.returnValue(constructorCreator.getThis()); - - for (Method method : interfaceMethods) { - MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.ofMethod(method)); - ResultHandle pythonProxy = methodCreator.readInstanceField(delegateField, methodCreator.getThis()); - ResultHandle[] args = new ResultHandle[method.getParameterCount()]; - for (int i = 0; i < args.length; i++) { - args[i] = methodCreator.getMethodParam(i); - } - ResultHandle result = methodCreator.invokeInterfaceMethod( - MethodDescriptor.ofMethod(method), - pythonProxy, args); - methodCreator.returnValue(result); - } - } catch (Exception e) { - throw new IllegalStateException(e); - } - writeClassOutput(classNameToBytecode, className, classBytecodeHolder.get()); - try { - // Now that the class created, we need to set it static field to the supplier of the delegate - Class out = (Class) asmClassLoader.loadClass(className); - out.getField(supplierField.getName()).set(null, delegateSupplier); - return out; - } catch (Exception e) { - throw new IllegalStateException( - "Impossible State: the class (" + className + ") should exists since it was just created"); - } - } - - /** - * Creates a class that looks like this: - * - * class PythonConstraintProvider implements ConstraintProvider { - * public static Function defineConstraintsImpl; - * - * @Override - * public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { - * return defineConstraintsImpl.apply(constraintFactory); - * } - * } - * - * @param className The simple name of the generated class - * @param defineConstraintsImpl The Python function that return the list of constraints - * @return never null - */ - @SuppressWarnings("unused") - public static Class defineConstraintProviderClass(String className, - ConstraintProvider defineConstraintsImpl) { - return defineWrapperFunction(className, ConstraintProvider.class, defineConstraintsImpl); - } - - /** - * Creates a class that looks like this: - * - * class PythonEasyScoreCalculator implements EasyScoreCalculator { - * public static EasyScoreCalculator easyScoreCalculatorImpl; - * - * @Override - * public Score calculateScore(Solution solution) { - * return easyScoreCalculatorImpl.calculateScore(solution); - * } - * } - * - * @param className The simple name of the generated class - * @param easyScoreCalculatorImpl The Python function that return the score for the solution - * @return never null - */ - @SuppressWarnings("unused") - public static Class defineEasyScoreCalculatorClass(String className, - EasyScoreCalculator easyScoreCalculatorImpl) { - return defineWrapperFunction(className, EasyScoreCalculator.class, easyScoreCalculatorImpl); - } - - /** - * Creates a class that looks like this: - * - * class PythonIncrementalScoreCalculator implements IncrementalScoreCalculator { - * public static Supplier<IncrementalScoreCalculator> supplier; - * public final IncrementalScoreCalculator delegate; - * - * public PythonIncrementalScoreCalculator() { - * delegate = supplier.get(); - * } - * - * @Override - * public Score calculateScore(Solution solution) { - * return delegate.calculateScore(solution); - * } - * - * ... - * } - * - * @param className The simple name of the generated class - * @param incrementalScoreCalculatorSupplier A supplier that returns a new instance of the incremental score calculator on - * each call - * @return never null - */ - @SuppressWarnings("unused") - public static Class defineIncrementalScoreCalculatorClass(String className, - Supplier incrementalScoreCalculatorSupplier, - boolean constraintMatchAware) { - if (constraintMatchAware) { - return defineWrapperClass(className, ConstraintMatchAwareIncrementalScoreCalculator.class, - (Supplier) incrementalScoreCalculatorSupplier); - } - return defineWrapperClass(className, IncrementalScoreCalculator.class, incrementalScoreCalculatorSupplier); - } - - /** - * Creates a class that looks like this: - * - * class PythonVariableListener implements VariableListener { - * public static Supplier<VariableListener> supplier; - * public final VariableListener delegate; - * - * public PythonVariableListener() { - * delegate = supplier.get(); - * } - * - * public void afterVariableChange(scoreDirector, entity) { - * delegate.afterVariableChange(scoreDirector, entity); - * } - * ... - * } - * - * @param className The simple name of the generated class - * @param variableListenerSupplier A supplier that returns a new instance of the variable listener on - * each call - * @return never null - */ - @SuppressWarnings("unused") - public static Class defineVariableListenerClass(String className, - Supplier variableListenerSupplier) { - return defineWrapperClass(className, VariableListener.class, variableListenerSupplier); - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonDelegateAppender.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonDelegateAppender.java deleted file mode 100644 index 84b9eb49..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonDelegateAppender.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.timefold.solver.python.logging; - -import java.util.function.Consumer; - -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; - -public class PythonDelegateAppender extends AppenderBase { - private static Consumer logEventConsumer; - - public static void setLogEventConsumer(Consumer logEventConsumer) { - PythonDelegateAppender.logEventConsumer = logEventConsumer; - } - - @Override - protected void append(ILoggingEvent eventObject) { - logEventConsumer.accept( - new PythonLoggingEvent( - PythonLogLevel.fromJavaLevel(eventObject.getLevel()), - eventObject.getFormattedMessage())); - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLogLevel.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLogLevel.java deleted file mode 100644 index 19dd3f0f..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLogLevel.java +++ /dev/null @@ -1,58 +0,0 @@ -package ai.timefold.solver.python.logging; - -import ch.qos.logback.classic.Level; - -public enum PythonLogLevel { - CRITICAL(50, Level.ERROR), - ERROR(40, Level.ERROR), - WARNING(30, Level.WARN), - INFO(20, Level.INFO), - DEBUG(10, Level.DEBUG), - TRACE(5, Level.TRACE), - NOTSET(0, Level.INFO); - - final int pythonLevelNumber; - final Level javaLogLevel; - - PythonLogLevel(int pythonLevelNumber, Level javaLogLevel) { - this.pythonLevelNumber = pythonLevelNumber; - this.javaLogLevel = javaLogLevel; - } - - public int getPythonLevelNumber() { - return pythonLevelNumber; - } - - public Level getJavaLogLevel() { - return javaLogLevel; - } - - public static PythonLogLevel fromJavaLevel(Level level) { - // Check INFO and ERROR first, since they have multiple corresponding - // Python levels - if (level.equals(Level.INFO)) { - return INFO; - } else if (level.equals(Level.ERROR)) { - return ERROR; - } else { - int levelNumber = level.toInt(); - for (PythonLogLevel pythonLogLevel : PythonLogLevel.values()) { - if (pythonLogLevel.getJavaLogLevel().toInt() == levelNumber) { - return pythonLogLevel; - } - } - throw new IllegalStateException("Unmatched log level (" + level + ") with level number (" + level.toInt() + ")."); - } - } - - public static PythonLogLevel fromPythonLevelNumber(int levelNumber) { - PythonLogLevel bestMatch = PythonLogLevel.CRITICAL; - int bestMatchLevelNumber = 50; - for (PythonLogLevel pythonLogLevel : PythonLogLevel.values()) { - if (pythonLogLevel.pythonLevelNumber >= levelNumber && pythonLogLevel.pythonLevelNumber < bestMatchLevelNumber) { - bestMatch = pythonLogLevel; - } - } - return bestMatch; - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingEvent.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingEvent.java deleted file mode 100644 index 8720ddea..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package ai.timefold.solver.python.logging; - -public record PythonLoggingEvent(PythonLogLevel level, String message) { -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingToLogbackAdapter.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingToLogbackAdapter.java deleted file mode 100644 index a3dd4c8d..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/logging/PythonLoggingToLogbackAdapter.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.timefold.solver.python.logging; - -import org.slf4j.LoggerFactory; - -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; - -public class PythonLoggingToLogbackAdapter { - private static final Logger LOGGER = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("ai.timefold"); - - public static void setLevel(int logLevel) { - PythonLogLevel pythonLogLevel = PythonLogLevel.fromPythonLevelNumber(logLevel); - LOGGER.setLevel(pythonLogLevel.getJavaLogLevel()); - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMapping.java deleted file mode 100644 index 741be6ae..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,88 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore; - -public final class BendableDecimalScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoresField; - private final Field softScoresField; - - public BendableDecimalScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoresField = clazz.getField("hard_scores"); - softScoresField = clazz.getField("soft_scores"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return BendableBigDecimalScore.class; - } - - private static PythonLikeTuple toPythonList(BigDecimal[] scores) { - PythonLikeTuple out = new PythonLikeTuple<>(); - for (var score : scores) { - out.add(new PythonDecimal(score)); - } - return out; - } - - @Override - public PythonLikeObject toPythonObject(BendableBigDecimalScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoresField.set(instance, toPythonList(javaObject.hardScores())); - softScoresField.set(instance, toPythonList(javaObject.softScores())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public BendableBigDecimalScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScoreTuple = ((PythonLikeTuple) hardScoresField.get(pythonObject)); - var softScoreTuple = ((PythonLikeTuple) softScoresField.get(pythonObject)); - BigDecimal[] hardScores = new BigDecimal[hardScoreTuple.size()]; - BigDecimal[] softScores = new BigDecimal[softScoreTuple.size()]; - for (int i = 0; i < hardScores.length; i++) { - hardScores[i] = ((PythonDecimal) hardScoreTuple.get(i)).value; - } - for (int i = 0; i < softScores.length; i++) { - softScores[i] = ((PythonDecimal) softScoreTuple.get(i)).value; - } - if (initScore == 0) { - return BendableBigDecimalScore.of(hardScores, softScores); - } else { - return BendableBigDecimalScore.ofUninitialized(initScore, hardScores, softScores); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java deleted file mode 100644 index 2f05fdc8..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,85 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore; - -public final class BendableScorePythonJavaTypeMapping implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoresField; - private final Field softScoresField; - - public BendableScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoresField = clazz.getField("hard_scores"); - softScoresField = clazz.getField("soft_scores"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return BendableLongScore.class; - } - - private static PythonLikeTuple toPythonList(long[] scores) { - PythonLikeTuple out = new PythonLikeTuple<>(); - for (long score : scores) { - out.add(PythonInteger.valueOf(score)); - } - return out; - } - - @Override - public PythonLikeObject toPythonObject(BendableLongScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoresField.set(instance, toPythonList(javaObject.hardScores())); - softScoresField.set(instance, toPythonList(javaObject.softScores())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public BendableLongScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScoreTuple = ((PythonLikeTuple) hardScoresField.get(pythonObject)); - var softScoreTuple = ((PythonLikeTuple) softScoresField.get(pythonObject)); - long[] hardScores = new long[hardScoreTuple.size()]; - long[] softScores = new long[softScoreTuple.size()]; - for (int i = 0; i < hardScores.length; i++) { - hardScores[i] = ((PythonInteger) hardScoreTuple.get(i)).value.longValue(); - } - for (int i = 0; i < softScores.length; i++) { - softScores[i] = ((PythonInteger) softScoreTuple.get(i)).value.longValue(); - } - if (initScore == 0) { - return BendableLongScore.of(hardScores, softScores); - } else { - return BendableLongScore.ofUninitialized(initScore, hardScores, softScores); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMapping.java deleted file mode 100644 index 2edafd72..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,74 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal.HardMediumSoftBigDecimalScore; - -public final class HardMediumSoftDecimalScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoreField; - private final Field mediumScoreField; - private final Field softScoreField; - - public HardMediumSoftDecimalScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoreField = clazz.getField("hard_score"); - mediumScoreField = clazz.getField("medium_score"); - softScoreField = clazz.getField("soft_score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return HardMediumSoftBigDecimalScore.class; - } - - @Override - public PythonLikeObject toPythonObject(HardMediumSoftBigDecimalScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoreField.set(instance, new PythonDecimal(javaObject.hardScore())); - mediumScoreField.set(instance, new PythonDecimal(javaObject.mediumScore())); - softScoreField.set(instance, new PythonDecimal(javaObject.softScore())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public HardMediumSoftBigDecimalScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonDecimal) hardScoreField.get(pythonObject)).value; - var mediumScore = ((PythonDecimal) mediumScoreField.get(pythonObject)).value; - var softScore = ((PythonDecimal) softScoreField.get(pythonObject)).value; - if (initScore == 0) { - return HardMediumSoftBigDecimalScore.of(hardScore, mediumScore, softScore); - } else { - return HardMediumSoftBigDecimalScore.ofUninitialized(initScore, hardScore, mediumScore, softScore); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java deleted file mode 100644 index 4f73c357..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,73 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore; - -public final class HardMediumSoftScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoreField; - private final Field mediumScoreField; - private final Field softScoreField; - - public HardMediumSoftScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoreField = clazz.getField("hard_score"); - mediumScoreField = clazz.getField("medium_score"); - softScoreField = clazz.getField("soft_score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return HardMediumSoftLongScore.class; - } - - @Override - public PythonLikeObject toPythonObject(HardMediumSoftLongScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoreField.set(instance, PythonInteger.valueOf(javaObject.hardScore())); - mediumScoreField.set(instance, PythonInteger.valueOf(javaObject.mediumScore())); - softScoreField.set(instance, PythonInteger.valueOf(javaObject.softScore())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public HardMediumSoftLongScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.longValue(); - var mediumScore = ((PythonInteger) mediumScoreField.get(pythonObject)).value.longValue(); - var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.longValue(); - if (initScore == 0) { - return HardMediumSoftLongScore.of(hardScore, mediumScore, softScore); - } else { - return HardMediumSoftLongScore.ofUninitialized(initScore, hardScore, mediumScore, softScore); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMapping.java deleted file mode 100644 index af0ffd85..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,70 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore; - -public final class HardSoftDecimalScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoreField; - private final Field softScoreField; - - public HardSoftDecimalScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoreField = clazz.getField("hard_score"); - softScoreField = clazz.getField("soft_score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return HardSoftBigDecimalScore.class; - } - - @Override - public PythonLikeObject toPythonObject(HardSoftBigDecimalScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoreField.set(instance, new PythonDecimal(javaObject.hardScore())); - softScoreField.set(instance, new PythonDecimal(javaObject.softScore())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public HardSoftBigDecimalScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonDecimal) hardScoreField.get(pythonObject)).value; - var softScore = ((PythonDecimal) softScoreField.get(pythonObject)).value; - if (initScore == 0) { - return HardSoftBigDecimalScore.of(hardScore, softScore); - } else { - return HardSoftBigDecimalScore.ofUninitialized(initScore, hardScore, softScore); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java deleted file mode 100644 index 15bc998c..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,68 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore; - -public final class HardSoftScorePythonJavaTypeMapping implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field hardScoreField; - private final Field softScoreField; - - public HardSoftScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - hardScoreField = clazz.getField("hard_score"); - softScoreField = clazz.getField("soft_score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return HardSoftLongScore.class; - } - - @Override - public PythonLikeObject toPythonObject(HardSoftLongScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - hardScoreField.set(instance, PythonInteger.valueOf(javaObject.hardScore())); - softScoreField.set(instance, PythonInteger.valueOf(javaObject.softScore())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public HardSoftLongScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.longValue(); - var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.longValue(); - if (initScore == 0) { - return HardSoftLongScore.of(hardScore, softScore); - } else { - return HardSoftLongScore.ofUninitialized(initScore, hardScore, softScore); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMapping.java deleted file mode 100644 index 4f8cf3f7..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,66 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore; - -public final class SimpleDecimalScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field scoreField; - - public SimpleDecimalScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - scoreField = clazz.getField("score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return SimpleBigDecimalScore.class; - } - - @Override - public PythonLikeObject toPythonObject(SimpleBigDecimalScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - scoreField.set(instance, new PythonDecimal(javaObject.score())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public SimpleBigDecimalScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var score = ((PythonDecimal) scoreField.get(pythonObject)).value; - if (initScore == 0) { - return SimpleBigDecimalScore.of(score); - } else { - return SimpleBigDecimalScore.ofUninitialized(initScore, score); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java deleted file mode 100644 index bff42450..00000000 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore; - -public final class SimpleScorePythonJavaTypeMapping implements PythonJavaTypeMapping { - private final PythonLikeType type; - private final Constructor constructor; - private final Field initScoreField; - private final Field scoreField; - - public SimpleScorePythonJavaTypeMapping(PythonLikeType type) - throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { - this.type = type; - Class clazz = type.getJavaClass(); - constructor = clazz.getConstructor(); - initScoreField = clazz.getField("init_score"); - scoreField = clazz.getField("score"); - } - - @Override - public PythonLikeType getPythonType() { - return type; - } - - @Override - public Class getJavaType() { - return SimpleLongScore.class; - } - - @Override - public PythonLikeObject toPythonObject(SimpleLongScore javaObject) { - try { - var instance = constructor.newInstance(); - initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); - scoreField.set(instance, PythonInteger.valueOf(javaObject.score())); - return (PythonLikeObject) instance; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - @Override - public SimpleLongScore toJavaObject(PythonLikeObject pythonObject) { - try { - var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var score = ((PythonInteger) scoreField.get(pythonObject)).value.longValue(); - if (initScore == 0) { - return SimpleLongScore.of(score); - } else { - return SimpleLongScore.ofUninitialized(initScore, score); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/timefold-solver-python-core/src/main/python/__init__.py b/timefold-solver-python-core/src/main/python/__init__.py deleted file mode 100644 index 6219f152..00000000 --- a/timefold-solver-python-core/src/main/python/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -`Timefold Solver `_ is a lightweight, -embeddable constraint satisfaction engine which optimizes planning problems. - -It solves use cases such as: - - - Employee shift rostering: timetabling nurses, repairmen, ... - - - Vehicle routing: planning vehicle routes for moving freight and/or passengers through - multiple destinations using known mapping tools ... - - - Agenda scheduling: scheduling meetings, appointments, maintenance jobs, advertisements, ... - - -Planning problems are defined using Python classes and functions. - -Examples --------- ->>> from timefold.solver import Solver, SolverFactory ->>> from timefold.solver.config import (SolverConfig, ScoreDirectorFactoryConfig, -... TerminationConfig, Duration) ->>> from domain import Timetable, Lesson, generate_problem ->>> from constraints import my_constraints -... ->>> solver_config = SolverConfig(solution_class=Timetable, entity_class_list=[Lesson], -... score_director_factory_config=ScoreDirectorFactoryConfig( -... constraint_provider_function=my_constraints -... ), -... termination_config=TerminationConfig( -... spent_limit=Duration(seconds=30)) -... ) ->>> solver = SolverFactory.create(solver_config).build_solver() ->>> problem = generate_problem() ->>> solution = solver.solve(problem) - -See Also --------- -:mod:`timefold.solver.config` -:mod:`timefold.solver.domain` -:mod:`timefold.solver.score` -:mod:`timefold.solver.test` -""" -from ._problem_change import * -from ._solution_manager import * -from ._solver import * -from ._solver_factory import * -from ._solver_manager import * - -import timefold.solver.config as config -import timefold.solver.domain as domain -import timefold.solver.heuristic as heuristic -import timefold.solver.score as score -import timefold.solver.test as test - -from ._timefold_java_interop import init, set_class_output_directory diff --git a/timefold-solver-python-core/src/main/python/_future.py b/timefold-solver-python-core/src/main/python/_future.py deleted file mode 100644 index 16bc243a..00000000 --- a/timefold-solver-python-core/src/main/python/_future.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Awaitable, TypeVar, TYPE_CHECKING - -if TYPE_CHECKING: - from java.util.concurrent import Future as JavaFuture - - -Result = TypeVar('Result') - - -class JavaFutureAwaitable(Awaitable[Result]): - _future: 'JavaFuture[Result]' - - def __init__(self, future: 'JavaFuture[Result]') -> None: - self._future = future - - def __await__(self) -> Result: - return self - - def __iter__(self): - return self - - def __next__(self): - raise StopIteration(self._future.get()) - - -def wrap_future(future: 'JavaFuture[Result]') -> Awaitable[Result]: - return JavaFutureAwaitable(future) - - -__all__ = ['wrap_future'] diff --git a/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py b/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py deleted file mode 100644 index 638a342d..00000000 --- a/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py +++ /dev/null @@ -1,351 +0,0 @@ -from jpype import JProxy, JImplements, JOverride, JConversion -from jpype.types import * -from types import FunctionType -from typing import TYPE_CHECKING -from decimal import Decimal -import timefold.solver._timefold_java_interop as _timefold_java_interop - -if TYPE_CHECKING: - from .score._score import Score - - -@JImplements('ai.timefold.solver.core.api.score.stream.ConstraintProvider', deferred=True) -class ConstraintProviderFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def defineConstraints(self, constraint_factory): - return self.delegate(constraint_factory) - - -@JImplements('java.util.function.Consumer', deferred=True) -class PythonConsumer: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def accept(self, item): - return self.delegate(item) - - -@JImplements('java.util.function.Supplier', deferred=True) -class PythonSupplier: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def get(self): - return self.delegate() - - -@JImplements('java.util.function.Function', deferred=True) -class PythonFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def apply(self, argument): - return self.delegate(argument) - - -@JImplements('java.util.function.BiFunction', deferred=True) -class PythonBiFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def apply(self, argument1, argument2): - return self.delegate(argument1, argument2) - - -@JImplements('ai.timefold.solver.core.api.function.TriFunction', deferred=True) -class PythonTriFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def apply(self, argument1, argument2, argument3): - return self.delegate(argument1, argument2, argument3) - - -@JImplements('ai.timefold.solver.core.api.function.QuadFunction', deferred=True) -class PythonQuadFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def apply(self, argument1, argument2, argument3, argument4): - return self.delegate(argument1, argument2, argument3, argument4) - - -@JImplements('ai.timefold.solver.core.api.function.PentaFunction', deferred=True) -class PythonPentaFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def apply(self, argument1, argument2, argument3, argument4, argument5): - return self.delegate(argument1, argument2, argument3, argument4, argument5) - - -@JImplements('java.util.function.ToIntFunction', deferred=True) -class PythonToIntFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsInt(self, argument): - return JInt(self.delegate(argument)) - - -@JImplements('java.util.function.ToIntBiFunction', deferred=True) -class PythonToIntBiFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsInt(self, argument1, argument2): - return JInt(self.delegate(argument1, argument2)) - - -@JImplements('ai.timefold.solver.core.api.function.ToIntTriFunction', deferred=True) -class PythonToIntTriFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsInt(self, argument1, argument2, argument3): - return JInt(self.delegate(argument1, argument2, argument3)) - - -@JImplements('ai.timefold.solver.core.api.function.ToIntQuadFunction', deferred=True) -class PythonToIntQuadFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsInt(self, argument1, argument2, argument3, argument4): - return JInt(self.delegate(argument1, argument2, argument3, argument4)) - - -@JImplements('ai.timefold.solver.core.api.function.ToIntPentaFunction', deferred=True) -class PythonToIntPentaFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsInt(self, argument1, argument2, argument3, argument4, argument5): - return JInt(self.delegate(argument1, argument2, argument3, argument4, argument5)) - - -@JImplements('java.util.function.ToLongFunction', deferred=True) -class PythonToLongFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsLong(self, argument): - return JLong(self.delegate(argument)) - - -@JImplements('java.util.function.ToLongBiFunction', deferred=True) -class PythonToLongBiFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsLong(self, argument1, argument2): - return JLong(self.delegate(argument1, argument2)) - - -@JImplements('ai.timefold.solver.core.api.function.ToLongTriFunction', deferred=True) -class PythonToLongTriFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsLong(self, argument1, argument2, argument3): - return JLong(self.delegate(argument1, argument2, argument3)) - - -@JImplements('ai.timefold.solver.core.api.function.ToLongQuadFunction', deferred=True) -class PythonToLongQuadFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsLong(self, argument1, argument2, argument3, argument4): - return JLong(self.delegate(argument1, argument2, argument3, argument4)) - - -@JImplements('ai.timefold.solver.core.api.function.ToLongPentaFunction', deferred=True) -class PythonToLongPentaFunction: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def applyAsLong(self, argument1, argument2, argument3, argument4, argument5): - return JLong(self.delegate(argument1, argument2, argument3, argument4, argument5)) - - - -@JImplements('java.util.function.Predicate', deferred=True) -class PythonPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument): - return self.delegate(argument) - - -@JImplements('java.util.function.BiPredicate', deferred=True) -class PythonBiPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2): - return self.delegate(argument1, argument2) - - -@JImplements('ai.timefold.solver.core.api.function.TriPredicate', deferred=True) -class PythonTriPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3): - return self.delegate(argument1, argument2, argument3) - - -@JImplements('ai.timefold.solver.core.api.function.QuadPredicate', deferred=True) -class PythonQuadPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3, argument4): - return self.delegate(argument1, argument2, argument3, argument4) - - -@JImplements('ai.timefold.solver.core.api.function.PentaPredicate', deferred=True) -class PythonPentaPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3, argument4, argument5): - return self.delegate(argument1, argument2, argument3, argument4, argument5) - - -def to_python_score(score) -> 'Score': - if isinstance(score, _timefold_java_interop._java_score_mapping_dict['SimpleScore']): - return _timefold_java_interop._python_score_mapping_dict['SimpleScore'](score.score(), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['HardSoftScore']): - return _timefold_java_interop._python_score_mapping_dict['HardSoftScore'](score.hardScore(), - score.softScore(), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['HardMediumSoftScore']): - return _timefold_java_interop._python_score_mapping_dict['HardMediumSoftScore'](score.hardScore(), - score.mediumScore(), - score.softScore(), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['BendableScore']): - return _timefold_java_interop._python_score_mapping_dict['BendableScore'](score.hardScores(), - score.softScores(), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['SimpleDecimalScore']): - return _timefold_java_interop._python_score_mapping_dict['SimpleDecimalScore'](Decimal(score.score().toPlainString()), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['HardSoftDecimalScore']): - return _timefold_java_interop._python_score_mapping_dict['HardSoftDecimalScore'](Decimal(score.hardScore().toPlainString()), - Decimal(score.softScore().toPlainString()), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['HardMediumSoftDecimalScore']): - return _timefold_java_interop._python_score_mapping_dict['HardMediumSoftDecimalScore'](Decimal(score.hardScore().toPlainString()), - Decimal(score.mediumScore().toPlainString()), - Decimal(score.softScore().toPlainString()), - init_score=score.initScore()) - elif isinstance(score, _timefold_java_interop._java_score_mapping_dict['BendableDecimalScore']): - return _timefold_java_interop._python_score_mapping_dict['BendableDecimalScore']([Decimal(part.toPlainString()) - for part in score.hardScores() - ], - [Decimal(part.toPlainString()) - for part in score.softScores() - ], - init_score=score.initScore()) - else: - raise TypeError(f'Unexpected score type: {type(score)}') - - -# Function convertors -def _has_java_class(item): - if isinstance(item, (JObject, int, str, bool)): - return True - if hasattr(type(item), '_timefold_java_class'): - return True - return False - - -def _proxy(value): - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference # noqa - return JProxy(OpaquePythonReference, inst=value, convert=True) - - -def _convert_to_java_compatible_object(item): - from ai.timefold.solver.python import PythonComparable # noqa - if _has_java_class(item) or item is None: - return item - return PythonComparable(_proxy(item)) - - -@JConversion('java.lang.Class', exact=type) -def _convert_type_to_class(jcls, type_obj): - from ._timefold_java_interop import get_class - from java.lang import Object - out = get_class(type_obj) - if out == Object and type_obj != Object: - raise ValueError(f'Type {type_obj} does not have a Java class proxy. Maybe annotate it with ' - f'@problem_fact, @planning_entity, or @planning_solution?') - - return out - - -@JConversion('java.lang.Class', exact=FunctionType) -def _convert_function_to_class(jcls, function_obj): - from ._timefold_java_interop import get_class - from java.lang import Object - out = get_class(function_obj) - if out == Object and function_obj != Object: - raise ValueError(f'Function {function_obj} does not have a Java class proxy. Maybe annotate it with ' - f'@constraint_provider?') - - return out - - -# Jpype convert int to primitive, but not to their wrappers, so add implicit conversion to wrappers -@JConversion('java.lang.Integer', exact=int) -def _convert_to_integer(jcls, obj): - from java.lang import Integer - return Integer @ JInt(obj) - - -@JConversion('java.lang.Long', exact=int) -def _convert_to_long(jcls, obj): - from java.lang import Long - return Long @ JLong(obj) - - -@JConversion('java.lang.Short', exact=int) -def _convert_to_short(jcls, obj): - from java.lang import Short - return Short @ JShort(obj) - - -@JConversion('java.lang.Byte', exact=int) -def _convert_to_byte(jcls, obj): - from java.lang import Byte - return Byte @ JByte(obj) diff --git a/timefold-solver-python-core/src/main/python/_problem_change.py b/timefold-solver-python-core/src/main/python/_problem_change.py deleted file mode 100644 index f52b5dab..00000000 --- a/timefold-solver-python-core/src/main/python/_problem_change.py +++ /dev/null @@ -1,295 +0,0 @@ -from abc import ABC, abstractmethod -from typing import TypeVar, Optional, Callable, TYPE_CHECKING, Generic -from types import FunctionType -from _jpyinterpreter import (convert_to_java_python_like_object, - unwrap_python_like_object, - update_python_object_from_java, - translate_python_bytecode_to_java_bytecode) -from jpype import JOverride, JImplements - -if TYPE_CHECKING: - from ai.timefold.solver.core.api.solver.change import (ProblemChangeDirector as _ProblemChangeDirector) - -Solution_ = TypeVar('Solution_') - - -class ProblemChangeDirector: - """ - Allows external changes to the working solution. - If the changes are not applied through the `ProblemChangeDirector`, - both internal and custom variable listeners are never notified about them, - resulting to inconsistencies in the working solution. - Should be used only from a `ProblemChange` implementation. - - To see an example implementation, please refer to the `ProblemChange` docstring. - """ - _delegate: '_ProblemChangeDirector' - _java_solution: Solution_ - _python_solution: Solution_ - - Entity = TypeVar('Entity') - ProblemFact = TypeVar('ProblemFact') - EntityOrProblemFact = TypeVar('EntityOrProblemFact') - - def __init__(self, delegate: '_ProblemChangeDirector', - java_solution: Solution_, - python_solution: Solution_): - self._delegate = delegate - self._java_solution = java_solution - self._python_solution = python_solution - - def _replace_solution_in_callable(self, callable: Callable): - if isinstance(callable, FunctionType): - if callable.__closure__ is not None: - for cell in callable.__closure__: - if cell.cell_contents is self._python_solution: - cell.cell_contents = self._java_solution - return callable - - def add_entity(self, entity: Entity, modifier: Callable[[Entity], None]) -> None: - """ - Add a new ``planning_entity`` instance into the ``working solution``. - - Parameters - ---------- - entity : Entity - The ``planning_entity`` instance - modifier : Callable[[Entity], None] - A callable that adds the entity to the working solution. - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.addEntity(convert_to_java_python_like_object(entity), converted_modifier) - update_python_object_from_java(self._java_solution) - - def add_problem_fact(self, fact: ProblemFact, modifier: Callable[[ProblemFact], None]) -> None: - """ - Add a new problem fact instance into the ``working solution``. - - Parameters - ---------- - fact : ProblemFact - The problem fact instance - modifier : Callable[[ProblemFact], None] - A callable that adds the fact to the working solution. - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.addProblemFact(convert_to_java_python_like_object(fact), converted_modifier) - update_python_object_from_java(self._java_solution) - - def change_problem_property(self, problem_fact_or_entity: EntityOrProblemFact, - modifier: Callable[[EntityOrProblemFact], None]) -> None: - """ - Change a property of either a ``planning_entity`` or a problem fact. - Translates the entity or the problem fact to its working solution counterpart - by performing a lookup as defined by `lookup_working_object_or_fail`. - - Parameters - ---------- - problem_fact_or_entity : EntityOrProblemFact - The ``planning_entity`` or problem fact instance - modifier : Callable[[EntityOrProblemFact], None] - Updates the property of the ``planning_entity`` or the problem fact - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.changeProblemProperty(convert_to_java_python_like_object(problem_fact_or_entity), - converted_modifier) - update_python_object_from_java(self._java_solution) - - def change_variable(self, entity: Entity, variable: str, - modifier: Callable[[Entity], None]) -> None: - """ - Change a ``PlanningVariable`` value of a ``planning_entity``. - Translates the entity to a working planning entity - by performing a lookup as defined by `lookup_working_object_or_fail`. - - Parameters - ---------- - entity : Entity - The ``planning_entity`` instance - variable : str - Name of the ``PlanningVariable`` - modifier : Callable[[Entity], None] - Updates the value of the ``PlanningVariable`` inside the ``planning_entity`` - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.changeVariable(convert_to_java_python_like_object(entity), variable, converted_modifier) - update_python_object_from_java(self._java_solution) - - def lookup_working_object(self, external_object: EntityOrProblemFact) -> Optional[EntityOrProblemFact]: - """ - As defined by `lookup_working_object_or_fail`, - but doesn't fail fast if no working object was ever added for the `external_object`. - It's recommended to use `lookup_working_object_or_fail` instead. - - Parameters - ---------- - external_object : EntityOrProblemFact - The entity or fact instance to lookup. - Can be ``None``. - - Returns - ------- - EntityOrProblemFact | None - None if there is no working object for the `external_object`, the looked up object - otherwise. - - Raises - ------ - If it cannot be looked up or if the `external_object`'s class is not supported. - """ - out = self._delegate.lookUpWorkingObject(convert_to_java_python_like_object(external_object)).orElse(None) - if out is None: - return None - return unwrap_python_like_object(out) - - def lookup_working_object_or_fail(self, external_object: EntityOrProblemFact) -> EntityOrProblemFact: - """ - Translate an entity or fact instance (often from another Thread) - to this `ProblemChangeDirector`'s internal working instance. - - Matches entities by ``PlanningId``. - - Parameters - ---------- - external_object : EntityOrProblemFact - The entity or fact instance to lookup. - Can be ``None``. - - Raises - ------ - If there is no working object for `external_object`, - if it cannot be looked up or if the `external_object`'s class is not supported. - """ - return unwrap_python_like_object(self._delegate.lookUpWorkingObjectOrFail(external_object)) - - def remove_entity(self, entity: Entity, modifier: Callable[[Entity], None]) -> None: - """ - Remove an existing `planning_entity` instance from the ``working solution``. - Translates the entity to its working solution counterpart - by performing a lookup as defined by `lookup_working_object_or_fail`. - - Parameters - ---------- - entity : Entity - The ``planning_entity`` instance - modifier : Callable[[Entity], None] - Removes the working entity from the ``working solution``. - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.removeEntity(convert_to_java_python_like_object(entity), converted_modifier) - update_python_object_from_java(self._java_solution) - - def remove_problem_fact(self, fact: ProblemFact, modifier: Callable[[ProblemFact], None]) -> None: - """ - Remove an existing problem fact instance from the ``working solution``. - Translates the problem fact to its working solution counterpart - by performing a lookup as defined by `lookup_working_object_or_fail`. - - Parameters - ---------- - fact : ProblemFact - The problem fact instance - modifier : Callable[[ProblemFact], None] - Removes the working problem fact from the ``working solution``. - """ - from java.util.function import Consumer - converted_modifier = translate_python_bytecode_to_java_bytecode(self._replace_solution_in_callable(modifier), - Consumer) - self._delegate.removeProblemFact(convert_to_java_python_like_object(fact), converted_modifier) - update_python_object_from_java(self._java_solution) - - def update_shadow_variables(self) -> None: - """ - Calls variable listeners on the external changes submitted so far. - This happens automatically after the entire `ProblemChange` has been processed, - but this method allows the user to specifically request it in the middle of the `ProblemChange`. - """ - self._delegate.updateShadowVariables() - update_python_object_from_java(self._java_solution) - - -class ProblemChange(Generic[Solution_], ABC): - """ - A `ProblemChange` represents a change in one or more planning entities or problem facts of a `planning_solution`. - - The Solver checks the presence of waiting problem changes after every Move evaluation. - If there are waiting problem changes, the Solver: - - 1. clones the last best solution and sets the clone as the new working solution - 2. applies every problem change keeping the order in which problem changes have been submitted; after every problem change, variable listeners are triggered - 3. calculates the score and makes the updated working solution the new best solution; note that this solution is not published via the ai. timefold. solver. core. api. solver. event. BestSolutionChangedEvent, as it hasn't been initialized yet - 4. restarts solving to fill potential uninitialized planning entities - - Note that the Solver clones a `planning_solution` at will. - Any change must be done on the problem facts and planning entities referenced by the `planning_solution`. - - Examples - -------- - An example implementation, based on the Cloud balancing problem, looks as follows: - >>> from timefold.solver import ProblemChange - >>> from domain import CloudBalance, CloudComputer - >>> - >>> class DeleteComputerProblemChange(ProblemChange[CloudBalance]): - ... computer: CloudComputer - ... - ... def __init__(self, computer: CloudComputer): - ... self.computer = computer - ... - ... def do_change(self, cloud_balance: CloudBalance, problem_change_director: ProblemChangeDirector): - ... working_computer = problem_change_director.lookup_working_object_or_fail(self.computer) - ... # First remove the problem fact from all planning entities that use it - ... for process in cloud_balance.process_list: - ... if process.computer == working_computer: - ... problem_change_director.change_variable(process, "computer", - ... lambda working_process: setattr(working_process, - ... 'computer', None)) - ... # A SolutionCloner does not clone problem fact lists (such as computer_list), only entity lists. - ... # Shallow clone the computer_list so only the working solution is affected. - ... computer_list = cloud_balance.computer_list.copy() - ... cloud_balance.computer_list = computer_list - ... # Remove the problem fact itself - ... problem_change_director.remove_problem_fact(working_computer, computer_list.remove) - """ - @abstractmethod - def do_change(self, working_solution: Solution_, problem_change_director: ProblemChangeDirector) -> None: - """ - Do the change on the `planning_solution`. - Every modification to the `planning_solution` must be done via the `ProblemChangeDirector`, - otherwise the Score calculation will be corrupted. - - Parameters - ---------- - working_solution : Solution_ - the working solution which contains the problem facts (and planning entities) to change - problem_change_director : ProblemChangeDirector - `ProblemChangeDirector` to perform the change through - """ - ... - - -@JImplements('ai.timefold.solver.core.api.solver.change.ProblemChange', deferred=True) -class ProblemChangeWrapper: - _delegate: ProblemChange - - def __init__(self, delegate: ProblemChange): - self._delegate = delegate - - @JOverride - def doChange(self, working_solution, problem_change_director: '_ProblemChangeDirector') -> None: - wrapped_problem_change_director = ProblemChangeDirector(problem_change_director, - working_solution, - unwrap_python_like_object(working_solution)) - self._delegate.do_change(working_solution, wrapped_problem_change_director) - - -__all__ = ['ProblemChange', 'ProblemChangeDirector'] diff --git a/timefold-solver-python-core/src/main/python/_solution_manager.py b/timefold-solver-python-core/src/main/python/_solution_manager.py deleted file mode 100644 index cbe24696..00000000 --- a/timefold-solver-python-core/src/main/python/_solution_manager.py +++ /dev/null @@ -1,117 +0,0 @@ -from ._solver_factory import SolverFactory -from ._solver_manager import SolverManager -from ._jpype_type_conversions import to_python_score -from .score import ScoreAnalysis, ScoreExplanation - -from typing import TypeVar, Generic, TYPE_CHECKING, Any - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from .score import Score - from ai.timefold.solver.core.api.solver import SolutionManager as _JavaSolutionManager - -Solution_ = TypeVar('Solution_') -ProblemId_ = TypeVar('ProblemId_') -Score_ = TypeVar('Score_', bound='Score') -Justification_ = TypeVar('Justification_', bound='ConstraintJustification') - - -class SolutionManager(Generic[Solution_]): - """ - A stateless service to help calculate `Score`, `ConstraintMatchTotal`, `Indictment`, etc. - To create a `SolutionManager` instance, use `create`. - """ - _delegate: '_JavaSolutionManager' - - def __init__(self, delegate: '_JavaSolutionManager'): - self._delegate = delegate - - @staticmethod - def create(solver_factory: SolverFactory[Solution_] | SolverManager[Solution_, Any]) -> \ - 'SolutionManager[Solution_]': - """ - Uses a `SolverFactory` or `SolverManager` to build a SolutionManager. - - Parameters - ---------- - solver_factory : SolverFactory | SolverManager - - Returns - ------- - SolutionManager - A `SolutionManager` instance. - """ - from ai.timefold.solver.core.api.solver import SolutionManager as JavaSolutionManager - return SolutionManager(JavaSolutionManager.create(solver_factory._delegate)) - - def update(self, solution: Solution_, solution_update_policy=None) -> 'Score': - """ - Updates the given solution according to the `SolutionUpdatePolicy`. - - Parameters - ---------- - solution : Solution_ - The solution to explain - solution_update_policy - - Returns - ------- - Score - The score of the updated solution. - """ - # TODO handle solution_update_policy - from _jpyinterpreter import convert_to_java_python_like_object, update_python_object_from_java - java_solution = convert_to_java_python_like_object(solution) - out = self._delegate.update(java_solution) - update_python_object_from_java(java_solution) - return to_python_score(out) - - def analyze(self, solution: Solution_, score_analysis_fetch_policy=None, solution_update_policy=None) \ - -> 'ScoreAnalysis': - """ - Calculates and retrieves information about which constraints contributed to the solution's score. - This is a faster, JSON-friendly version of `explain`. - - Parameters - ---------- - solution : Solution_ - A fully initialized solution - score_analysis_fetch_policy - solution_update_policy - - Returns - ------- - ScoreAnalysis - The `ScoreAnalysis` corresponding to the given solution. - """ - # TODO handle policies - from _jpyinterpreter import convert_to_java_python_like_object - return ScoreAnalysis(self._delegate.analyze(convert_to_java_python_like_object(solution))) - - def explain(self, solution: Solution_, solution_update_policy=None) -> 'ScoreExplanation': - """ - Calculates and retrieves ConstraintMatchTotals and Indictments necessary for - describing the quality of a particular solution. - For a simplified, faster and JSON-friendly alternative, see `analyze`. - - Parameters - ---------- - solution - solution_update_policy - - Returns - ------- - ScoreExplanation - The `ScoreExplanation` corresponding to the given solution. - """ - # TODO handle policies - from _jpyinterpreter import convert_to_java_python_like_object - return ScoreExplanation(self._delegate.explain(convert_to_java_python_like_object(solution))) - - def recommend_fit(self, solution: Solution_, entity_or_element, proposition_function, - score_analysis_fetch_policy=None): - # TODO - raise NotImplementedError - - -__all__ = ['SolutionManager'] diff --git a/timefold-solver-python-core/src/main/python/_solver.py b/timefold-solver-python-core/src/main/python/_solver.py deleted file mode 100644 index fd3b6473..00000000 --- a/timefold-solver-python-core/src/main/python/_solver.py +++ /dev/null @@ -1,263 +0,0 @@ -from ._problem_change import ProblemChange, ProblemChangeWrapper -from ._timefold_java_interop import update_log_level -from ._jpype_type_conversions import to_python_score -from typing import TypeVar, TYPE_CHECKING, Generic, Callable -from datetime import timedelta -from jpype import JClass, JImplements, JOverride -from dataclasses import dataclass - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from .score import Score - from ai.timefold.solver.core.api.solver import Solver as _JavaSolver - -Solution_ = TypeVar('Solution_') - - -@dataclass -class BestSolutionChangedEvent(Generic[Solution_]): - """ - Delivered when the best solution changes during solving. - Delivered in the solver thread (which is the thread that calls `Solver.solve`). - - Attributes - ---------- - new_best_score: Score - Returns the Score of the `new_best_solution`. - This is useful for generic code, - which doesn't know the type of the `planning_solution` to retrieve the Score - from `new_best_solution` easily. - - new_best_solution : Solution_ - Note that: - - - In real-time planning, not all ProblemChanges might be processed: - check `is_every_problem_change_processed`. - - - This `planning_solution` might be uninitialized: check `Score.init_score`. - - - This `planning_solution` might be infeasible: check `Score.is_feasible`. - - is_every_problem_change_processed : bool - Checks if all scheduled ProblemChanges have been processed. - This method is thread-safe. - - time_spent: timedelta - The duration between starting solving and finding the current best solution. - """ - new_best_score: 'Score' - new_best_solution: Solution_ - is_every_problem_change_processed: bool - time_spent: timedelta - - -class Solver(Generic[Solution_]): - """ - A `Solver` solves a planning problem and returns the best solution found. - It's recommended to create a new `Solver` instance for each dataset. - - To create a `Solver`, use `SolverFactory.build_solver`. - To solve a planning problem, call `solve`. - To solve a planning problem without blocking the current thread, use `SolverManager` instead. - - These methods are not thread-safe and should be called from the same thread, - except for the methods that are explicitly marked as thread-safe. - Note that despite that solve is not thread-safe for clients of this class, - that method is free to do multithreading inside itself. - """ - _delegate: '_JavaSolver' - _solution_class: JClass - _has_event_listener: bool - _event_listener_list: list[Callable[[BestSolutionChangedEvent[Solution_]], None]] - - def __init__(self, delegate: '_JavaSolver', solution_class: JClass): - self._delegate = delegate - self._solution_class = solution_class - self._has_event_listener = False - self._event_listener_list = [] - - def solve(self, problem: Solution_): - """ - Solves the planning problem and returns the best solution encountered - (which might or might not be optimal, feasible or even initialized). - - It can take seconds, minutes, even hours or days before this method returns, - depending on the termination configuration. - To terminate a `Solver` early, call `terminate_early`. - - Parameters - ---------- - problem : Solution_ - A `planning_solution`, usually its planning variables are uninitialized - - Returns - ------- - Solution_ - The best solution encountered before terminating. - It can return the original, uninitialized `planning_solution` with a ``None`` `Score`. - """ - from java.lang import Exception as JavaException - from ai.timefold.jpyinterpreter.types.errors import PythonBaseException - from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object - java_problem = convert_to_java_python_like_object(problem) - if not self._solution_class.isInstance(java_problem): - raise ValueError( - f'The problem ({problem}) is not an instance of the @planning_solution class ({self._solution_class})' - ) - update_log_level() - try: - java_solution = self._delegate.solve(java_problem) - except PythonBaseException as e: - raise unwrap_python_like_object(e) - except JavaException as e: - raise RuntimeError(f'Solving failed due to an error: {e.getMessage()}.\n' - f'Java stack trace: {e.stacktrace()}') from e - return unwrap_python_like_object(java_solution) - - def is_solving(self) -> bool: - """ - This method is thread-safe. - - Returns - ------- - bool - ``True`` if the solve method is still running - """ - return self._delegate.isSolving() - - def terminate_early(self) -> bool: - """ - Notifies the solver that it should stop at its earliest convenience. - This method returns immediately, but it takes an undetermined time for the `solve` to actually return. - - If the solver is running in daemon mode, this is the only way to terminate it normally. - - This method is thread-safe. - It can only be called from a different thread because the original thread is still calling `solve`. - - Returns - ------- - bool - ``True`` if successful, ``False`` if was already terminating or terminated - """ - return self._delegate.terminateEarly() - - def is_terminate_early(self) -> bool: - """ - This method is thread-safe. - - Returns - ------- - bool - ``True`` if `terminate_early` has been called since the `Solver` started. - """ - return self._delegate.isTerminateEarly() - - def add_problem_change(self, problem_change: ProblemChange[Solution_]) -> None: - """ - Schedules a `ProblemChange` to be processed. - As a side effect, this restarts the `Solver`, effectively resetting all Terminations, but not `terminate_early`. - This method is thread-safe. - Follow specifications of `queue.Queue.put` with by default a maxsize of 0. - To learn more about problem change semantics, please refer to the `ProblemChange` docstring. - - Parameters - ---------- - problem_change : ProblemChange - A `ProblemChange` to be processed. - - See Also - -------- - add_problem_changes - """ - self._delegate.addProblemChange(ProblemChangeWrapper(problem_change)) # noqa - - def add_problem_changes(self, problem_changes: list[ProblemChange[Solution_]]) -> None: - """ - Schedules multiple `ProblemChange`s to be processed. - As a side effect, this restarts the `Solver`, effectively resetting all Terminations, but not `terminate_early`. - This method is thread-safe. - Follow specifications of `queue.Queue.put` with by default a maxsize of 0. - To learn more about problem change semantics, please refer to the `ProblemChange` docstring. - - Parameters - ---------- - problem_changes : list[ProblemChange] - A list of `ProblemChange`s to be processed. - - See Also - -------- - add_problem_change - """ - self._delegate.addProblemChanges([ProblemChangeWrapper(problem_change) for problem_change in problem_changes]) # noqa - - def is_every_problem_change_processed(self) -> bool: - """ - Checks if all scheduled `ProblemChange`s have been processed. - This method is thread-safe. - - Returns - ------- - bool - ``True`` if there are no `ProblemChange`s left to do - """ - return self._delegate.isEveryProblemChangeProcessed() - - def add_event_listener(self, event_listener: Callable[[BestSolutionChangedEvent[Solution_]], None]): - """ - Adds a listener to be notified when a new best solution is found. - - Parameters - ---------- - event_listener : Callable[[BestSolutionChangedEvent[Solution]], None] - The listener to be notified when a new best solution is found. - - Examples - -------- - >>> from timefold.solver import Solver, BestSolutionChangedEvent - >>> from domain import Timetable, build_solver, generate_problem - >>> - >>> def best_solution_listener(event: BestSolutionChangedEvent[Timetable]) -> None: - ... print(event.new_best_score) - ... - >>> solver = build_solver() - >>> solver.add_event_listener(best_solution_listener) - >>> timetable = generate_problem() - >>> solver.solve(timetable) - """ - from ai.timefold.solver.core.api.solver.event import SolverEventListener - event_listener_list = self._event_listener_list - if not self._has_event_listener: - @JImplements(SolverEventListener) - class EventListener: - @JOverride - def bestSolutionChanged(self, event): - from _jpyinterpreter import unwrap_python_like_object - nonlocal event_listener_list - event = BestSolutionChangedEvent( - new_best_score=to_python_score(event.getNewBestScore()), - new_best_solution=unwrap_python_like_object(event.getNewBestSolution()), - is_every_problem_change_processed=event.isEveryProblemChangeProcessed(), - time_spent=timedelta(milliseconds=event.getTimeMillisSpent()) - ) - for listener in event_listener_list: - listener(event) - - self._has_event_listener = True - self._delegate.addEventListener(EventListener()) # noqa - - event_listener_list.append(event_listener) - - def remove_event_listener(self, event_listener: Callable[[BestSolutionChangedEvent[Solution_]], None]): - """ - Removes a listener added by `add_event_listener`. - - Parameters - ---------- - event_listener : Callable[[BestSolutionChangedEvent[Solution]], None] - The listener to be removed - """ - self._event_listener_list.remove(event_listener) - - -__all__ = ['Solver', 'BestSolutionChangedEvent'] diff --git a/timefold-solver-python-core/src/main/python/_solver_factory.py b/timefold-solver-python-core/src/main/python/_solver_factory.py deleted file mode 100644 index 8a7dac6f..00000000 --- a/timefold-solver-python-core/src/main/python/_solver_factory.py +++ /dev/null @@ -1,76 +0,0 @@ -from ._solver import Solver -from .config import SolverConfig, SolverConfigOverride - -from typing import TypeVar, Generic, TYPE_CHECKING -from jpype import JClass - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from ai.timefold.solver.core.api.solver import SolverFactory as _JavaSolverFactory - - -Solution_ = TypeVar('Solution_') - - -class SolverFactory(Generic[Solution_]): - """ - Creates `Solver` instances. - Most applications only need one `SolverFactory`. - To create a `SolverFactory`, create a `SolverConfig` first and then use - `create`. - - These methods are thread-safe unless explicitly stated otherwise. - """ - _delegate: '_JavaSolverFactory' - _solution_class: JClass - - def __init__(self, delegate: '_JavaSolverFactory', solution_class: JClass): - self._delegate = delegate - self._solution_class = solution_class - - @staticmethod - def create(solver_config: SolverConfig[Solution_]) -> 'SolverFactory[Solution_]': - """ - Uses a `SolverConfig` to build a `SolverFactory`. - - Parameters - ---------- - solver_config : SolverConfig - The `SolverConfig` to build the `SolverFactory` from. - - Returns - ------- - SolverFactory - A `SolverFactory` instance. - - Notes - ----- - Subsequent changes to the config have no effect on the returned instance. - """ - from ai.timefold.solver.core.api.solver import SolverFactory as JavaSolverFactory - solver_config = solver_config._to_java_solver_config() - delegate = JavaSolverFactory.create(solver_config) # noqa - return SolverFactory(delegate, solver_config.getSolutionClass()) # noqa - - def build_solver(self, solver_config_override: SolverConfigOverride = None) -> Solver[Solution_]: - """ - Creates a new Solver instance. - - Parameters - ---------- - solver_config_override : SolverConfigOverride, optional - If present, overrides to apply to the configured `SolverConfig` on the created `Solver`. - - Returns - ------- - Solver - A `Solver` instance. - """ - if solver_config_override is None: - return Solver(self._delegate.buildSolver(), self._solution_class) - else: - return Solver(self._delegate.buildSolver(solver_config_override._to_java_solver_config_override()), - self._solution_class) - - -__all__ = ['SolverFactory'] diff --git a/timefold-solver-python-core/src/main/python/_solver_manager.py b/timefold-solver-python-core/src/main/python/_solver_manager.py deleted file mode 100644 index 38eb0fcf..00000000 --- a/timefold-solver-python-core/src/main/python/_solver_manager.py +++ /dev/null @@ -1,564 +0,0 @@ -from ._problem_change import ProblemChange, ProblemChangeWrapper -from .config import SolverConfig, SolverConfigOverride, SolverManagerConfig -from ._solver_factory import SolverFactory -from ._future import wrap_future -from ._timefold_java_interop import update_log_level - -from typing import Awaitable, TypeVar, Generic, Callable, TYPE_CHECKING -from datetime import timedelta -from enum import Enum - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from ai.timefold.solver.core.api.solver import (SolverManager as _JavaSolverManager, - SolverJob as _JavaSolverJob, - SolverJobBuilder as _JavaSolverJobBuilder) - -Solution_ = TypeVar('Solution_') -ProblemId_ = TypeVar('ProblemId_') - - -class SolverStatus(Enum): - """ - The status of the problem submitted to the SolverManager. - Retrieve this status with `SolverManager.get_solver_status` or - `SolverJob.get_solver_status`. - """ - - NOT_SOLVING = 'NOT_SOLVING' - """ - The problem's solving has terminated or the problem was never submitted to the `SolverManager`. - `SolverManager.get_solver_status` cannot tell the difference, but `SolverJob.get_solver_status` can. - """ - - SOLVING_SCHEDULED = 'SOLVING_SCHEDULED' - """ - No solver thread started solving this problem yet, but sooner or later a solver thread will solve it. - For example, submitting 7 problems to a `SolverManager` with a `SolverManagerConfig.parallel_solver_count` of 4, - puts 3 into this state for non-trivial amount of time. - - Transitions into `SOLVING_ACTIVE` (or `NOT_SOLVING` if it is terminated early, before it starts). - """ - - SOLVING_ACTIVE = 'SOLVING_ACTIVE' - """ - A solver thread started solving the problem, but hasn't finished yet. - If CPU resource are scarce and that solver thread is waiting for CPU time, - the state doesn't change, it's still considered solving active. - Transitions into `NOT_SOLVING` when terminated. - """ - - @staticmethod - def _from_java_enum(enum_value): - return getattr(SolverStatus, enum_value.name()) - - -class SolverJob(Generic[Solution_, ProblemId_]): - """ - Represents a problem that has been submitted to solve on the SolverManager. - """ - _delegate: '_JavaSolverJob' - - def __init__(self, delegate: '_JavaSolverJob'): - self._delegate = delegate - - def get_problem_id(self) -> ProblemId_: - """ - A value given to `SolverManager.solve`, `SolverManager.solve_and_listen` or - `SolverJobBuilder.with_problem_id`. - - Returns - ------- - ProblemId_ - The problem id corresponding to this `SolverJob`. - """ - from _jpyinterpreter import unwrap_python_like_object - return unwrap_python_like_object(self._delegate.getProblemId()) - - def get_solver_status(self) -> SolverStatus: - """ - Returns whether the `Solver` is scheduled to solve, actively solving or not. - Returns `SolverStatus.NOT_SOLVING` if the solver already terminated. - - Returns - ------- - SolverStatus - The `SolverStatus` for this `SolverJob`. - """ - return SolverStatus._from_java_enum(self._delegate.getSolverStatus()) - - def get_solving_duration(self) -> timedelta: - """ - Returns the duration spent solving since the last start. - If it hasn't started it yet, it returns ``timedelta(0)``. - If it hasn't ended yet, it returns the time between the last start and now. - If it has ended already, it returns the time between the last start and the ending. - - Returns - ------- - timedelta - The duration spent solving since the last (re)start, at least 0. - """ - return timedelta(milliseconds=self._delegate.getSolvingDuration().toMillis()) - - def get_final_best_solution(self) -> Solution_: - """ - Waits if necessary for the solver to complete and then returns the final best `planning_solution`. - - Returns - ------- - Solution_ - Never ``None``, but it could be the original uninitialized problem. - """ - from _jpyinterpreter import unwrap_python_like_object - return unwrap_python_like_object(self._delegate.getFinalBestSolution()) - - def terminate_early(self) -> None: - """ - Terminates the solver or cancels the solver job if it hasn't (re) started yet. - Does nothing if the solver already terminated. - - Waits for the termination or cancellation to complete before returning. - During termination, a best_solution_consumer could still be called. - When the solver terminates, the final_best_solution_consumer is executed with the latest best solution. - These consumers run on a consumer thread independently of the termination - and may still run even after this method returns. - """ - self._delegate.terminateEarly() - - def is_terminated_early(self) -> bool: - """ - Checks if `terminate_early` has been called on this `SolverJob`. - - Returns - ------- - bool - ``True`` if `terminate_early` has been called since the underlying `Solver` started solving. - """ - return self._delegate.isTerminatedEarly() - - def add_problem_change(self, problem_change: ProblemChange[Solution_]) -> Awaitable[None]: - """ - Schedules a `ProblemChange` to be processed by the underlying `Solver` and returns immediately. - To learn more about problem change semantics, please refer to the `ProblemChange` docstring. - - Parameters - ---------- - problem_change : ProblemChange - The `ProblemChange` to be processed. - - Returns - ------- - Awaitable - An awaitable that completes after the best solution containing this change has been consumed. - """ - return wrap_future(self._delegate.addProblemChange(ProblemChangeWrapper(problem_change))) - - -class SolverJobBuilder(Generic[Solution_, ProblemId_]): - """ - Provides a fluent contract that allows customization and submission of planning problems to solve. - A `SolverManager` can solve multiple planning problems and can be used across different threads. - - Hence, it is possible to have multiple distinct build configurations - that are scheduled to run by the `SolverManager` instance. - - To solve a planning problem, set the problem configuration: - `with_problem_id`, `with_problem_finder` and `with_problem`. - - Then solve it by calling `run`. - """ - _delegate: '_JavaSolverJobBuilder' - - def __init__(self, delegate: '_JavaSolverJobBuilder'): - self._delegate = delegate - - def with_problem_id(self, problem_id: ProblemId_) -> 'SolverJobBuilder': - """ - Sets the problem id. - - Parameters - ---------- - problem_id : ProblemId_ - A ID for each planning problem. This must be unique. - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from _jpyinterpreter import convert_to_java_python_like_object - return SolverJobBuilder(self._delegate.withProblemId(convert_to_java_python_like_object(problem_id))) - - def with_problem(self, problem: Solution_) -> 'SolverJobBuilder': - """ - Sets the problem definition. - - Parameters - ---------- - problem : Solution_ - A `planning_solution`, usually with uninitialized planning variables - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from _jpyinterpreter import convert_to_java_python_like_object - return SolverJobBuilder(self._delegate.withProblem(convert_to_java_python_like_object(problem))) - - def with_config_override(self, config_override: SolverConfigOverride) -> 'SolverJobBuilder': - """ - Sets the solver config override. - - Parameters - ---------- - config_override : SolverConfigOverride - Allows overriding the default behavior of Solver - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - return SolverJobBuilder(self._delegate.withConfigOverride(config_override._to_java_solver_config_override())) - - def with_problem_finder(self, problem_finder: Callable[[ProblemId_], Solution_]) -> 'SolverJobBuilder': - """ - Sets the mapping function to the problem definition. - - Parameters - ---------- - problem_finder : Callable[[ProblemId_], Solution_] - A function that returns a `planning_solution`, usually with uninitialized planning variables - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from java.util.function import Function - from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object - java_finder = Function @ (lambda problem_id: convert_to_java_python_like_object( - problem_finder(unwrap_python_like_object(problem_id)))) - return SolverJobBuilder(self._delegate.withProblemFinder(java_finder)) - - def with_best_solution_consumer(self, best_solution_consumer: Callable[[Solution_], None]) -> 'SolverJobBuilder': - """ - Sets the best solution consumer, which may be called multiple times during the solving process. - - Parameters - ---------- - best_solution_consumer : Callable[[Solution_], None] - Called multiple times for each new best solution on a consumer thread - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from java.util.function import Consumer - from _jpyinterpreter import unwrap_python_like_object - - java_consumer = Consumer @ (lambda solution: best_solution_consumer(unwrap_python_like_object(solution))) - return SolverJobBuilder(self._delegate.withBestSolutionConsumer(java_consumer)) - - def with_final_best_solution_consumer(self, final_best_solution_consumer: Callable[[Solution_], None]) -> 'SolverJobBuilder': - """ - Sets the final best solution consumer, - which is called at the end of the solving process and returns the final best solution. - - Parameters - ---------- - final_best_solution_consumer : Callable[[Solution_], None] - Called only once at the end of the solving process on a consumer thread - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from java.util.function import Consumer - from _jpyinterpreter import unwrap_python_like_object - - java_consumer = Consumer @ (lambda solution: final_best_solution_consumer(unwrap_python_like_object(solution))) - return SolverJobBuilder( - self._delegate.withFinalBestSolutionConsumer(java_consumer)) - - def with_exception_handler(self, exception_handler: Callable[[ProblemId_, Exception], None]) -> 'SolverJobBuilder': - """ - Sets the custom exception handler. - - Parameters - ---------- - exception_handler : Callable[[ProblemId_, Exception], None] - Called if an exception or error occurs. - If not present, it defaults to logging the exception as an error. - - Returns - ------- - SolverJobBuilder - This `SolverJobBuilder`. - """ - from java.util.function import BiConsumer - from _jpyinterpreter import unwrap_python_like_object - - java_consumer = BiConsumer @ (lambda problem_id, error: exception_handler(unwrap_python_like_object(problem_id), - error)) - return SolverJobBuilder( - self._delegate.withExceptionHandler(java_consumer)) - - def run(self) -> SolverJob[Solution_, ProblemId_]: - """ - Submits a planning problem to solve and returns immediately. - The planning problem is solved on a solver thread, as soon as one is available. - - Returns - ------- - SolverJob - The `SolverJob` built from this `SolverJobBuilder`. - """ - update_log_level() - return SolverJob(self._delegate.run()) - - -class SolverManager(Generic[Solution_, ProblemId_]): - """ - A `SolverManager` solves multiple planning problems of the same domain, - asynchronously without blocking the calling thread. - To create a `SolverManager`, use `create`. - To solve a planning problem, call `solve`, `solve_and_listen` or `solve_builder`. - - These methods are thread-safe unless explicitly stated otherwise. - - Internally a `SolverManager` manages a thread pool of solver threads (which call `Solver.solve`) - and consumer threads (to handle the `BestSolutionChangedEvents`). - - To learn more about problem change semantics, please refer to the `ProblemChange` Javadoc. - """ - _delegate: '_JavaSolverManager' - - def __init__(self, delegate: '_JavaSolverManager'): - self._delegate = delegate - - @staticmethod - def create(solver_factory_or_config: 'SolverConfig | SolverFactory[Solution_]', - solver_manager_config: 'SolverManagerConfig' = None) -> 'SolverManager[Solution_, ProblemId_]': - """ - Use a `SolverConfig` or `SolverFactory` to build a `SolverManager`. - - Parameters - ---------- - solver_factory_or_config : SolverConfig | SolverFactory[Solution_] - The `SolverConfig` or `SolverFactory` to build the `SolverManager` from. - - solver_manager_config: SolverManagerConfig, optional - Additional settings that can be used to configure the `SolverManager`. - - Returns - ------- - SolverManager - A new `SolverManager` instance. - """ - from ai.timefold.solver.core.api.solver import SolverManager as JavaSolverManager - from ai.timefold.solver.python import DaemonThreadFactory - - if solver_manager_config is None: - solver_manager_config = SolverManagerConfig() - - java_solver_manager_config = solver_manager_config._to_java_solver_manager_config() # noqa - java_solver_manager_config.setThreadFactoryClass(DaemonThreadFactory.class_) - - if isinstance(solver_factory_or_config, SolverConfig): - solver_factory_or_config = SolverFactory.create(solver_factory_or_config) - - return SolverManager(JavaSolverManager.create(solver_factory_or_config._delegate, # noqa - java_solver_manager_config)) - - def solve(self, problem_id: ProblemId_, problem: Solution_, - final_best_solution_listener: Callable[[Solution_], None] = None) -> SolverJob[Solution_, ProblemId_]: - """ - Submits a planning problem to solve and returns immediately. - The planning problem is solved on a solver Thread, as soon as one is available. - To retrieve the final best solution, use `SolverJob.get_final_best_solution`. - In server applications, it's recommended to set `final_best_solution_listener`, - to avoid loading the problem going stale if solving can't start immediately. - To listen to intermediate best solutions too, use `solve_and_listen` instead. - - Defaults to logging exceptions as an error. - - To stop a solver job before it naturally terminates, call `SolverJob.terminate_early`. - - Parameters - ---------- - problem_id : ProblemId_ - A ID for each planning problem. - This must be unique. - Use this problemId to terminate the solver early, - to get the status or if the problem changes while solving. - - problem : Solution_ - A `planning_solution` usually with uninitialized planning variables - - final_best_solution_listener : Callable[[Solution_], None], optional - Called only once, at the end, on a consumer thread - - Returns - ------- - SolverJob - A new `SolverJob`. - """ - builder = (self.solve_builder() - .with_problem_id(problem_id) - .with_problem(problem)) - - if final_best_solution_listener is not None: - builder = builder.with_final_best_solution_consumer(final_best_solution_listener) - - return builder.run() - - def solve_and_listen(self, problem_id: ProblemId_, problem: Solution_, listener: Callable[[Solution_], None]) \ - -> SolverJob[Solution_, ProblemId_]: - """ - Submits a planning problem to solve and returns immediately. - The planning problem is solved on a solver thread, as soon as one is available. - When the solver finds a new best solution, the `best_solution_consumer` is called every time, - on a consumer thread, as soon as one is available (taking into account any throttling waiting time), - unless a newer best solution is already available by then (in which case skip ahead discards it). - - Defaults to logging exceptions as an error. - - To stop a solver job before it naturally terminates, call `terminate_early`. - - Parameters - ---------- - problem_id : ProblemId_ - A ID for each planning problem. - This must be unique. - Use this problemId to terminate the solver early, - to get the status or if the problem changes while solving. - - problem: Solution_ - A `planning_solution` usually with uninitialized planning variables. - - listener : Callable[[Solution_], None] - Called multiple times, on a consumer thread. - - Returns - ------- - SolverJob - A new `SolverJob`. - """ - return (self.solve_builder() - .with_problem_id(problem_id) - .with_problem(problem) - .with_best_solution_consumer(listener) - .run()) - - def solve_builder(self) -> SolverJobBuilder[Solution_, ProblemId_]: - """ - Creates a `SolverJobBuilder` that allows to customize and submit a planning problem to solve. - - Returns - ------- - SolverJobBuilder - A new `SolverJobBuilder`. - """ - return SolverJobBuilder(self._delegate.solveBuilder()) - - def get_solver_status(self, problem_id: ProblemId_) -> SolverStatus: - """ - Returns if the Solver is scheduled to solve, actively solving or not. - Returns `SolverStatus.NOT_SOLVING` if the solver already terminated or if the `problem_id` was never added. - To distinguish between both cases, use `SolverJob.get_solver_status` instead. - Here, that distinction is not supported because it would cause a memory leak. - - Parameters - ---------- - problem_id : ProblemId_ - A value given to `SolverManager.solve`, `SolverManager.solve_and_listen` or - `SolverJobBuilder.with_problem_id`. - - Returns - ------- - SolverStatus - The `SolverStatus` corresponding to `problem_id`. - """ - from _jpyinterpreter import convert_to_java_python_like_object - return SolverStatus._from_java_enum(self._delegate.getSolverStatus( - convert_to_java_python_like_object(problem_id))) - - def terminate_early(self, problem_id: ProblemId_) -> None: - """ - Terminates the solver or cancels the solver job if it hasn't (re)started yet. - Does nothing if the solver already terminated or the `problem_id` was never added. - To distinguish between both cases, use `SolverJob.terminate_early` instead. - Here, that distinction is not supported because it would cause a memory leak. - - Waits for the termination or cancellation to complete before returning. - During termination, a `best_solution_consumer` could still be called. - When the solver terminates, the `final_best_solution_consumer` is executed with the latest best solution. - These consumers run on a consumer thread independently of the termination - and may still run even after this method returns. - - Parameters - ---------- - problem_id : ProblemId_ - A value given to `SolverManager.solve`, `SolverManager.solve_and_listen` or - `SolverJobBuilder.with_problem_id`. - """ - from _jpyinterpreter import convert_to_java_python_like_object - self._delegate.terminateEarly(convert_to_java_python_like_object(problem_id)) - - def add_problem_change(self, problem_id: ProblemId_, problem_change: ProblemChange[Solution_]) -> Awaitable[None]: - """ - Schedules a `ProblemChange` to be processed by the underlying `Solver` and returns immediately. - If the solver already terminated or the `problem_id` was never added, throws an exception. - The same applies if the underlying `Solver` is not in the `SolverStatus.SOLVING_ACTIVE` state. - - Parameters - ---------- - problem_id : ProblemId_ - A value given to `SolverManager.solve`, `SolverManager.solve_and_listen` or - `SolverJobBuilder.with_problem_id`. - problem_change : ProblemChange - A problem change to be processed by the underlying `Solver`. - - Returns - ------- - Awaitable - An awaitable that completes after the best solution containing this change has been consumed. - """ - from _jpyinterpreter import convert_to_java_python_like_object - return wrap_future(self._delegate.addProblemChange(convert_to_java_python_like_object(problem_id), - ProblemChangeWrapper(problem_change))) - - def close(self) -> None: - """ - Terminates all solvers, - cancels all solver jobs that haven't (re)started yet and discards all queued ProblemChanges. - Releases all thread pool resources. - - No new planning problems can be submitted after calling this method. - """ - self._delegate.close() - - def __enter__(self) -> 'SolverManager[Solution_, ProblemId_]': - """ - Returns self, so it can be used as a context manager. - - Returns - ------- - SolverManager - This `SolverManager`. - """ - return self - - def __exit__(self, exc_type, exc_val, exc_tb) -> None: - """ - Calls `close` to release resources associated with this `SolverManager`. - """ - self._delegate.close() - - -__all__ = ['SolverManager', 'SolverJobBuilder', 'SolverJob', 'SolverStatus'] diff --git a/timefold-solver-python-core/src/main/python/_timefold_java_interop.py b/timefold-solver-python-core/src/main/python/_timefold_java_interop.py deleted file mode 100644 index 09f041fe..00000000 --- a/timefold-solver-python-core/src/main/python/_timefold_java_interop.py +++ /dev/null @@ -1,367 +0,0 @@ -import logging -import pathlib -import jpype -import jpype.imports -from jpype.types import * -import importlib.resources -from typing import cast, TypeVar, Callable, Union, TYPE_CHECKING, Any -from _jpyinterpreter import get_path -from ._jpype_type_conversions import PythonSupplier, ConstraintProviderFunction, PythonConsumer - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from java.lang import ClassLoader - from ai.timefold.solver.core.api.score.stream import (Constraint as _Constraint, - ConstraintFactory as _ConstraintFactory) - from ai.timefold.solver.python import PythonLoggingEvent - -logger = logging.getLogger('timefold.solver') - -Solution_ = TypeVar('Solution_') -ProblemId_ = TypeVar('ProblemId_') -Score_ = TypeVar('Score_') - -_compilation_queue: list[type] = [] -_enterprise_installed: bool = False -_scores_registered: bool = False -_python_score_mapping_dict: dict[str, object] = {} -_java_score_mapping_dict: dict[str, object] = {} - - -def is_enterprise_installed() -> bool: - global _enterprise_installed - return _enterprise_installed - - -def extract_timefold_jars() -> list[str]: - """ - Returns - ------- - list[str] - a list contains classpath entries for - Timefold Solver's dependencies. - """ - global _enterprise_installed - try: - - enterprise_dependencies = [str(get_path('timefold.solver.enterprise.jars', - p.name).__enter__()) - for p in importlib.resources.files('timefold.solver.enterprise.jars').iterdir() - if p.name.endswith(".jar")] - _enterprise_installed = True - except ModuleNotFoundError: - enterprise_dependencies = [] - _enterprise_installed = False - return [str(get_path('timefold.solver.jars', p.name).__enter__()) - for p in importlib.resources.files('timefold.solver.jars').iterdir() - if p.name.endswith(".jar")] + enterprise_dependencies - - -def init(*args, path: list[str] = None, include_timefold_jars: bool = True) -> None: - """ - Most users will never need to call this method. - Only call this method if you are using other Java libraries. - This method will automatically be called when a Timefold class is instantiated. - - Initializes the JVM. - - Parameters - ---------- - args : list[str] - JVM arguments. - path : list[str], optional - List of dependencies to use as the classpath. - include_timefold_jars : bool, optional - If the Timefold dependencies should be added to `path`. - Defaults to True. - """ - from _jpyinterpreter import init - - if jpype.isJVMStarted(): # noqa - raise RuntimeError('JVM already started. Maybe call init before timefold.solver.types imports?') - if path is None: - include_timefold_jars = True - path = [] - if include_timefold_jars: - path = path + extract_timefold_jars() - if len(args) == 0: - args = [jpype.getDefaultJVMPath()] - init(*args, path=path, include_translator_jars=False) - - from ai.timefold.solver.python.logging import PythonLoggingToLogbackAdapter, PythonDelegateAppender - PythonDelegateAppender.setLogEventConsumer(PythonConsumer(forward_logging_events)) - update_log_level() - - -def update_log_level() -> None: - from ai.timefold.solver.python.logging import PythonLoggingToLogbackAdapter - PythonLoggingToLogbackAdapter.setLevel(logger.getEffectiveLevel()) - - -def register_score_python_java_type_mappings(): - global _scores_registered, _java_score_mapping_dict, _python_score_mapping_dict - if _scores_registered: - return - - _scores_registered = True - - from decimal import Decimal - from .score._score import (SimpleScore, HardSoftScore, HardMediumSoftScore, BendableScore, - SimpleDecimalScore, HardSoftDecimalScore, HardMediumSoftDecimalScore, - BendableDecimalScore) - from ai.timefold.solver.core.api.score.buildin.simplelong import SimpleLongScore as _SimpleScore - from ai.timefold.solver.core.api.score.buildin.hardsoftlong import HardSoftLongScore as _HardSoftScore - from ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong import HardMediumSoftLongScore as _HardMediumSoftScore - from ai.timefold.solver.core.api.score.buildin.bendablelong import BendableLongScore as _BendableScore - - from ai.timefold.solver.core.api.score.buildin.simplebigdecimal import SimpleBigDecimalScore as _SimpleDecimalScore - from ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal import HardSoftBigDecimalScore as _HardSoftDecimalScore - from ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal import HardMediumSoftBigDecimalScore as _HardMediumSoftDecimalScore - from ai.timefold.solver.core.api.score.buildin.bendablebigdecimal import BendableBigDecimalScore as _BendableDecimalScore - - from ai.timefold.solver.python.score import (SimpleScorePythonJavaTypeMapping, - HardSoftScorePythonJavaTypeMapping, - HardMediumSoftScorePythonJavaTypeMapping, - BendableScorePythonJavaTypeMapping, - SimpleDecimalScorePythonJavaTypeMapping, - HardSoftDecimalScorePythonJavaTypeMapping, - HardMediumSoftDecimalScorePythonJavaTypeMapping, - BendableDecimalScorePythonJavaTypeMapping, - ) - from _jpyinterpreter import translate_python_class_to_java_class, add_python_java_type_mapping - - _python_score_mapping_dict['SimpleScore'] = SimpleScore - _python_score_mapping_dict['HardSoftScore'] = HardSoftScore - _python_score_mapping_dict['HardMediumSoftScore'] = HardMediumSoftScore - _python_score_mapping_dict['BendableScore'] = BendableScore - _python_score_mapping_dict['SimpleDecimalScore'] = SimpleDecimalScore - _python_score_mapping_dict['HardSoftDecimalScore'] = HardSoftDecimalScore - _python_score_mapping_dict['HardMediumSoftDecimalScore'] = HardMediumSoftDecimalScore - _python_score_mapping_dict['BendableDecimalScore'] = BendableDecimalScore - - _java_score_mapping_dict['SimpleScore'] = _SimpleScore - _java_score_mapping_dict['HardSoftScore'] = _HardSoftScore - _java_score_mapping_dict['HardMediumSoftScore'] = _HardMediumSoftScore - _java_score_mapping_dict['BendableScore'] = _BendableScore - _java_score_mapping_dict['SimpleDecimalScore'] = _SimpleDecimalScore - _java_score_mapping_dict['HardSoftDecimalScore'] = _HardSoftDecimalScore - _java_score_mapping_dict['HardMediumSoftDecimalScore'] = _HardMediumSoftDecimalScore - _java_score_mapping_dict['BendableDecimalScore'] = _BendableDecimalScore - - SimpleScoreType = translate_python_class_to_java_class(SimpleScore) - HardSoftScoreType = translate_python_class_to_java_class(HardSoftScore) - HardMediumSoftScoreType = translate_python_class_to_java_class(HardMediumSoftScore) - BendableScoreType = translate_python_class_to_java_class(BendableScore) - - SimpleDecimalScoreType = translate_python_class_to_java_class(SimpleDecimalScore) - HardSoftDecimalScoreType = translate_python_class_to_java_class(HardSoftDecimalScore) - HardMediumSoftDecimalScoreType = translate_python_class_to_java_class(HardMediumSoftDecimalScore) - BendableDecimalScoreType = translate_python_class_to_java_class(BendableDecimalScore) - - add_python_java_type_mapping(SimpleScorePythonJavaTypeMapping(SimpleScoreType)) - add_python_java_type_mapping(HardSoftScorePythonJavaTypeMapping(HardSoftScoreType)) - add_python_java_type_mapping(HardMediumSoftScorePythonJavaTypeMapping(HardMediumSoftScoreType)) - add_python_java_type_mapping(BendableScorePythonJavaTypeMapping(BendableScoreType)) - - add_python_java_type_mapping(SimpleDecimalScorePythonJavaTypeMapping(SimpleDecimalScoreType)) - add_python_java_type_mapping(HardSoftDecimalScorePythonJavaTypeMapping(HardSoftDecimalScoreType)) - add_python_java_type_mapping(HardMediumSoftDecimalScorePythonJavaTypeMapping(HardMediumSoftDecimalScoreType)) - add_python_java_type_mapping(BendableDecimalScorePythonJavaTypeMapping(BendableDecimalScoreType)) - - -def forward_logging_events(event: 'PythonLoggingEvent') -> None: - logger.log(event.level().getPythonLevelNumber(), - event.message()) - - -def ensure_init(): - """ - Start the JVM if it isn't started; does nothing otherwise - """ - if jpype.isJVMStarted(): # noqa - return - else: - init() - - -def set_class_output_directory(path: pathlib.Path): - """ - Sets the output directory for classes generated by Timefold Solver. - By default, the classes are only stored in memory. - - Parameters - ---------- - path : pathlib.Path - Path to the output directory. - It will be created if it doesn't exist. - """ - ensure_init() - - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa - PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = path - - -def get_class(python_class: Union[type, Callable]) -> JClass: - """Return the Java Class for the given Python Class""" - from java.lang import Object, Class - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - from _jpyinterpreter import is_c_native, get_java_type_for_python_type - - if python_class is None: - return cast(JClass, None) - if isinstance(python_class, jpype.JClass): - return cast(JClass, python_class).class_ - if isinstance(python_class, Class): - return cast(JClass, python_class) - if python_class == int: - from java.lang import Integer - return cast(JClass, Integer).class_ - if python_class == str: - from java.lang import String - return cast(JClass, String).class_ - if python_class == bool: - from java.lang import Boolean - return cast(JClass, Boolean).class_ - if hasattr(python_class, '_timefold_java_class'): - return cast(JClass, python_class._timefold_java_class) - if isinstance(python_class, type): - return cast(JClass, get_java_type_for_python_type(python_class).getJavaClass()) - if is_c_native(python_class): - return cast(JClass, OpaquePythonReference.class_) - return cast(JClass, Object) - - -def get_asm_type(python_class: Union[type, Callable]) -> Any: - """Return the ASM type for the given Python Class""" - from java.lang import Object, Class - from ai.timefold.jpyinterpreter import AnnotationMetadata - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference - from _jpyinterpreter import is_c_native, get_java_type_for_python_type - - if python_class is None: - return None - if isinstance(python_class, jpype.JClass): - return AnnotationMetadata.getValueAsType(python_class.class_.getName()) - if isinstance(python_class, Class): - return AnnotationMetadata.getValueAsType(python_class.getName()) - if python_class == int: - from java.lang import Integer - return AnnotationMetadata.getValueAsType(Integer.class_.getName()) - if python_class == str: - from java.lang import String - return AnnotationMetadata.getValueAsType(String.class_.getName()) - if python_class == bool: - from java.lang import Boolean - return AnnotationMetadata.getValueAsType(Boolean.class_.getName()) - if hasattr(python_class, '_timefold_java_class'): - return AnnotationMetadata.getValueAsType(python_class._timefold_java_class.getName()) - if isinstance(python_class, type): - return AnnotationMetadata.getValueAsType(get_java_type_for_python_type(python_class).getJavaTypeInternalName()) - if is_c_native(python_class): - return AnnotationMetadata.getValueAsType(OpaquePythonReference.class_.getName()) - return AnnotationMetadata.getValueAsType(Object.class_.getName()) - - -def register_java_class(python_object: Solution_, - java_class: JClass) -> Solution_: - python_object._timefold_java_class = java_class - class_identifier = _get_class_identifier_for_object(python_object) - class_identifier_to_java_class_map[class_identifier] = java_class - return python_object - - -unique_class_id = 0 -"""A unique identifier; used to guarantee the generated class java name is unique""" - -class_identifier_to_java_class_map = dict() -"""Maps a class identifier to the corresponding java class (the last one defined with that identifier)""" - - -def _get_class_identifier_for_object(python_object): - module = getattr(python_object, '__module__', '__main__') - if module == '__main__': - return python_object.__qualname__ - else: - return f'{module}.{python_object.__qualname__}' - - -def _compose_unique_class_name(class_identifier: str): - from jpype import JInt - from ai.timefold.jpyinterpreter.util import JavaIdentifierUtils - from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator - unique_class_name = f'org.jpyinterpreter.user.{class_identifier}' - unique_class_name = JavaIdentifierUtils.sanitizeClassName(unique_class_name) - number_of_instances = PythonBytecodeToJavaBytecodeTranslator.classNameToSharedInstanceCount.merge( - unique_class_name, JInt(1), lambda a, b: JInt(a + b)) - if number_of_instances > 1: - unique_class_name = f'{unique_class_name}$${number_of_instances}' - return unique_class_name - - -class OverrideClassLoader: - thread_class_loader: 'ClassLoader' - - def __enter__(self): - from java.lang import Thread - from ai.timefold.solver.python import PythonWrapperGenerator # noqa - class_loader = PythonWrapperGenerator.getClassLoaderForAliasMap(class_identifier_to_java_class_map) - current_thread = Thread.currentThread() - self.thread_class_loader = current_thread.getContextClassLoader() - current_thread.setContextClassLoader(class_loader) - return class_loader - - def __exit__(self, exc_type, exc_val, exc_tb): - from java.lang import Thread - current_thread = Thread.currentThread() - current_thread.setContextClassLoader(self.thread_class_loader) - - -def compile_class(python_class: type) -> None: - from _jpyinterpreter import translate_python_class_to_java_class - ensure_init() - class_identifier = _get_class_identifier_for_object(python_class) - out = translate_python_class_to_java_class(python_class).getJavaClass() - class_identifier_to_java_class_map[class_identifier] = out - - -def _add_to_compilation_queue(python_class: type | PythonSupplier) -> None: - global _compilation_queue - _compilation_queue.append(python_class) - - -def _process_compilation_queue() -> None: - global _compilation_queue - - register_score_python_java_type_mappings() - while len(_compilation_queue) > 0: - python_class = _compilation_queue.pop(0) - - if isinstance(python_class, PythonSupplier): - python_class = python_class.get() - - compile_class(python_class) - - -def _to_constraint_java_array(python_list: list['_Constraint']) -> JArray: - # reimport since the one in global scope is only for type checking - import ai.timefold.solver.core.api.score.stream.Constraint as ActualConstraintClass - out = jpype.JArray(ActualConstraintClass)(len(python_list)) - for i in range(len(python_list)): - out[i] = python_list[i] - return out - - -def _generate_constraint_provider_class(original_function: Callable[['_ConstraintFactory'], list['_Constraint']], - wrapped_constraint_provider: Callable[['_ConstraintFactory'], - list['_Constraint']]) -> JClass: - ensure_init() - register_score_python_java_type_mappings() - from ai.timefold.solver.python import PythonWrapperGenerator # noqa - from ai.timefold.solver.core.api.score.stream import ConstraintProvider - class_identifier = _get_class_identifier_for_object(original_function) - out = PythonWrapperGenerator.defineConstraintProviderClass( - _compose_unique_class_name(class_identifier), - JObject(ConstraintProviderFunction(lambda cf: _to_constraint_java_array(wrapped_constraint_provider(cf))), - ConstraintProvider)) - class_identifier_to_java_class_map[class_identifier] = out - return out diff --git a/timefold-solver-python-core/src/main/python/config/__init__.py b/timefold-solver-python-core/src/main/python/config/__init__.py deleted file mode 100644 index 9392743f..00000000 --- a/timefold-solver-python-core/src/main/python/config/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Classes used to configure the `Solver`. - -Examples --------- ->>> from timefold.solver.config import (SolverConfig, ScoreDirectorFactoryConfig, -... TerminationConfig, Duration) ->>> from domain import Timetable, Lesson ->>> from constraints import my_constraints ->>> ->>> solver_config = SolverConfig(solution_class=Timetable, entity_class_list=[Lesson], -... score_director_factory_config=ScoreDirectorFactoryConfig( -... constraint_provider_function=my_constraints -... ), -... termination_config=TerminationConfig( -... spent_limit=Duration(seconds=30)) -... ) -""" -from ._config import * diff --git a/timefold-solver-python-core/src/main/python/config/_config.py b/timefold-solver-python-core/src/main/python/config/_config.py deleted file mode 100644 index fa714cfb..00000000 --- a/timefold-solver-python-core/src/main/python/config/_config.py +++ /dev/null @@ -1,406 +0,0 @@ -from ..score import ConstraintFactory, Constraint, IncrementalScoreCalculator -from .._timefold_java_interop import is_enterprise_installed - -from typing import Any, Optional, Callable, TypeVar, Generic, Literal, TYPE_CHECKING -from dataclasses import dataclass, field -from enum import Enum -from pathlib import Path -from jpype import JClass - -if TYPE_CHECKING: - from java.time import Duration as _JavaDuration - from ai.timefold.solver.core.config.solver import SolverConfig as _JavaSolverConfig - from ai.timefold.solver.core.config.solver.termination import TerminationConfig as _JavaTerminationConfig - from ai.timefold.solver.core.config.score.director import ( - ScoreDirectorFactoryConfig as _JavaScoreDirectorFactoryConfig) - - -_java_environment_mode = 'ai.timefold.solver.core.config.solver.EnvironmentMode' -_java_termination_composition_style = 'ai.timefold.solver.core.config.solver.termination.TerminationCompositionStyle' - - -def _lookup_on_java_class(java_class: str, attribute: str) -> Any: - return getattr(JClass(java_class), attribute) - - -@dataclass(kw_only=True) -class Duration: - """ - Represents a duration of time. - """ - milliseconds: int = field(default=0) - seconds: int = field(default=0) - minutes: int = field(default=0) - hours: int = field(default=0) - days: int = field(default=0) - - def to_milliseconds(self) -> int: - return self._to_java_duration().toMillis() - - def to_seconds(self) -> int: - return self._to_java_duration().toSeconds() - - def to_minutes(self) -> int: - return self._to_java_duration().toMinutes() - - def to_hours(self) -> int: - return self._to_java_duration().toHours() - - def to_days(self) -> int: - return self._to_java_duration().toDays() - - @staticmethod - def _from_java_duration(duration: '_JavaDuration'): - return Duration( - milliseconds=duration.toMillis() - ) - - def _to_java_duration(self) -> '_JavaDuration': - from java.time import Duration - return (Duration.ZERO - .plusMillis(self.milliseconds) - .plusSeconds(self.seconds) - .plusMinutes(self.minutes) - .plusHours(self.hours) - .plusDays(self.days) - ) - - -class EnvironmentMode(Enum): - """ - The environment mode also allows you to detect common bugs in your implementation. - Also, a `Solver` has a single Random instance. - Some optimization algorithms use the Random instance a lot more than others. - For example simulated annealing depends highly on random numbers, - while tabu search only depends on it to deal with score ties. - This environment mode influences the seed of that Random instance. - """ - - NON_REPRODUCIBLE = 'NON_REPRODUCIBLE' - """ - The non reproducible mode is equally fast or slightly faster than REPRODUCIBLE. - The random seed is different on every run, - which makes it more robust against an unlucky random seed. - An unlucky random seed gives a bad result on a certain data set with a certain solver configuration. - Note that in most use cases the impact of the random seed is relatively low on the result. - An occasional bad result is far more likely to be caused by another issue (such as a score trap). - In multithreaded scenarios, this mode allows the use of work stealing and other non deterministic speed tricks. - """ - - REPRODUCIBLE = 'REPRODUCIBLE' - """ - The reproducible mode is the default mode because it is recommended during development. - In this mode, 2 runs on the same computer will execute the same code in the same order. - They will also yield the same result, - except if they use a time based termination and they have a sufficiently large difference in allocated CPU time. - This allows you to benchmark new optimizations (such as a new Move implementation) - fairly and reproduce bugs in your code reliably. - - Warning: some code can disrupt reproducibility regardless of this mode. - See the reference manual for more info. - In practice, this mode uses the default random seed, - and it also disables certain concurrency optimizations (such as work stealing). - """ - - FAST_ASSERT = 'FAST_ASSERT' - """ - This mode turns on several assertions (but not all of them) to fail-fast on a bug in a Move implementation, - a constraint rule, the engine itself or something else at a reasonable performance cost (in development at least). - This mode is reproducible (see REPRODUCIBLE mode). - This mode is intrusive because it calls calculate_score more frequently than a non assert mode. - This mode is slow. - """ - - NON_INTRUSIVE_FULL_ASSERT = 'NON_INTRUSIVE_FULL_ASSERT' - """ - This mode turns on several assertions (but not all of them) to fail-fast on a bug in a Move implementation, - a constraint, the engine itself or something else at an overwhelming performance cost. - This mode is reproducible (see REPRODUCIBLE mode). - This mode is non-intrusive, unlike FULL_ASSERT and FAST_ASSERT. - This mode is horribly slow. - """ - - FULL_ASSERT = 'FULL_ASSERT' - """ - This mode turns on all assertions to fail-fast on a bug in a Move implementation, - a constraint, the engine itself or something else at a horrible performance cost. - This mode is reproducible (see REPRODUCIBLE mode). - This mode is intrusive because it calls calculate_score more frequently than a non assert mode. - This mode is horribly slow. - """ - - TRACKED_FULL_ASSERT = 'TRACKED_FULL_ASSERT' - """ - This mode turns on FULL_ASSERT and enables variable tracking to fail-fast on a bug in a Move implementation, - a constraint, the engine itself or something else at the highest performance cost. - Because it tracks genuine and shadow variables, - it is able to report precisely what variables caused the corruption and report any missed VariableListener events. - This mode is reproducible (see REPRODUCIBLE mode). - This mode is intrusive because it calls calculate_score more frequently than a non assert mode. - This mode is by far the slowest of all the modes. - """ - - def _get_java_enum(self): - return _lookup_on_java_class(_java_environment_mode, self.name) - - -class TerminationCompositionStyle(Enum): - OR = 'OR' - AND = 'AND' - - def _get_java_enum(self): - return _lookup_on_java_class(_java_termination_composition_style, self.name) - - -class MoveThreadCount(Enum): - AUTO = 'AUTO' - """ - Configure the number of move threads dynamically based on the - computer's core count. - """ - - NONE = 'NONE' - """ - Disables multithreaded solving. - """ - - -class RequiresEnterpriseError(EnvironmentError): - def __init__(self, feature): - super().__init__(f'Feature {feature} requires timefold-enterprise to be installed. ' - f'See https://docs.timefold.ai/timefold-solver/latest/enterprise-edition/' - f'enterprise-edition#switchToEnterpriseEdition for instructions on how to ' - f'install timefold-enterprise.') - - -Solution_ = TypeVar('Solution_') - - -@dataclass(kw_only=True) -class SolverConfig(Generic[Solution_]): - """ - To read it from XML, use `create_from_xml_resource`. - To build a `SolverFactory` with it, use `SolverFactory.create`. - """ - solution_class: Optional[type[Solution_]] = field(default=None) - entity_class_list: Optional[list[type]] = field(default=None) - environment_mode: Optional[EnvironmentMode] = field(default=EnvironmentMode.REPRODUCIBLE) - random_seed: Optional[int] = field(default=None) - move_thread_count: int | MoveThreadCount = field(default=MoveThreadCount.NONE) - nearby_distance_meter_function: Optional[Callable[[Any, Any], float]] = field(default=None) - termination_config: Optional['TerminationConfig'] = field(default=None) - score_director_factory_config: Optional['ScoreDirectorFactoryConfig'] = field(default=None) - xml_source_text: Optional[str] = field(default=None) - xml_source_file: Optional[Path] = field(default=None) - - @staticmethod - def create_from_xml_resource(path: Path) -> 'SolverConfig': - return SolverConfig(xml_source_file=path) - - @staticmethod - def create_from_xml_text(xml_text: str) -> 'SolverConfig': - return SolverConfig(xml_source_text=xml_text) - - def _to_java_solver_config(self) -> '_JavaSolverConfig': - from .._timefold_java_interop import OverrideClassLoader, get_class, _process_compilation_queue - from ai.timefold.solver.core.config.solver import SolverConfig as JavaSolverConfig - from java.io import File, ByteArrayInputStream # noqa - from java.lang import IllegalArgumentException - from java.util import ArrayList - - _process_compilation_queue() - - out = JavaSolverConfig() - with OverrideClassLoader() as class_loader: - out.setClassLoader(class_loader) - # First, inherit the config from the xml text/file - if self.xml_source_text is not None: - inherited = JavaSolverConfig.createFromXmlInputStream( - ByteArrayInputStream(self.xml_source_text.encode())) - out.inherit(inherited) - if self.xml_source_file is not None: - try: - inherited = JavaSolverConfig.createFromXmlFile(File(str(self.xml_source_file))) - out.inherit(inherited) - except IllegalArgumentException as e: - raise FileNotFoundError(e.getMessage()) from e - - # Next, override fields - if self.move_thread_count is not MoveThreadCount.NONE: - if not is_enterprise_installed(): - raise RequiresEnterpriseError('multithreaded solving') - if isinstance(self.move_thread_count, MoveThreadCount): - out.setMoveThreadCount(self.move_thread_count.name) - else: - out.setMoveThreadCount(str(self.move_thread_count)) - elif out.getMoveThreadCount() is not None and not is_enterprise_installed(): - raise RequiresEnterpriseError('multithreaded solving') - - if self.nearby_distance_meter_function is not None: - if not is_enterprise_installed(): - raise RequiresEnterpriseError('nearby selection') - out.setNearbyDistanceMeterClass(get_class(self.nearby_distance_meter_function)) - elif out.getNearbyDistanceMeterClass() is not None and not is_enterprise_installed(): - raise RequiresEnterpriseError('nearby selection') - - if self.solution_class is not None: - from ai.timefold.solver.core.api.domain.solution import PlanningSolution as JavaPlanningSolution - java_class = get_class(self.solution_class) - if java_class is None: - raise RuntimeError(f'Unable to generate Java class for {self.solution_class}') - if java_class.getAnnotation(JavaPlanningSolution) is None: - raise TypeError(f'{self.solution_class} is not a @planning_solution class. ' - f'Maybe move the @planning_solution decorator to the top of the decorator list?') - out.setSolutionClass(get_class(self.solution_class)) - - if self.entity_class_list is not None: - from ai.timefold.solver.core.api.domain.entity import PlanningEntity as JavaPlanningEntity - entity_class_list = ArrayList() - for entity_class in self.entity_class_list: - java_class = get_class(entity_class) - if java_class is None: - raise RuntimeError(f'Unable to generate Java class for {entity_class}') - if java_class.getAnnotation(JavaPlanningEntity) is None: - raise TypeError(f'{entity_class} is not a @planning_entity class. ' - f'Maybe move the @planning_entity decorator to the top of the decorator list?') - entity_class_list.add(java_class) - out.setEntityClassList(entity_class_list) - - if self.environment_mode is not None: - out.setEnvironmentMode(self.environment_mode._get_java_enum()) - - if self.random_seed is not None: - out.setRandomSeed(self.random_seed) - - if self.score_director_factory_config is not None: - out.setScoreDirectorFactoryConfig( - self.score_director_factory_config._to_java_score_director_factory_config()) - - if self.termination_config is not None: - out.setTerminationConfig( - self.termination_config._to_java_termination_config(out.getTerminationConfig())) - - return out - - -@dataclass(kw_only=True) -class ScoreDirectorFactoryConfig: - constraint_provider_function: Optional[Callable[[ConstraintFactory], list[Constraint]]] =\ - field(default=None) - easy_score_calculator_function: Optional[Callable] = field(default=None) - incremental_score_calculator_class: Optional[type[IncrementalScoreCalculator]] = field(default=None) - - def _to_java_score_director_factory_config(self, inherited_config: '_JavaScoreDirectorFactoryConfig' = None): - from ai.timefold.solver.core.config.score.director import ( - ScoreDirectorFactoryConfig as JavaScoreDirectorFactoryConfig) - from .._timefold_java_interop import get_class - out = JavaScoreDirectorFactoryConfig() - if inherited_config is not None: - out.inherit(inherited_config) - if self.constraint_provider_function is not None: - from ai.timefold.solver.core.api.score.stream import ConstraintProvider - java_class = get_class(self.constraint_provider_function) - if not issubclass(java_class, ConstraintProvider): - raise TypeError(f'{self.constraint_provider_function} is not a @constraint_provider function. ' - f'Maybe move the @constraint_provider decorator to the top of the decorator list?') - out.setConstraintProviderClass(java_class) # noqa - if self.easy_score_calculator_function is not None: - out.setEasyScoreCalculatorClass(get_class(self.easy_score_calculator_function)) # noqa - if self.incremental_score_calculator_class is not None: - out.setIncrementalScoreCalculatorClass(get_class(self.incremental_score_calculator_class)) # noqa - return out - - -@dataclass(kw_only=True) -class TerminationConfig: - score_calculation_count_limit: Optional[int] = field(default=None) - step_count_limit: Optional[int] = field(default=None) - best_score_feasible: Optional[bool] = field(default=None) - best_score_limit: Optional[str] = field(default=None) - spent_limit: Optional[Duration] = field(default=None) - unimproved_spent_limit: Optional[Duration] = field(default=None) - unimproved_score_difference_threshold: Optional[str] = field(default=None) - unimproved_step_count_limit: Optional[int] = field(default=None) - termination_config_list: Optional[list['TerminationConfig']] = field(default=None) - termination_composition_style: Optional[TerminationCompositionStyle] = field(default=None) - - def _to_java_termination_config(self, inherited_config: '_JavaTerminationConfig' = None) -> \ - '_JavaTerminationConfig': - from java.util import ArrayList - from ai.timefold.solver.core.config.solver.termination import TerminationConfig as JavaTerminationConfig - out = JavaTerminationConfig() - if inherited_config is not None: - out.inherit(inherited_config) - - if self.score_calculation_count_limit is not None: - out.setScoreCalculationCountLimit(self.score_calculation_count_limit) - if self.step_count_limit is not None: - out.setStepCountLimit(self.step_count_limit) - if self.best_score_limit is not None: - out.setBestScoreLimit(self.best_score_limit) - if self.best_score_feasible is not None: - out.setBestScoreFeasible(self.best_score_feasible) - if self.spent_limit is not None: - out.setSpentLimit(self.spent_limit._to_java_duration()) - if self.unimproved_spent_limit is not None: - out.setUnimprovedSpentLimit(self.unimproved_spent_limit._to_java_duration()) - if self.unimproved_score_difference_threshold is not None: - out.setUnimprovedScoreDifferenceThreshold(self.unimproved_score_difference_threshold) - if self.unimproved_step_count_limit is not None: - out.setUnimprovedStepCountLimit(self.unimproved_step_count_limit) - if self.termination_config_list is not None: - termination_config_list = ArrayList() - for termination_config in self.termination_config_list: - termination_config_list.add(termination_config._to_java_termination_config()) - out.setTerminationConfigList(termination_config_list) - if self.termination_composition_style is not None: - out.setTerminationCompositionStyle(self.termination_composition_style._get_java_enum()) - return out - - -@dataclass(kw_only=True) -class SolverConfigOverride: - """ - Includes settings to override default Solver configuration. - - Attributes - ---------- - termination_config: TerminationConfig, optional - sets the solver TerminationConfig. - """ - termination_config: Optional[TerminationConfig] = field(default=None) - - def _to_java_solver_config_override(self): - from ai.timefold.solver.core.api.solver import SolverConfigOverride - out = SolverConfigOverride() - if self.termination_config is not None: - out = out.withTerminationConfig(self.termination_config._to_java_termination_config()) - return out - - -@dataclass(kw_only=True) -class SolverManagerConfig: - """ - Includes settings to configure a SolverManager. - - Attributes - ---------- - parallel_solver_count: int | 'AUTO', optional - If set to an integer, the number of parallel jobs that can be run - simultaneously. - If unset or set to 'AUTO', the number of parallel jobs is determined - based on the number of CPU cores available. - """ - parallel_solver_count: Optional[int | Literal['AUTO']] = field(default=None) - - def _to_java_solver_manager_config(self): - from ai.timefold.solver.core.config.solver import SolverManagerConfig as JavaSolverManagerConfig - out = JavaSolverManagerConfig() - if self.parallel_solver_count is not None: - out = out.withParallelSolverCount(str(self.parallel_solver_count)) - return out - - -__all__ = ['Duration', 'EnvironmentMode', 'TerminationCompositionStyle', - 'RequiresEnterpriseError', 'MoveThreadCount', 'SolverManagerConfig', - 'SolverConfig', 'SolverConfigOverride', 'ScoreDirectorFactoryConfig', 'TerminationConfig'] diff --git a/timefold-solver-python-core/src/main/python/config/py.typed b/timefold-solver-python-core/src/main/python/config/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/timefold-solver-python-core/src/main/python/domain/__init__.py b/timefold-solver-python-core/src/main/python/domain/__init__.py deleted file mode 100644 index b8211eb1..00000000 --- a/timefold-solver-python-core/src/main/python/domain/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Annotations, classes and decorators used to -define the domain of a planning problem. -See `the modeling planning problems section in Timefold Solver documentation -`_. - -Examples --------- ->>> from timefold.domain import PlanningVariable, PlanningId, planning_entity ->>> from typing import Annotated ->>> from datetime import datetime ->>> ->>> class Room: -... id: Annotated[str, PlanningId] -... ->>> class Timeslot: -... id: Annotated[str, PlanningId] -... start: datetime -... end: datetime -... ->>> @planning_entity ->>> class Lesson: -... id: Annotated[str, PlanningId] -... teacher: str -... room: Annotated[Room, PlanningVariable] -... timeslot: Annotated[Timeslot, PlanningVariable] -""" -from ._annotations import * -from ._value_range import * -from ._variable_listener import * -from typing import TYPE_CHECKING as _TYPE_CHECKING - -if _TYPE_CHECKING: - class CountableValueRange: - ... - - -def __getattr__(name: str): - from ._value_range import lookup_value_range_class # noqa - return lookup_value_range_class(name) - - -if not _TYPE_CHECKING: - exported = [name for name in globals().keys() if not name.startswith('_')] - exported += ['CountableValueRange'] - __all__ = exported diff --git a/timefold-solver-python-core/src/main/python/domain/_annotations.py b/timefold-solver-python-core/src/main/python/domain/_annotations.py deleted file mode 100644 index 19da539f..00000000 --- a/timefold-solver-python-core/src/main/python/domain/_annotations.py +++ /dev/null @@ -1,783 +0,0 @@ -import jpype - -from ._variable_listener import VariableListener -from .._timefold_java_interop import ensure_init, get_asm_type -from _jpyinterpreter import JavaAnnotation, AnnotationValueSupplier -from jpype import JImplements, JOverride -from typing import Union, List, Callable, Type, TYPE_CHECKING, TypeVar - -if TYPE_CHECKING: - from ai.timefold.solver.core.api.solver.change import ProblemChange as _ProblemChange - - -Solution_ = TypeVar('Solution_') - - -class PlanningId(JavaAnnotation): - """ - Specifies that an attribute is the id to match when locating - an external object (often from another thread). - Used during Move rebasing and in a ProblemChange. - It is specified on an attribute of a `planning_entity` class, - planning value class or any problem fact class. - The return type can be any comparable type that overrides - ``__eq__`` and ``__hash__``, and is usually ``int`` or ``str``. - It must never return a ``None`` instance. - - Examples - -------- - >>> from timefold.solver.domain import PlanningId - >>> from typing import Annotated - >>> - >>> class Room: - ... id: Annotated[str, PlanningId] - - See Also - -------- - planning_entity - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.lookup import PlanningId as JavaPlanningId - super().__init__(JavaPlanningId, {}) - - -class PlanningPin: - """ - Specifies that a boolean attribute of a `planning_entity` determines if the planning entity is pinned. - A pinned planning entity is never changed during planning. - For example, - it allows the user to pin a shift to a specific employee before solving and the solver will not undo that, - regardless of the constraints. - - The boolean is ``False`` if the planning entity is movable and ``True`` if the planning entity is pinned. - It applies to all the planning variables of that planning entity. - If set on an entity with PlanningListVariable, this will pin the entire list of planning values as well. - This is syntactic sugar for ``@planning_entity(pinning_filter=...)``, - which is a more flexible and verbose way to pin a planning entity. - - Examples - -------- - >>> from timefold.solver.domain import PlanningPin, planning_entity - >>> from typing import Annotated - >>> - >>> @planning_entity - ... class Lesson: - ... is_pinned: Annotated[bool, PlanningPin] - """ - pass - - -class PlanningVariable(JavaAnnotation): - """ - Specifies that an attribute can be changed and should be optimized by the optimization algorithms. - It is specified on an attribute of a `planning_entity` class. - - Examples - -------- - >>> from timefold.solver.domain import PlanningVariable, planning_entity - >>> from typing import Annotated - >>> from domain import Room, Timeslot - >>> - >>> @planning_entity - ... class Lesson: - ... teacher: str - ... room: Annotated[Room, PlanningVariable] - ... timeslot: Annotated[Timeslot, PlanningVariable] - """ - def __init__(self, *, - value_range_provider_refs: List[str] = None, - allows_unassigned: bool = False, - graph_type=None): - ensure_init() - from ai.timefold.solver.core.api.domain.variable import PlanningVariable as JavaPlanningVariable - super().__init__(JavaPlanningVariable, - { - 'valueRangeProviderRefs': value_range_provider_refs, - 'graphType': graph_type, - 'allowsUnassigned': allows_unassigned - }) - - -class PlanningListVariable(JavaAnnotation): - """ - Specifies that an attribute can be changed and should be optimized by the optimization algorithms. - It is specified on an attribute of a `planning_entity` class. - The type of the PlanningListVariable annotated attribute must be ``list[Value]``. - - List variable - ------------- - A planning entity's attribute annotated with `PlanningListVariable` is referred to as a list variable. - The way solver optimizes a list variable is by adding, removing, - or changing order of elements in the `list` object held by the list variable. - - Disjoint lists - -------------- - Furthermore, - the current implementation works under the assumption - that the list variables of all entity instances are "disjoint lists": - - - List means that the order of elements inside a list planning variable is significant. - - Disjoint means that any given pair of entities have no common elements in their list variables. - In other words, each element from the list variable's value range appears in exactly one entity's list variable. - - This makes sense for common use cases, - for example the Vehicle Routing Problem or Task Assigning. - In both cases the order in which customers are visited and tasks are being worked on matters. - Also, each customer must be visited once and each task must be completed by exactly one employee. - - Examples - -------- - >>> from timefold.solver.domain import PlanningListVariable, planning_entity - >>> from typing import Annotated - >>> from domain import Visit - >>> - >>> @planning_entity - ... class Vehicle: - ... visits: Annotated[list[Visit], PlanningListVariable] - - See Also - -------- - PlanningPin - PlanningPinToIndex - """ - def __init__(self, *, - value_range_provider_refs: List[str] = None, - allows_unassigned_values: bool = False): - ensure_init() - from ai.timefold.solver.core.api.domain.variable import PlanningListVariable as JavaPlanningListVariable - super().__init__(JavaPlanningListVariable, - { - 'valueRangeProviderRefs': value_range_provider_refs, - 'allowsUnassignedValues': allows_unassigned_values - }) - - -class ShadowVariable(JavaAnnotation): - """ - Specifies that an attribute is a custom shadow variable of one or more source variables. - The source variable may be a genuine `PlanningVariable`, `PlanningListVariable`, or another shadow variable. - It is specified on an attribute of a `planning_entity` class. - - Examples - -------- - >>> from timefold.solver.domain import ShadowVariable, PreviousElementShadowVariable, planning_entity - >>> from typing import Annotated - >>> from domain import ArrivalTimeVariableListener - >>> from datetime import datetime - >>> - >>> @planning_entity - >>> class Visit: - ... previous: Annotated['Visit', PreviousElementShadowVariable] - ... arrival_time: Annotated[datetime, - ... ShadowVariable( - ... variable_listener_class=ArrivalTimeVariableListener, - ... source_variable_name='previous' - ... ) - ... ] - - See Also - -------- - VariableListener - PiggybackShadowVariable - """ - def __init__(self, *, - variable_listener_class: Type[VariableListener], - source_variable_name: str, - source_entity_class: Type = None): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ShadowVariable as JavaShadowVariable - - super().__init__(JavaShadowVariable, - { - 'variableListenerClass': AnnotationValueSupplier( - lambda: get_asm_type(variable_listener_class)), - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name), - 'sourceEntityClass': source_entity_class, - }) - - -class PiggybackShadowVariable(JavaAnnotation): - """ - Specifies that an attribute is a custom shadow variable that is - updated by another shadow variable's variable listener. - It is specified on an attribute of a `planning_entity` class. - - Examples - -------- - >>> from timefold.solver.domain import ShadowVariable, PreviousElementShadowVariable, planning_entity - >>> from typing import Annotated - >>> from domain import ArrivalTimeVariableListener - >>> from datetime import datetime - >>> - >>> @planning_entity - >>> class Visit: - ... previous: Annotated['Visit', PreviousElementShadowVariable] - ... arrival_time: Annotated[datetime, - ... ShadowVariable( - ... variable_listener_class=ArrivalTimeVariableListener, - ... source_variable_name='previous' - ... ) - ... departure_time: Annotated[datetime, - ... PiggybackShadowVariable(shadow_variable_name='arrival_time') - - See Also - -------- - VariableListener - """ - def __init__(self, *, - shadow_variable_name: str, - shadow_entity_class: Type = None): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ( - PiggybackShadowVariable as JavaPiggybackShadowVariable) - super().__init__(JavaPiggybackShadowVariable, - { - 'shadowVariableName': PythonClassTranslator.getJavaFieldName(shadow_variable_name), - 'shadowEntityClass': shadow_entity_class, - }) - - -class IndexShadowVariable(JavaAnnotation): - """ - Specifies that an attribute is an index of this planning value in another entity's `PlanningListVariable`. - It is specified on an attribute of a `planning_entity` class. - The source variable must be a list variable. - - Examples - -------- - >>> from timefold.solver.domain import IndexShadowVariable, planning_entity - >>> from typing import Annotated - >>> - >>> @planning_entity - ... class Visit: - ... visit_index: Annotated[int, IndexShadowVariable] - """ - def __init__(self, *, - source_variable_name: str): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ( - IndexShadowVariable as JavaIndexShadowVariable) - super().__init__(JavaIndexShadowVariable, - { - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name) - }) - - -class PreviousElementShadowVariable(JavaAnnotation): - """ - Specifies that an attribute references the previous element in the same `PlanningListVariable`. - The previous element's index is one lower than this element's index. - It is ``None`` if this element is the first element in the list variable. - It is specified on an attribute of a `planning_entity` class. - The source variable must be a list variable. - - Examples - -------- - >>> from timefold.solver.domain import PreviousElementShadowVariable, planning_entity - >>> from typing import Annotated - >>> - >>> @planning_entity - >>> class Visit: - ... previous: Annotated['Visit', PreviousElementShadowVariable] - """ - def __init__(self, *, - source_variable_name: str): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ( - PreviousElementShadowVariable as JavaPreviousElementShadowVariable) - super().__init__(JavaPreviousElementShadowVariable, - { - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name) - }) - - -class NextElementShadowVariable(JavaAnnotation): - """ - Specifies that an attribute references the next element in the same `PlanningListVariable`. - The next element's index is one higher than this element's index. - It is ``None`` if this element is the last element in the list variable. - It is specified on an attribute of a `planning_entity` class. - The source variable must be a list variable. - """ - def __init__(self, *, - source_variable_name: str): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ( - NextElementShadowVariable as JavaNextElementShadowVariable) - super().__init__(JavaNextElementShadowVariable, - { - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name) - }) - - -class AnchorShadowVariable(JavaAnnotation): - """ - Specifies that an attribute is the anchor of a chained `PlanningVariable`, - which implies it's a shadow variable. - It is specified on an attribute of a `planning_entity` class. - """ - def __init__(self, *, - source_variable_name: str): - ensure_init() - from ai.timefold.jpyinterpreter import PythonClassTranslator - from ai.timefold.solver.core.api.domain.variable import ( - AnchorShadowVariable as JavaAnchorShadowVariable) - super().__init__(JavaAnchorShadowVariable, - { - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name) - }) - - -class InverseRelationShadowVariable(JavaAnnotation): - """ - Specifies that an attribute is the inverse of a `PlanningVariable`, - which implies it's a shadow variable. - It is specified on an attribute of a `planning_entity` class. - - Examples - -------- - >>> from timefold.solver.domain import InverseRelationShadowVariable, planning_entity - >>> from typing import Annotated - >>> from domain import Vehicle - >>> - >>> @planning_entity - >>> class Visit: - ... vehicle: Annotated[Vehicle, InverseRelationShadowVariable(source_variable_name='visits')] - """ - def __init__(self, *, - source_variable_name: str): - ensure_init() - from ai.timefold.solver.core.api.domain.variable import ( - InverseRelationShadowVariable as JavaInverseRelationShadowVariable) - from ai.timefold.jpyinterpreter import PythonClassTranslator - super().__init__(JavaInverseRelationShadowVariable, - { - 'sourceVariableName': PythonClassTranslator.getJavaFieldName(source_variable_name) - }) - - -class ProblemFactProperty(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class is a problem fact. - A problem fact must not change during solving (except through a `ProblemChange` event). - The constraints in a `timefold.solver.score.ConstraintProvider` rely on problem facts for - `timefold.solver.score.ConstraintFactory.for_each`. - Do not annotate a planning entity or a planning paramerization as a problem fact: - they are automatically available as facts for `timefold.solver.score.ConstraintFactory.for_each`. - - Examples - -------- - >>> from timefold.solver.domain import ProblemFactProperty, planning_solution - >>> from typing import Annotated - >>> from domain import School - >>> - >>> @planning_solution - >>> class Timetable: - ... school: Annotated[School, ProblemFactProperty] - ... # ... - - See Also - -------- - ProblemFactCollectionProperty - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.solution import ( - ProblemFactProperty as JavaProblemFactProperty) - super().__init__(JavaProblemFactProperty, {}) - - -class ProblemFactCollectionProperty(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class is a collection of problem facts. - A problem fact must not change during solving (except through a `ProblemChange` event). - The constraints in a `timefold.solver.score.ConstraintProvider` rely on problem facts for - `timefold.solver.score.ConstraintFactory.for_each`. - Do not annotate a planning entity or a planning paramerization as a problem fact: - they are automatically available as facts for `timefold.solver.score.ConstraintFactory.for_each`. - - Examples - -------- - >>> from timefold.solver.domain import ProblemFactCollectionProperty, planning_solution - >>> from typing import Annotated - >>> from domain import School - >>> - >>> @planning_solution - >>> class Timetable: - ... schools: Annotated[list[School], ProblemFactCollectionProperty] - ... # ... - - See Also - -------- - ProblemFactProperty - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.solution import ( - ProblemFactCollectionProperty as JavaProblemFactCollectionProperty) - super().__init__(JavaProblemFactCollectionProperty, {}) - - -class PlanningEntityProperty(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class is a planning entity. - The planning entity class should be decorated by the `planning_entity` decorator. - The planning entity will be added to the `ScoreDirector`. - - Examples - -------- - >>> from timefold.solver.domain import PlanningEntityProperty, planning_solution - >>> from typing import Annotated - >>> from domain import Lesson - >>> - >>> @planning_solution - >>> class Timetable: - ... lesson: Annotated[Lesson, PlanningEntityProperty] - ... # ... - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.solution import ( - PlanningEntityProperty as JavaPlanningEntityProperty) - super().__init__(JavaPlanningEntityProperty, {}) - - -class PlanningEntityCollectionProperty(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class is a collection of planning entities. - The class of every element in the planning entity collection should be decorated by the `planning_entity` decorator. - Every element in the planning entity collection will be added to the `ScoreDirector`. - - Examples - -------- - >>> from timefold.solver.domain import PlanningEntityCollectionProperty, planning_solution - >>> from typing import Annotated - >>> from domain import Lesson - >>> - >>> @planning_solution - >>> class Timetable: - ... lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty] - ... # ... - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.solution import ( - PlanningEntityCollectionProperty as JavaPlanningEntityCollectionProperty) - super().__init__(JavaPlanningEntityCollectionProperty, {}) - - -class ValueRangeProvider(JavaAnnotation): - """ - Provides the planning values that can be used for a `PlanningVariable`. - This is specified on an attribute which returns a collection (like `list` or `set`) or ValueRange. - A collection is implicitly converted to a ValueRange. - - Examples - -------- - >>> from timefold.solver.domain import ProblemFactCollectionProperty, ValueRangeProvider, planning_solution - >>> from typing import Annotated - >>> from domain import Room - >>> - >>> @planning_solution - >>> class Timetable: - ... rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider] - ... # ... - """ - def __init__(self, *, id: str = None): - ensure_init() - from ai.timefold.solver.core.api.domain.valuerange import ( - ValueRangeProvider as JavaValueRangeProvider) - super().__init__(JavaValueRangeProvider, { - 'id': id - }) - - -class PlanningScore(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class holds the `timefold.score.Score` of that solution. - This attribute can be ``None`` if the planning solution is uninitialized. - This attribute is modified by the Solver, - every time when the `timefold.score.Score` of this planning solution has been calculated. - - Examples - -------- - >>> from timefold.solver.domain import PlanningScore, planning_solution - >>> from timefold.solver.score import HardSoftScore - >>> from typing import Annotated - >>> - >>> @planning_solution - >>> class Timetable: - ... score: Annotated[HardSoftScore, PlanningScore] - ... # ... - """ - def __init__(self, *, - bendable_hard_levels_size: int = None, - bendable_soft_levels_size: int = None): - ensure_init() - from ai.timefold.solver.core.api.domain.solution import ( - PlanningScore as JavaPlanningScore) - super().__init__(JavaPlanningScore, - { - 'bendableHardLevelsSize': bendable_hard_levels_size, - 'bendableSoftLevelsSize': bendable_soft_levels_size - }) - - -class DeepPlanningClone(JavaAnnotation): - """ - Marks a problem fact class as being required to be deep planning cloned. - Not needed for a `planning_solution` or `planning_entity` because those are automatically deep cloned. - It can also mark an attribute as being required to be deep planning cloned. - This is especially useful for `list` (or `dict`) properties. - Not needed for a `list` (or `dist`) attribute with a generic type of `planning_entity`, - because those are automatically deep cloned. - - Notes - ----- - If it annotates an attribute returning `list` (or `dict`), - it clones the `list` (or `dict`), - but its elements (or keys and values) are only cloned if they are of a type that needs to be planning cloned. - - Examples - -------- - >>> from timefold.solver.domain import DeepPlanningClone, ShadowVariable, planning_entity - >>> from datetime import date - >>> from typing import Annotated - >>> - >>> @planning_entity - ... class Employee: - ... work_day_to_hours: Annotated[dict[date, int], ShadowVariable(...), DeepPlanningClone] - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.solution.cloner import ( - DeepPlanningClone as JavaDeepPlanningClone) - super().__init__(JavaDeepPlanningClone, {}) - - -class ConstraintConfigurationProvider(JavaAnnotation): - """ - Specifies that an attribute on a `planning_solution` class is a `constraint_configuration`. - This attribute is automatically a ProblemFactProperty too, so no need to declare that explicitly. - The type of this attribute must be decorated by the `constraint_configuration` decorator. - - Examples - -------- - >>> from timefold.solver.domain import ConstraintConfigurationProvider, planning_solution - >>> from typing import Annotated - >>> from domain import MyConstraintConfiguration - >>> - >>> @planning_solution - ... class Timetable: - ... configuration: Annotated[MyConstraintConfiguration, ConstraintConfigurationProvider] - """ - def __init__(self): - ensure_init() - from ai.timefold.solver.core.api.domain.constraintweight import ( - ConstraintConfigurationProvider as JavaConstraintConfigurationProvider) - super().__init__(JavaConstraintConfigurationProvider, {}) - - -class ConstraintWeight(JavaAnnotation): - """ - Specifies that an attribute set the constraint weight and score level of a constraint. - For example, with a constraint weight of 2soft, - a constraint match penalization with weight multiplier of 3 will result in a Score of -6soft. - It is specified on an attribute of a `constraint_configuration` class. - - Examples - -------- - >>> from timefold.solver.domain import ConstraintWeight, constraint_configuration - >>> from timefold.solver.score import HardSoftScore - >>> from typing import Annotated - >>> - >>> @constraint_configuration - ... class ConstraintConfiguration: - ... maximize_value: Annotated[HardSoftScore, ConstraintWeight('Maximize value')] - """ - def __init__(self, constraint_name: str, *, constraint_package: str = None): - ensure_init() - from ai.timefold.solver.core.api.domain.constraintweight import ConstraintWeight as JavaConstraintWeight - super().__init__(JavaConstraintWeight, { - 'value': constraint_name, - 'constraintPackage': constraint_package - }) - - -@JImplements('ai.timefold.solver.core.api.domain.entity.PinningFilter', deferred=True) -class _PythonPinningFilter: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def accept(self, solution, entity): - return self.delegate(solution, entity) - - -def planning_entity(entity_class: Type = None, /, *, pinning_filter: Callable = None) -> Union[Type, - Callable[[Type], Type]]: - """ - Specifies that the class is a planning entity. - There are two types of entities: - - - Genuine entity - Must have at least one genuine planning variable, and zero or more shadow variables. - - - Shadow entity - Must have at least one shadow variable, and no genuine variables. - - If a planning entity has neither a genuine nor a shadow variable, - it is not a planning entity and the solver will fail fast. - - Parameters - ---------- - pinning_filter : Callable[[Solution, Entity], bool], optional - A pinned planning entity is never changed during planning, - this is useful in repeated planning use cases (such as continuous planning and real-time planning). - This applies to all the planning variables of this planning entity. - - The predicate should return ``False`` if the selection entity is pinned, - and it should return ``True`` if the selection entity is movable - - Examples - -------- - >>> from timefold.solver.domain import PlanningVariable, planning_entity - >>> from typing import Annotated - >>> from domain import Timeslot, Room - >>> - >>> @planning_entity - ... class Lesson: - ... teacher: str - ... room: Annotated[Room, PlanningVariable] - ... timeslot: Annotated[Timeslot, PlanningVariable] - """ - ensure_init() - from ai.timefold.solver.core.api.domain.entity import PlanningEntity as JavaPlanningEntity - - def planning_entity_wrapper(entity_class_argument): - from .._timefold_java_interop import _add_to_compilation_queue - from ai.timefold.solver.core.api.domain.entity import PinningFilter - from _jpyinterpreter import add_class_annotation, translate_python_bytecode_to_java_bytecode - from typing import get_origin, Annotated - - planning_pin_field = None - for name, type_hint in entity_class_argument.__annotations__.items(): - if get_origin(type_hint) == Annotated: - for metadata in type_hint.__metadata__: - if metadata == PlanningPin or isinstance(metadata, PlanningPin): - if planning_pin_field is not None: - raise ValueError(f'Only one attribute can be annotated with PlanningPin, ' - f'but found multiple fields ({planning_pin_field} and {name}).') - planning_pin_field = name - - pinning_filter_function = None - if pinning_filter is not None: - if planning_pin_field is not None: - pinning_filter_function = lambda solution, entity: (getattr(entity, planning_pin_field, False) or - pinning_filter(solution, entity)) - else: - pinning_filter_function = pinning_filter - else: - if planning_pin_field is not None: - pinning_filter_function = lambda solution, entity: getattr(entity, planning_pin_field, False) - - out = add_class_annotation(JavaPlanningEntity, - pinningFilter=pinning_filter_function)(entity_class_argument) - _add_to_compilation_queue(out) - return out - - if entity_class: # Called as @planning_entity - return planning_entity_wrapper(entity_class) - else: # Called as @planning_entity(pinning_filter=some_function) - return planning_entity_wrapper - - -def planning_solution(planning_solution_class: Type[Solution_]) -> Type[Solution_]: - """ - Specifies that the class is a planning solution. - A solution represents a problem and a possible solution of that problem. - A possible solution does not need to be optimal or even feasible. - A solution's planning variables might not be initialized (especially when delivered as a problem). - - A solution is mutable. - For scalability reasons (to facilitate incremental score calculation), - the same solution instance (called the working solution per move thread) is continuously modified. - It's cloned to recall the best solution. - - Each planning solution must have exactly one `PlanningScore` annotated attribute. - Each planning solution must have at least one `PlanningEntityCollectionProperty` or `PlanningEntityProperty` - annotated attribute. - Each planning solution is recommended to have one `ConstraintConfigurationProvider` annotated attribute too. - Each planning solution - used with ConstraintStream score calculation must have at least one `ProblemFactCollectionProperty` or - `ProblemFactProperty` annotated attribute. - - Examples - -------- - >>> from timefold.solver.domain import (PlanningScore, PlanningEntityCollectionProperty, - ... ProblemFactCollectionProperty, ValueRangeProvider, - ... ConstraintConfigurationProvider, planning_solution) - ... from timefold.solver.score import HardSoftScore - >>> from typing import Annotated - >>> from domain import Lesson, Room, Timeslot, TimetablingConstraintConfiguration - >>> - >>> @planning_solution - ... class Timetable: - ... lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty] - ... rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider] - ... timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider] - ... configuration: Annotated[TimetablingConstraintConfiguration, ConstraintConfigurationProvider] - ... score: Annotated[HardSoftScore, PlanningScore] - """ - ensure_init() - from _jpyinterpreter import add_class_annotation - from .._timefold_java_interop import _add_to_compilation_queue - from ai.timefold.solver.core.api.domain.solution import PlanningSolution as JavaPlanningSolution - out = add_class_annotation(JavaPlanningSolution)(planning_solution_class) - _add_to_compilation_queue(planning_solution_class) - return out - - -def constraint_configuration(constraint_configuration_class: Type[Solution_]) -> Type[Solution_]: - """ - Allows end users to change the constraint weights, by not hard coding them. - This decorator specifies that the class holds a number of ConstraintWeight annotated attributes. - That class must also have a weight for each of the constraints. - - A `planning_solution` has at most one attribute annotated with `ConstraintConfigurationProvider` - with returns a type of the `ConstraintConfiguration` decorated class. - - Examples - -------- - >>> from timefold.solver.domain import ConstraintWeight, constraint_configuration - >>> from timefold.solver.score import HardSoftScore - >>> from typing import Annotated - >>> - >>> @constraint_configuration - ... class ConstraintConfiguration: - ... maximize_value: Annotated[HardSoftScore, ConstraintWeight('Maximize value')] - """ - ensure_init() - from _jpyinterpreter import add_class_annotation - from ai.timefold.solver.core.api.domain.constraintweight import ( - ConstraintConfiguration as JavaConstraintConfiguration) - out = add_class_annotation(JavaConstraintConfiguration)(constraint_configuration_class) - return out - - -__all__ = ['PlanningId', 'PlanningScore', 'PlanningPin', 'PlanningVariable', - 'PlanningListVariable', 'ShadowVariable', - 'PiggybackShadowVariable', - 'IndexShadowVariable', 'PreviousElementShadowVariable', 'NextElementShadowVariable', - 'AnchorShadowVariable', 'InverseRelationShadowVariable', - 'ProblemFactProperty', 'ProblemFactCollectionProperty', - 'PlanningEntityProperty', 'PlanningEntityCollectionProperty', - 'ValueRangeProvider', 'DeepPlanningClone', 'ConstraintConfigurationProvider', - 'ConstraintWeight', - 'planning_entity', 'planning_solution', 'constraint_configuration'] diff --git a/timefold-solver-python-core/src/main/python/domain/_value_range.py b/timefold-solver-python-core/src/main/python/domain/_value_range.py deleted file mode 100644 index f328b2d0..00000000 --- a/timefold-solver-python-core/src/main/python/domain/_value_range.py +++ /dev/null @@ -1,113 +0,0 @@ -from .._timefold_java_interop import ensure_init -from typing import TYPE_CHECKING -from decimal import Decimal -if TYPE_CHECKING: - class CountableValueRange: - """ - A set of a values for a PlanningVariable. - These values might be stored in memory as a collection (usually a `list` or `set`), - but if the values are numbers, - they can also be stored in memory by their bounds to use less memory and provide more opportunities. - It always has a discrete (as in non-continuous) range. - """ - ... - - -class ValueRangeFactory: - """ - Factory for `CountableValueRange`. - """ - # Return cannot be typed, since CountableValueRange does not exist in the globals dict - # since it is loaded lazily (to not start the JVM prematurely) - @staticmethod - def create_int_value_range(start: int, end: int, step: int = None): - """ - Build a `CountableValueRange` of all `int` values between two bounds. - - Parameters - ---------- - start : int - The inclusive lower bound of the range. - end : int - The exclusive upper bound of the range. - step : int, optional - The step of the range, defaults to ``1``. - - Examples - -------- - >>> ValueRangeFactory.create_int_value_range(1, 10) - CountableValueRange([1, 2, 3, 4, 5, 6, 7, 8, 9]) - >>> ValueRangeFactory.create_int_value_range(1, 10, 2) - CountableValueRange([1, 3, 5, 7, 9]) - """ - ensure_init() - import jpype.imports - from ai.timefold.solver.python import PythonValueRangeFactory - from java.math import BigInteger - if step is None: - return PythonValueRangeFactory.createIntValueRange(BigInteger(str(start)), BigInteger(str(end))) - else: - return PythonValueRangeFactory.createIntValueRange(BigInteger(str(start)), BigInteger(str(end)), - BigInteger(str(step))) - - @staticmethod - def create_float_value_range(start: Decimal, end: Decimal, step: Decimal = None): - """ - Build a `CountableValueRange` of all `Decimal` values (of a specific scale) between two bounds. - - Parameters - ---------- - start : Decimal - The inclusive lower bound of the range. - end : Decimal - The exclusive upper bound of the range. - step : Decimal, optional - The step of the range, defaults to the lowest positive number - with the same scale as start. - - Examples - -------- - >>> ValueRangeFactory.create_float_value_range(Decimal('1.0'), Decimal('2.0')) - CountableValueRange([1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]) - >>> ValueRangeFactory.create_float_value_range(Decimal('1.0'), Decimal('2.0'), Decimal('0.2')) - CountableValueRange([1.0, 1.2, 1.4, 1.6, 1.8]) - """ - ensure_init() - import jpype.imports - from ai.timefold.solver.python import PythonValueRangeFactory - from java.math import BigDecimal - if step is None: - return PythonValueRangeFactory.createFloatValueRange(BigDecimal(str(start)), BigDecimal(str(end))) - else: - return PythonValueRangeFactory.createFloatValueRange(BigDecimal(str(start)), BigDecimal(str(end)), - BigDecimal(str(step))) - - @staticmethod - def create_bool_value_range(): - """ - Build a CountableValueRange of both boolean values. - - Examples - -------- - >>> ValueRangeFactory.create_bool_value_range() - CountableValueRange([True, False]) - """ - ensure_init() - import jpype.imports - from ai.timefold.solver.python import PythonValueRangeFactory - return PythonValueRangeFactory.createBooleanValueRange() - - -def lookup_value_range_class(name: str): - ensure_init() - import jpype.imports - from ai.timefold.solver.core.api.domain.valuerange import CountableValueRange - match name: - case 'CountableValueRange': - return CountableValueRange - - case _: - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -__all__ = ['ValueRangeFactory'] diff --git a/timefold-solver-python-core/src/main/python/domain/_variable_listener.py b/timefold-solver-python-core/src/main/python/domain/_variable_listener.py deleted file mode 100644 index ee9c031c..00000000 --- a/timefold-solver-python-core/src/main/python/domain/_variable_listener.py +++ /dev/null @@ -1,106 +0,0 @@ -from ..score import ScoreDirector -from _jpyinterpreter import add_java_interface -from typing import TYPE_CHECKING, TypeVar - -if TYPE_CHECKING: - from ai.timefold.solver.core.api.domain.variable import VariableListener - -Solution_ = TypeVar('Solution_') -Entity_ = TypeVar('Entity_') - - -@add_java_interface('ai.timefold.solver.core.api.domain.variable.VariableListener') -class VariableListener: - """ - A listener sourced on a basic PlanningVariable. - - Changes shadow variables when a source basic planning variable changes. - The source variable can be either a genuine or a shadow variable. - - Important: it must only change the shadow variable(s) for which it's configured! - It should never change a genuine variable or a problem fact. - It can change its shadow variable(s) on multiple entity instances - (for example: an arrival_time change affects all trailing entities too). - - It is recommended to keep implementations stateless. - If state must be implemented, - implementations may need to override the methods `reset_working_solution`, and `close`. - """ - def after_entity_added(self, score_director: ScoreDirector, entity) -> None: - pass - - def after_entity_removed(self, score_director: ScoreDirector, entity) -> None: - pass - - def before_entity_added(self, score_director: ScoreDirector, entity) -> None: - pass - - def before_entity_removed(self, score_director: ScoreDirector, entity) -> None: - pass - - def close(self) -> None: - pass - - def reset_working_solution(self, score_director: ScoreDirector) -> None: - pass - - def after_variable_changed(self, score_director: ScoreDirector, entity) -> None: - pass - - def before_variable_changed(self, score_director: ScoreDirector, entity) -> None: - pass - - def requires_unique_entity_events(self) -> bool: - return False - - -if not TYPE_CHECKING: # We do not want these methods to appear in the API - def afterEntityAdded(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).after_entity_added(self, score_director, entity) - - VariableListener.afterEntityAdded = afterEntityAdded - - def afterEntityRemoved(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).after_entity_removed(self, score_director, entity) - - VariableListener.afterEntityRemoved = afterEntityRemoved - - def beforeEntityAdded(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).before_entity_added(self, score_director, entity) - - VariableListener.beforeEntityAdded = beforeEntityAdded - - def beforeEntityRemoved(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).before_entity_removed(self, score_director, entity) - - VariableListener.beforeEntityRemoved = beforeEntityRemoved - - def resetWorkingSolution(self, java_score_director) -> None: - score_director = ScoreDirector(java_score_director) - type(self).reset_working_solution(self, score_director) - - VariableListener.resetWorkingSolution = resetWorkingSolution - - def afterVariableChanged(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).after_variable_changed(self, score_director, entity) - - VariableListener.afterVariableChanged = afterVariableChanged - - def beforeVariableChanged(self, java_score_director, entity) -> None: - score_director = ScoreDirector(java_score_director) - type(self).before_variable_changed(self, score_director, entity) - - VariableListener.beforeVariableChanged = beforeVariableChanged - - def requiresUniqueEntityEvents(self) -> bool: - return type(self).requires_unique_entity_events(self) - - VariableListener.requiresUniqueEntityEvents = requiresUniqueEntityEvents - - -__all__ = ['VariableListener'] diff --git a/timefold-solver-python-core/src/main/python/heuristic/__init__.py b/timefold-solver-python-core/src/main/python/heuristic/__init__.py deleted file mode 100644 index 111ff858..00000000 --- a/timefold-solver-python-core/src/main/python/heuristic/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Classes and decorators to configure heuristics. -""" -from ._nearby_selection import * diff --git a/timefold-solver-python-core/src/main/python/heuristic/_nearby_selection.py b/timefold-solver-python-core/src/main/python/heuristic/_nearby_selection.py deleted file mode 100644 index a28748b4..00000000 --- a/timefold-solver-python-core/src/main/python/heuristic/_nearby_selection.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Callable, TypeVar -from .._timefold_java_interop import ensure_init, register_java_class - -Origin_ = TypeVar('Origin_') -Destination_ = TypeVar('Destination_') - - -def nearby_distance_meter(distance_function: Callable[[Origin_, Destination_], float], /) \ - -> Callable[[Origin_, Destination_], float]: - """ - Decorate a function so it can act as a distance meter for nearby selection. - - The function must have the signature ``(Origin_, Destination_) -> float``. - - The function should measure the distance from the origin to the destination. - The distance can be in any unit, such a meters, foot, seconds or milliseconds. - For example, vehicle routing often uses driving time in seconds. - - Distances can be asymmetrical: - the distance from an origin to a destination often differs from the distance from that destination to that origin. - - Implementations are expected to be stateless. - The solver may choose to reuse instances. - - """ - ensure_init() - from _jpyinterpreter import translate_python_bytecode_to_java_bytecode, generate_proxy_class_for_translated_function - from ai.timefold.solver.core.impl.heuristic.selector.common.nearby import NearbyDistanceMeter # noqa - java_class = generate_proxy_class_for_translated_function(NearbyDistanceMeter, - translate_python_bytecode_to_java_bytecode( - distance_function, - NearbyDistanceMeter)) - return register_java_class(distance_function, java_class) - - -__all__ = ['nearby_distance_meter'] diff --git a/timefold-solver-python-core/src/main/python/py.typed b/timefold-solver-python-core/src/main/python/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/timefold-solver-python-core/src/main/python/score/__init__.py b/timefold-solver-python-core/src/main/python/score/__init__.py deleted file mode 100644 index 2e6f48e1..00000000 --- a/timefold-solver-python-core/src/main/python/score/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Classes and decorators used to define constraints. - -Examples --------- ->>> from timefold.solver.score import ConstraintFactory, Constraint, Joiners, HardSoftScore, constraint_provider ->>> from domain import Lesson ->>> ->>> @constraint_provider -... def timetabling_constraints(cf: ConstraintFactory) -> list[Constraint]: -... return [ -... cf.for_each_unique_pair(Lesson, -... Joiners.equal(lambda lesson: lesson.teacher), -... Joiners.equal(lambda lesson: lesson.timeslot)) -... .penalize(HardSoftScore.ONE_HARD) -... .as_constraint('Overlapping Timeslots') -... ] -""" -from ._annotations import * -from ._constraint_builder import * -from ._constraint_factory import * -from ._constraint_stream import * -from ._function_translator import * -from ._group_by import * -from ._incremental_score_calculator import * -from ._joiners import * -from ._score import * -from ._score_analysis import * -from ._score_director import * diff --git a/timefold-solver-python-core/src/main/python/score/_annotations.py b/timefold-solver-python-core/src/main/python/score/_annotations.py deleted file mode 100644 index fb93ca06..00000000 --- a/timefold-solver-python-core/src/main/python/score/_annotations.py +++ /dev/null @@ -1,96 +0,0 @@ -from ._constraint_factory import ConstraintFactory -from ._constraint_builder import Constraint -from .._timefold_java_interop import ensure_init, _generate_constraint_provider_class, register_java_class -from typing import TypeVar, Callable, TYPE_CHECKING - -if TYPE_CHECKING: - from ..score import Score - -Solution_ = TypeVar('Solution_') - - -def constraint_provider(constraint_provider_function: Callable[[ConstraintFactory], list[Constraint]], /) \ - -> Callable[[ConstraintFactory], list[Constraint]]: - """ - A decorator used to convert a function into a constraint provider. - Used by Constraint Streams' Score calculation. - An implementation must be stateless in order to facilitate - building a single set of constraints independent of potentially changing constraint weights. - - The function must have the signature ``ConstraintFactory -> list[Constraint]``. - - Examples - -------- - >>> from timefold.solver.score import ConstraintFactory, Constraint, Joiners, HardSoftScore, constraint_provider - >>> from domain import Lesson - >>> - >>> @constraint_provider - ... def timetabling_constraints(cf: ConstraintFactory) -> list[Constraint]: - ... return [ - ... cf.for_each_unique_pair(Lesson, - ... Joiners.equal(lambda lesson: lesson.teacher), - ... Joiners.equal(lambda lesson: lesson.timeslot)) - ... .penalize(HardSoftScore.ONE_HARD) - ... .as_constraint('Overlapping Timeslots') - ... ] - - See Also - -------- - Joiners - ConstraintCollectors - ConstraintFactory - UniConstraintStream - """ - ensure_init() - - def constraint_provider_wrapper(function): - def wrapped_constraint_provider(constraint_factory): - from ..score import ConstraintFactory - out = function(ConstraintFactory(constraint_factory)) - return out - java_class = _generate_constraint_provider_class(function, wrapped_constraint_provider) - return register_java_class(wrapped_constraint_provider, java_class) - - return constraint_provider_wrapper(constraint_provider_function) - - -def easy_score_calculator(easy_score_calculator_function: Callable[[Solution_], 'Score']) -> \ - Callable[[Solution_], 'Score']: - """ - Used for easy Python `Score` calculation. - This is non-incremental calculation, which is slow. - An implementation must be stateless. - - The function must have the signature ``Solution_ -> Score``. - - Examples - -------- - >>> from timefold.solver.score import SimpleScore, easy_score_calculator - >>> from domain import Timetable - >>> - >>> @easy_score_calculator - ... def timetabling_constraints(timetable: Timetable) -> SimpleScore: - ... total_score = 0 - ... - ... for lesson_1 in timetable.lessons: - ... for lesson_2 in timetable.lessons: - ... if lesson_1.teacher == lesson_2.teacher and lesson_1.timeslot == lesson_2.timeslot: - ... total_score -= 1 - ... - ... return SimpleScore.of(total_score) - - """ - ensure_init() - from _jpyinterpreter import translate_python_bytecode_to_java_bytecode, generate_proxy_class_for_translated_function - from ai.timefold.solver.core.api.score.calculator import EasyScoreCalculator - - def wrapped_easy_score_calculator(solution): - return easy_score_calculator_function(solution)._to_java_score() - - java_class = generate_proxy_class_for_translated_function(EasyScoreCalculator, - translate_python_bytecode_to_java_bytecode( - wrapped_easy_score_calculator, EasyScoreCalculator)) - return register_java_class(easy_score_calculator_function, java_class) - - -__all__ = ['constraint_provider', 'easy_score_calculator'] diff --git a/timefold-solver-python-core/src/main/python/score/_constraint_builder.py b/timefold-solver-python-core/src/main/python/score/_constraint_builder.py deleted file mode 100644 index 4795c161..00000000 --- a/timefold-solver-python-core/src/main/python/score/_constraint_builder.py +++ /dev/null @@ -1,412 +0,0 @@ -import timefold.solver.score as score_api -from .._jpype_type_conversions import to_python_score -from ._function_translator import function_cast -from ..score._score import Score -from typing import TypeVar, Callable, Generic, Collection, TYPE_CHECKING, Type - -if TYPE_CHECKING: - import jpype.imports - from ai.timefold.solver.core.api.score.stream.uni import UniConstraintBuilder as _JavaUniConstraintBuilder - from ai.timefold.solver.core.api.score.stream.bi import BiConstraintBuilder as _JavaBiConstraintBuilder - from ai.timefold.solver.core.api.score.stream.tri import TriConstraintBuilder as _JavaTriConstraintBuilder - from ai.timefold.solver.core.api.score.stream.quad import QuadConstraintBuilder as _JavaQuadConstraintBuilder - - -A = TypeVar('A') -B = TypeVar('B') -C = TypeVar('C') -D = TypeVar('D') -ScoreType = TypeVar('ScoreType', bound=Score) - - -class Constraint: - """ - This represents a single constraint in the ConstraintStream API that impacts the Score. - It is defined in a function decorated by `constraint_provider` by calling `ConstraintFactory.for_each`. - """ - ... - - -class UniConstraintBuilder(Generic[A, ScoreType]): - """ - Used to build a `Constraint` out of a `UniConstraintStream`, applying optional configuration. - To build the constraint, use one of the terminal operations, such as `as_constraint`. - - Unless `justify_with` is called, the default justification mapping will be used. - The function takes the input arguments and score and converts them to a `DefaultConstraintJustification`. - - Unless `indict_with` is called, the default indicted objects' mapping will be used. - The function takes the input arguments and converts them into a `list`. - """ - delegate: '_JavaUniConstraintBuilder[A, ScoreType]' - a_type: Type[A] - - def __init__(self, delegate: '_JavaUniConstraintBuilder[A, ScoreType]', - a_type: Type[A]) -> None: - self.delegate = delegate - self.a_type = a_type - - def indict_with(self, indictment_function: Callable[[A], Collection]) -> 'UniConstraintBuilder[A, ScoreType]': - """ - Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. - Each object in the collection - returned by this function will become an `Indictment` and be available as a key in - `ScoreExplanation.indictment_map`. - - Parameters - ---------- - indictment_function : Callable[[A], Collection] - the function that returns the indicted objects. - - Returns - ------- - UniConstraintBuilder - this `UniConstraintBuilder`. - """ - return UniConstraintBuilder(self.delegate.indictWith( - function_cast(indictment_function, self.a_type)), self.a_type) - - def justify_with(self, justification_function: Callable[[A, ScoreType], 'score_api.ConstraintJustification']) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Sets a custom function to apply on a constraint match to justify it. - That function must not return a `Collection`, - else a RuntimeError will be raised during score calculation. - - Parameters - ---------- - justification_function : Callable[[A, ScoreType], ConstraintJustification] - the function that returns the justification. - - Returns - ------- - UniConstraintBuilder - this `UniConstraintBuilder`. - """ - from ai.timefold.solver.core.api.score import Score - - def wrapped(a, score): - return justification_function(a, to_python_score(score)) - - return UniConstraintBuilder(self.delegate.justifyWith( - function_cast(wrapped, self.a_type, Score)), self.a_type) - - def as_constraint(self, constraint_package_or_name: str, constraint_name: str = None) -> Constraint: - """ - Builds a Constraint from the constraint stream. - - Parameters - ---------- - constraint_package_or_name : str - If `constraint_name` is also provided, this is the constraint package name. - Otherwise, this is the constraint name. - The constraint package defaults to the module of the `planning_solution` class. - - constraint_name : str, optional - The constraint name. - If present, `constraint_package_or_name` is treated as the constraint package name. - - Returns - ------- - Constraint - A `Constraint`. - """ - if constraint_name is None: - return self.delegate.asConstraint(constraint_package_or_name) - else: - return self.delegate.asConstraint(constraint_package_or_name, constraint_name) - - -class BiConstraintBuilder(Generic[A, B, ScoreType]): - """ - Used to build a `Constraint` out of a `UniConstraintStream`, applying optional configuration. - To build the constraint, use one of the terminal operations, such as `as_constraint`. - - Unless `justify_with` is called, the default justification mapping will be used. - The function takes the input arguments and score and converts them to a `DefaultConstraintJustification`. - - Unless `indict_with` is called, the default indicted objects' mapping will be used. - The function takes the input arguments and converts them into a `list`. - """ - delegate: '_JavaBiConstraintBuilder[A, B, ScoreType]' - a_type: Type[A] - b_type: Type[B] - - def __init__(self, delegate: '_JavaBiConstraintBuilder[A, B, ScoreType]', - a_type: Type[A], b_type: Type[B]) -> None: - self.delegate = delegate - self.a_type = a_type - self.b_type = b_type - - def indict_with(self, indictment_function: Callable[[A, B], Collection]) -> 'BiConstraintBuilder[A, B, ScoreType]': - """ - Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. - Each object in the collection - returned by this function will become an `Indictment` and be available as a key in - `ScoreExplanation.indictment_map`. - - Parameters - ---------- - indictment_function : Callable[[A, B], Collection] - the function that returns the indicted objects. - - Returns - ------- - BiConstraintBuilder - this `BiConstraintBuilder`. - """ - return BiConstraintBuilder(self.delegate.indictWith( - function_cast(indictment_function, self.a_type, self.b_type)), self.a_type, self.b_type) - - def justify_with(self, justification_function: Callable[[A, B, ScoreType], - 'score_api.ConstraintJustification']) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Sets a custom function to apply on a constraint match to justify it. - That function must not return a `Collection`, - else a RuntimeError will be raised during score calculation. - - Parameters - ---------- - justification_function : Callable[[A, B, ScoreType], ConstraintJustification] - the function that returns the justification. - - Returns - ------- - BiConstraintBuilder - this `BiConstraintBuilder`. - """ - from ai.timefold.solver.core.api.score import Score - - def wrapped(a, b, score): - return justification_function(a, b, to_python_score(score)) - - return BiConstraintBuilder(self.delegate.justifyWith( - function_cast(wrapped, self.a_type, self.b_type, Score)), self.a_type, self.b_type) - - def as_constraint(self, constraint_package_or_name: str, constraint_name: str = None) -> Constraint: - """ - Builds a Constraint from the constraint stream. - - Parameters - ---------- - constraint_package_or_name : str - If `constraint_name` is also provided, this is the constraint package name. - Otherwise, this is the constraint name. - The constraint package defaults to the module of the `planning_solution` class. - - constraint_name : str, optional - The constraint name. - If present, `constraint_package_or_name` is treated as the constraint package name. - - Returns - ------- - Constraint - A `Constraint`. - """ - if constraint_name is None: - return self.delegate.asConstraint(constraint_package_or_name) - else: - return self.delegate.asConstraint(constraint_package_or_name, constraint_name) - - -class TriConstraintBuilder(Generic[A, B, C, ScoreType]): - """ - Used to build a `Constraint` out of a `UniConstraintStream`, applying optional configuration. - To build the constraint, use one of the terminal operations, such as `as_constraint`. - - Unless `justify_with` is called, the default justification mapping will be used. - The function takes the input arguments and score and converts them to a `DefaultConstraintJustification`. - - Unless `indict_with` is called, the default indicted objects' mapping will be used. - The function takes the input arguments and converts them into a `list`. - """ - delegate: '_JavaTriConstraintBuilder[A, B, C, ScoreType]' - a_type: Type[A] - b_type: Type[B] - c_type: Type[C] - - def __init__(self, delegate: '_JavaTriConstraintBuilder[A, B, C, ScoreType]', - a_type: Type[A], b_type: Type[B], c_type: Type[C]) -> None: - self.delegate = delegate - self.a_type = a_type - self.b_type = b_type - self.c_type = c_type - - def indict_with(self, indictment_function: Callable[[A, B, C], Collection]) -> \ - 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. - Each object in the collection - returned by this function will become an `Indictment` and be available as a key in - `ScoreExplanation.indictment_map`. - - Parameters - ---------- - indictment_function : Callable[[A, B, C], Collection] - the function that returns the indicted objects. - - Returns - ------- - TriConstraintBuilder - this `TriConstraintBuilder`. - """ - return TriConstraintBuilder(self.delegate.indictWith( - function_cast(indictment_function, self.a_type, self.b_type, self.c_type)), self.a_type, self.b_type, - self.c_type) - - def justify_with(self, justification_function: Callable[[A, B, C, ScoreType], - 'score_api.ConstraintJustification']) -> \ - 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Sets a custom function to apply on a constraint match to justify it. - That function must not return a `Collection`, - else a RuntimeError will be raised during score calculation. - - Parameters - ---------- - justification_function : Callable[[A, B, C, ScoreType], ConstraintJustification] - the function that returns the justification. - - Returns - ------- - TriConstraintBuilder - this `TriConstraintBuilder`. - """ - from ai.timefold.solver.core.api.score import Score - - def wrapped(a, b, c, score): - return justification_function(a, b, c, to_python_score(score)) - - return TriConstraintBuilder(self.delegate.justifyWith( - function_cast(wrapped, self.a_type, self.b_type, self.c_type, Score)), - self.a_type, self.b_type, self.c_type) - - def as_constraint(self, constraint_package_or_name: str, constraint_name: str = None) -> Constraint: - """ - Builds a Constraint from the constraint stream. - - Parameters - ---------- - constraint_package_or_name : str - If `constraint_name` is also provided, this is the constraint package name. - Otherwise, this is the constraint name. - The constraint package defaults to the module of the `planning_solution` class. - - constraint_name : str, optional - The constraint name. - If present, `constraint_package_or_name` is treated as the constraint package name. - - Returns - ------- - Constraint - A `Constraint`. - """ - if constraint_name is None: - return self.delegate.asConstraint(constraint_package_or_name) - else: - return self.delegate.asConstraint(constraint_package_or_name, constraint_name) - - -class QuadConstraintBuilder(Generic[A, B, C, D, ScoreType]): - """ - Used to build a `Constraint` out of a `UniConstraintStream`, applying optional configuration. - To build the constraint, use one of the terminal operations, such as `as_constraint`. - - Unless `justify_with` is called, the default justification mapping will be used. - The function takes the input arguments and score and converts them to a `DefaultConstraintJustification`. - - Unless `indict_with` is called, the default indicted objects' mapping will be used. - The function takes the input arguments and converts them into a `list`. - """ - delegate: '_JavaQuadConstraintBuilder[A, B, C, D, ScoreType]' - a_type: Type[A] - b_type: Type[B] - c_type: Type[C] - d_type: Type[D] - - def __init__(self, delegate: '_JavaQuadConstraintBuilder[A, B, C, D, ScoreType]', - a_type: Type[A], b_type: Type[B], c_type: Type[C], d_type: Type[D]) -> None: - self.delegate = delegate - self.a_type = a_type - self.b_type = b_type - self.c_type = c_type - self.d_type = d_type - - def indict_with(self, indictment_function: Callable[[A, B, C, D], Collection]) -> \ - 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. - Each object in the collection - returned by this function will become an `Indictment` and be available as a key in - `ScoreExplanation.indictment_map`. - - Parameters - ---------- - indictment_function : Callable[[A, B, C, D], Collection] - the function that returns the indicted objects. - - Returns - ------- - QuadConstraintBuilder - this `QuadConstraintBuilder`. - """ - return QuadConstraintBuilder(self.delegate.indictWith( - function_cast(indictment_function, self.a_type, self.b_type, self.c_type, self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def justify_with(self, justification_function: Callable[[A, B, C, D, ScoreType], - 'score_api.ConstraintJustification']) \ - -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Sets a custom function to apply on a constraint match to justify it. - That function must not return a `Collection`, - else a RuntimeError will be raised during score calculation. - - Parameters - ---------- - justification_function : Callable[[A, B, C, D, ScoreType], ConstraintJustification] - the function that returns the justification. - - Returns - ------- - QuadConstraintBuilder - this `QuadConstraintBuilder`. - """ - from ai.timefold.solver.core.api.score import Score - - def wrapped(a, b, c, d, score): - return justification_function(a, b, c, d, to_python_score(score)) - - return QuadConstraintBuilder(self.delegate.justifyWith( - function_cast(wrapped, self.a_type, self.b_type, self.c_type, self.d_type, Score)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def as_constraint(self, constraint_package_or_name: str, constraint_name: str = None) -> Constraint: - """ - Builds a Constraint from the constraint stream. - - Parameters - ---------- - constraint_package_or_name : str - If `constraint_name` is also provided, this is the constraint package name. - Otherwise, this is the constraint name. - The constraint package defaults to the module of the `planning_solution` class. - - constraint_name : str, optional - The constraint name. - If present, `constraint_package_or_name` is treated as the constraint package name. - - Returns - ------- - Constraint - A `Constraint`. - """ - if constraint_name is None: - return self.delegate.asConstraint(constraint_package_or_name) - else: - return self.delegate.asConstraint(constraint_package_or_name, constraint_name) - - -__all__ = ['Constraint', - 'UniConstraintBuilder', 'BiConstraintBuilder', 'TriConstraintBuilder', 'QuadConstraintBuilder'] diff --git a/timefold-solver-python-core/src/main/python/score/_constraint_factory.py b/timefold-solver-python-core/src/main/python/score/_constraint_factory.py deleted file mode 100644 index 475e8fc1..00000000 --- a/timefold-solver-python-core/src/main/python/score/_constraint_factory.py +++ /dev/null @@ -1,93 +0,0 @@ -from .._timefold_java_interop import get_class -from typing import TYPE_CHECKING, Type, TypeVar, cast -if TYPE_CHECKING: - import jpype.imports # noqa - from ai.timefold.solver.core.api.score.stream import ConstraintFactory as _JavaConstraintFactory - from ai.timefold.solver.core.api.score.stream.bi import BiJoiner - - -class ConstraintFactory: - """ - The factory to create every ConstraintStream (for example with `for_each`) - which ends in a `Constraint` returned by a function decorated with `constraint_provider`. - """ - delegate: '_JavaConstraintFactory' - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - def __init__(self, delegate: '_JavaConstraintFactory'): - self.delegate = delegate - - def get_default_constraint_package(self) -> str: - """ - This is `constraint_configuration(constraint_package=...)` if available, - otherwise the module of the `constraint_provider` function. - """ - return self.delegate.getDefaultConstraintPackage() - - def for_each(self, source_class: Type[A_]) -> 'UniConstraintStream[A_]': - """ - Start a ConstraintStream of all instances of the `source_class` - that are known as problem facts or planning entities. - If the `source_class` is a `planning_entity`, - then it is automatically filtered to only contain entities for which each genuine `PlanningVariable` - (of the `source_class` or a superclass thereof) has a non-None value. - - If the `source_class` is a shadow entity (an entity without any genuine planning variables), - and if there exists a genuine `planning_entity` with a `PlanningListVariable` which accepts instances of this - shadow entity as values in that list, and if that list variable allows unassigned values, then this stream will - filter out all `source_class` instances which are not present in any instances of that list variable. - This is achieved in one of two ways: - - - If the `source_class` - has `InverseRelationShadowVariable` field referencing instance of an entity with the list variable, - the value of that field will be used to determine if the value is assigned. - ``None`` in that field means the instance of `source_class` is unassigned. - - - As fallback, - the value is considered assigned if there exists an instance of the entity - where its list variable contains the value. - This will perform significantly worse - and only exists so that using the `InverseRelationShadowVariable` can remain optional. - Adding the field is strongly recommended. - """ - source_class = get_class(source_class) - return UniConstraintStream(self.delegate.forEach(source_class), self.get_default_constraint_package(), - cast(Type['A_'], source_class)) - - def for_each_including_unassigned(self, source_class: Type[A_]) -> 'UniConstraintStream[A_]': - """ - As defined by `for_each`, - but without any filtering of unassigned planning entities - (for ``PlanningVariable(allows_unassigned=True)``) - or shadow entities not assigned to any applicable list variable - (for ``PlanningListVariable(allows_unassigned_values=True)``). - """ - source_class = get_class(source_class) - return UniConstraintStream(self.delegate.forEachIncludingUnassigned(source_class), - self.get_default_constraint_package(), - cast(Type['A_'], source_class)) - - def for_each_unique_pair(self, source_class: Type[A_], *joiners: 'BiJoiner[A_, A_]') -> \ - 'BiConstraintStream[A_, A_]': - """ - Create a new `BiConstraintStream` for every unique combination of A and another A with a higher `PlanningId` - for which every `BiJoiner` is true (for the properties it extracts from both facts). - """ - source_class = get_class(source_class) - return BiConstraintStream(self.delegate.forEachUniquePair(source_class, - extract_joiners(joiners, source_class, source_class)), - self.get_default_constraint_package(), - cast(Type['A_'], source_class), - cast(Type['A_'], source_class)) - - -from ._constraint_stream import * -from ._joiners import extract_joiners - -__all__ = [ - 'ConstraintFactory' -] diff --git a/timefold-solver-python-core/src/main/python/score/_constraint_stream.py b/timefold-solver-python-core/src/main/python/score/_constraint_stream.py deleted file mode 100644 index bcdf128c..00000000 --- a/timefold-solver-python-core/src/main/python/score/_constraint_stream.py +++ /dev/null @@ -1,3476 +0,0 @@ -from .._timefold_java_interop import get_class -import jpype.imports # noqa -from jpype import JClass -from typing import TYPE_CHECKING, Type, Callable, overload, TypeVar, Generic, Any, Union, cast -from decimal import Decimal - -if TYPE_CHECKING: - from ai.timefold.solver.core.api.score.stream.uni import (UniConstraintCollector, - UniConstraintStream as _JavaUniConstraintStream) - from ai.timefold.solver.core.api.score.stream.bi import (BiJoiner, BiConstraintCollector, - BiConstraintStream as _JavaBiConstraintStream) - from ai.timefold.solver.core.api.score.stream.tri import (TriJoiner, TriConstraintCollector, - TriConstraintStream as _JavaTriConstraintStream) - from ai.timefold.solver.core.api.score.stream.quad import (QuadJoiner, QuadConstraintCollector, - QuadConstraintStream as _JavaQuadConstraintStream) - from ai.timefold.solver.core.api.score.stream.penta import PentaJoiner - -# Class type variables -A = TypeVar('A') -B = TypeVar('B') -C = TypeVar('C') -D = TypeVar('D') -ScoreType = TypeVar('ScoreType', bound='Score') - - -class UniConstraintStream(Generic[A]): - """ - A ConstraintStream that matches one fact. - """ - delegate: '_JavaUniConstraintStream[A]' - package: str - a_type: Type[A] - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - def __init__(self, delegate: '_JavaUniConstraintStream[A]', package: str, - a_type: Type[A]): - self.delegate = delegate - self.package = package - self.a_type = a_type - - def get_constraint_factory(self): - """ - The ConstraintFactory that build this. - """ - return ConstraintFactory(self.delegate.getConstraintFactory()) - - def filter(self, predicate: Callable[[A], bool]) -> 'UniConstraintStream[A]': - """ - Exhaustively test each fact against the predicate and match if the predicate returns ``True``. - """ - translated_predicate = predicate_cast(predicate, self.a_type) - return UniConstraintStream(self.delegate.filter(translated_predicate), self.package, - self.a_type) - - def join(self, unistream_or_type: Union['UniConstraintStream[B_]', Type[B_]], *joiners: 'BiJoiner[A, B_]') -> \ - 'BiConstraintStream[A,B_]': - """ - Create a new `BiConstraintStream` for every combination of A and B that satisfies all specified joiners. - """ - b_type = None - if isinstance(unistream_or_type, UniConstraintStream): - b_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - b_type = get_class(unistream_or_type) - unistream_or_type = b_type - - join_result = self.delegate.join(unistream_or_type, extract_joiners(joiners, self.a_type, b_type)) - return BiConstraintStream(join_result, self.package, - self.a_type, b_type) - - @overload - def if_exists(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> 'UniConstraintStream[A]': - ... - - @overload - def if_exists(self, other_stream: 'UniConstraintStream[B_]', *joiners: 'BiJoiner[A, B_]') \ - -> 'UniConstraintStream[A]': - ... - - def if_exists(self, unistream_or_type: Union['UniConstraintStream[B_]', Type[B_]], - *joiners: 'BiJoiner[A, B_]') -> 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where B exists that satisfies all specified joiners. - """ - b_type = None - if isinstance(unistream_or_type, UniConstraintStream): - b_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - b_type = get_class(unistream_or_type) - unistream_or_type = b_type - return UniConstraintStream(self.delegate.ifExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, b_type)), - self.package, self.a_type) - - def if_exists_including_unassigned(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> \ - 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where B exists that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, item_type)), - self.package, self.a_type) - - def if_exists_other(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A, if another A exists that does not equal the first, - and for which all specified joiners are satisfied. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifExistsOther(cast(Type['A_'], item_type), - extract_joiners(joiners, - self.a_type, item_type)), - self.package, self.a_type) - - def if_exists_other_including_unassigned(self, item_type: Type, *joiners: 'BiJoiner') -> \ - 'UniConstraintStream': - """ - Create a new UniConstraintStream for every A, if another A exists that does not equal the first. - For classes decorated with `planning_entity`, this method also includes entities with ``None`` variables, - or entities that are not assigned to any list variable. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifExistsOtherIncludingUnassigned(cast(Type['A_'], item_type), - extract_joiners(joiners, - self.a_type, item_type)), - self.package, self.a_type) - - @overload - def if_not_exists(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> 'UniConstraintStream[A]': - ... - - @overload - def if_not_exists(self, other_stream: 'UniConstraintStream[B_]', *joiners: 'BiJoiner[A, B_]') \ - -> 'UniConstraintStream[A]': - ... - - def if_not_exists(self, unistream_or_type: Union['UniConstraintStream[B_]', Type[B_]], - *joiners: 'BiJoiner[A, B_]') -> 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where B does not exist that satisfies all specified joiners. - """ - b_type = None - if isinstance(unistream_or_type, UniConstraintStream): - b_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - b_type = get_class(unistream_or_type) - unistream_or_type = b_type - return UniConstraintStream(self.delegate.ifNotExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, b_type)), - self.package, self.a_type) - - def if_not_exists_including_unassigned(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> \ - 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where B does not exist that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifNotExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, item_type)), - self.package, self.a_type) - - def if_not_exists_other(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> \ - 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where B does not exist that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifNotExistsOther(cast(Type['A_'], item_type), - extract_joiners(joiners, - self.a_type, - item_type)), - self.package, self.a_type) - - def if_not_exists_other_including_unassigned(self, item_type: Type[B_], *joiners: 'BiJoiner[A, B_]') -> \ - 'UniConstraintStream[A]': - """ - Create a new `UniConstraintStream` for every A where a different A does not exist - that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return UniConstraintStream(self.delegate.ifNotExistsOtherIncludingUnassigned(cast(Type['A_'], item_type), - extract_joiners(joiners, - self.a_type, item_type)), - self.package, self.a_type) - - @overload - def group_by(self, key_mapping: Callable[[A], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, collector: 'UniConstraintCollector[A, Any, A_]') -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], - second_key_mapping: Callable[[A], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A], A_], - collector: 'UniConstraintCollector[A, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_collector: 'UniConstraintCollector[A, Any, A_]', - second_collector: 'UniConstraintCollector[A, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], second_key_mapping: Callable[[A], B_], - third_key_mapping: Callable[[A], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], second_key_mapping: Callable[[A], B_], - collector: 'UniConstraintCollector[A, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A], A_], first_collector: 'UniConstraintCollector[A, Any, B_]', - second_collector: 'UniConstraintCollector[A, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_collector: 'UniConstraintCollector[A, Any, A_]', - second_collector: 'UniConstraintCollector[A, Any, B_]', - third_collector: 'UniConstraintCollector[A, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], second_key_mapping: Callable[[A], B_], - third_key_mapping: Callable[[A], C_], - fourth_key_mapping: Callable[[A], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], second_key_mapping: Callable[[A], B_], - third_key_mapping: Callable[[A], C_], - collector: 'UniConstraintCollector[A, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A], A_], second_key_mapping: Callable[[A], B_], - first_collector: 'UniConstraintCollector[A, Any, C_]', - second_collector: 'UniConstraintCollector[A, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A], A_], first_collector: 'UniConstraintCollector[A, Any, B_]', - second_collector: 'UniConstraintCollector[A, Any, C_]', - third_collector: 'UniConstraintCollector[A, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_collector: 'UniConstraintCollector[A, Any, A_]', - second_collector: 'UniConstraintCollector[A, Any, B_]', - third_collector: 'UniConstraintCollector[A, Any, C_]', - fourth_collector: 'UniConstraintCollector[A, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def group_by(self, *args): - """ - Collect items into groups using the group_key_function(s) and optionally aggregate the group's items into a - result. - - The syntax of group_by is zero to four group_key functions, followed by zero to four collectors. At most - four arguments can be passed to group_by. - - If no group_key function is passed to group_by, all items in the stream are aggregated into a single result - by the passed constraint collectors. - - Returns - ------- - UniConstraintStream | BiConstraintStream | TriConstraintStream | QuadConstraintStream - The type of stream returned depends on the number of arguments passed: - - - 1 -> UniConstraintStream - - - 2 -> BiConstraintStream - - - 3 -> TriConstraintStream - - - 4 -> QuadConstraintStream - - Examples - -------- - - Count the items in this stream; returns Uni[int] - - >>> group_by(ConstraintCollectors.count()) - - Count the number of shifts each employee has; returns Bi[Employee] - - >>> group_by(lambda shift: shift.employee, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Get the dates of the first and last shift of each employee; returns Tri[Employee, datetime.date, datetime.date] - - >>> group_by(lambda shift: shift.employee, - ... ConstraintCollectors.min(lambda shift: shift.date) - ... ConstraintCollectors.max(lambda shift: shift.date)) - """ - return perform_group_by(self.delegate, self.package, args, self.a_type) - - @overload - def map(self, mapping_function: Callable[[A], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def map(self, mapping_function: Callable[[A], A_], - mapping_function2: Callable[[A], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def map(self, mapping_function: Callable[[A], A_], mapping_function2: Callable[[A], B_], - mapping_function3: Callable[[A], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def map(self, mapping_function: Callable[[A], A_], mapping_function2: Callable[[A], B_], - mapping_function3: Callable[[A], C_], - mapping_function4: Callable[[A], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def map(self, *mapping_functions): - """ - Transforms the stream in such a way that tuples are remapped using the given function. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for map.') - if len(mapping_functions) > 4: - raise ValueError(f'At most four mapping functions can be passed to map (got {len(mapping_functions)}).') - translated_functions = tuple(map(lambda mapping_function: function_cast(mapping_function, self.a_type), - mapping_functions)) - if len(mapping_functions) == 1: - return UniConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return BiConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object')) - if len(mapping_functions) == 3: - return TriConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - if len(mapping_functions) == 4: - return QuadConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - @overload - def expand(self, mapping_function: Callable[[A], B_]) -> 'BiConstraintStream[A, B_]': - ... - - @overload - def expand(self, mapping_function: Callable[[A], B_], - mapping_function2: Callable[[A], C_]) -> 'TriConstraintStream[A, B_, C_]': - ... - - @overload - def expand(self, mapping_function: Callable[[A], B_], mapping_function2: Callable[[A], C_], - mapping_function3: Callable[[A], D_]) -> 'TriConstraintStream[A, B_, C_, D_]': - ... - - def expand(self, *mapping_functions): - """ - Tuple expansion is a special case of tuple mapping - which only increases stream cardinality and can not introduce duplicate tuples. - It enables you to add extra facts to each tuple in a constraint stream by applying a mapping function to it. - This is useful in situations where an expensive computations needs to be cached for use later in the stream. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for expand.') - if len(mapping_functions) > 3: - raise ValueError( - f'At most three mapping functions can be passed to expand on a UniStream ' - f'(got {len(mapping_functions)}).') - translated_functions = tuple(map(lambda mapping_function: function_cast(mapping_function, self.a_type), - mapping_functions)) - if len(mapping_functions) == 1: - return BiConstraintStream(self.delegate.expand(*translated_functions), self.package, - - self.a_type, JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return TriConstraintStream(self.delegate.expand(*translated_functions), self.package, - - self.a_type, JClass('java.lang.Object'), JClass('java.lang.Object')) - if len(mapping_functions) == 3: - return QuadConstraintStream(self.delegate.expand(*translated_functions), self.package, - - self.a_type, JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - def flatten_last(self, flattening_function: Callable[[A], A_]) -> 'UniConstraintStream[A_]': - """ - Takes each tuple and applies a mapping on it, which turns the tuple into an Iterable. - """ - translated_function = function_cast(flattening_function, self.a_type) - return UniConstraintStream(self.delegate.flattenLast(translated_function), self.package, - - JClass('java.lang.Object')) - - def distinct(self) -> 'UniConstraintStream[A]': - """ - Transforms the stream in such a way that all the tuples going through it are distinct. - """ - return UniConstraintStream(self.delegate.distinct(), self.package, self.a_type) - - @overload - def concat(self, other: 'UniConstraintStream[A]') -> 'UniConstraintStream[A]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B_]') -> 'BiConstraintStream[A, B_]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B_]', padding_b: Callable[[A], B_]) -> 'BiConstraintStream[A, B_]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B_, C_]') -> 'TriConstraintStream[A, B_, C_]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B_, C_]', padding_b: Callable[[A], B_], - padding_c: Callable[[A], C_]) -> 'TriConstraintStream[A, B_, C_]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B_, C_, D_]') -> 'QuadConstraintStream[A, B_, C_, D_]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B_, C_, D_]', padding_b: Callable[[A], B_], - padding_c: Callable[[A], C_], padding_d: Callable[[A], D_]) -> 'QuadConstraintStream[A, B_, C_, D_]': - ... - - def concat(self, other, padding_b=None, padding_c=None, padding_d=None): - """ - The concat building block allows you - to create a constraint stream containing tuples of two other constraint streams. - If join acts like a cartesian product of two lists, concat acts like a concatenation of two lists. - Unlike union of sets, concatenation of lists repeats duplicated elements. - If the two constraint concatenating streams share tuples, which happens e.g. - when they come from the same source of data, the tuples will be repeated downstream. - If this is undesired, use the distinct building block. - """ - specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d]) - if isinstance(other, UniConstraintStream): - if specified_count == 0: - return UniConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type) - else: - raise ValueError(f'Concatenating UniConstraintStreams requires no padding functions, ' - f'got {specified_count} instead.') - elif isinstance(other, BiConstraintStream): - if specified_count == 0: - return BiConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, other.b_type) - elif specified_count > 1: - raise ValueError(f'Concatenating Uni and BiConstraintStream requires 1 padding function, ' - f'got {specified_count} instead.') - elif padding_b is None: - raise ValueError(f'Concatenating Uni and BiConstraintStream requires padding_b to be provided.') - return BiConstraintStream(self.delegate.concat(other.delegate, padding_b), self.package, - self.a_type, other.b_type) - elif isinstance(other, TriConstraintStream): - if specified_count == 0: - return TriConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, other.b_type, other.c_type) - elif specified_count != 2: - raise ValueError(f'Concatenating Uni and TriConstraintStream requires 2 padding functions, ' - f'got {specified_count} instead.') - elif padding_d is not None: - raise ValueError(f'Concatenating Uni and TriConstraintStream requires ' - f'padding_b and padding_c to be provided.') - return TriConstraintStream(self.delegate.concat(other.delegate, padding_b, padding_c), self.package, - self.a_type, other.b_type, other.c_type) - elif isinstance(other, QuadConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), - self.package, self.a_type, other.b_type, other.c_type, other.d_type) - elif specified_count != 3: - raise ValueError(f'Concatenating Uni and QuadConstraintStream requires 3 padding functions, ' - f'got {specified_count} instead.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_b, padding_c, padding_d), - self.package, self.a_type, other.b_type, other.c_type, other.d_type) - else: - raise RuntimeError(f'Unhandled constraint stream type {type(other)}.') - - def complement(self, cls: type[A]) -> 'UniConstraintStream[A]': - """ - Adds to the stream all instances of a given class which are not yet present in it. - These instances must be present in the solution, - which means the class needs to be either a planning entity or a problem fact. - - Parameters - ---------- - cls : Type[A] - the type of the instances to add to the stream. - """ - result = self.delegate.complement(get_class(cls)) - return UniConstraintStream(result, self.package, self.a_type) - - def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], int], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.penalizeLong(constraint_weight, - to_long_function_cast(match_weigher, self.a_type)), - self.a_type) - - def penalize_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A], Decimal] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.penalizeBigDecimal(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.penalizeBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.rewardLong(constraint_weight, - to_long_function_cast(match_weigher, self.a_type)), - self.a_type) - - def reward_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A], Decimal] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.rewardBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.impactLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type)), - self.a_type) - - def impact_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A], Decimal] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type) - else: - return UniConstraintBuilder(self.delegate.impactBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - def penalize_configurable(self, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.penalizeConfigurableLong(to_long_function_cast(match_weigher, - self.a_type)), - self.a_type) - - def penalize_configurable_decimal(self, match_weigher: Callable[[A], Decimal] = None) \ - -> 'UniConstraintBuilder[A, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.penalizeConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - def reward_configurable(self, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.rewardConfigurableLong(to_long_function_cast(match_weigher, - self.a_type)), - self.a_type) - - def reward_configurable_decimal(self, match_weigher: Callable[[A], Decimal] = None) \ - -> 'UniConstraintBuilder[A, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.rewardConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - def impact_configurable(self, match_weigher: Callable[[A], int] = None) -> \ - 'UniConstraintBuilder[A, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - if match_weigher is None: - return UniConstraintBuilder(self.delegate.impactConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.impactConfigurableLong(to_long_function_cast(match_weigher, - self.a_type)), - self.a_type) - - - def impact_configurable_decimal(self, match_weigher: Callable[[A], Decimal] = None) \ - -> 'UniConstraintBuilder[A, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A], Decimal], optional - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - UniConstraintBuilder - a `UniConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return UniConstraintBuilder(self.delegate.impactConfigurable(), self.a_type) - else: - return UniConstraintBuilder(self.delegate.impactConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - return_type=BigDecimal)), - self.a_type) - - -class BiConstraintStream(Generic[A, B]): - """ - A ConstraintStream that matches two facts. - """ - delegate: '_JavaBiConstraintStream[A,B]' - package: str - a_type: Type[A] - b_type: Type[B] - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - def __init__(self, delegate: '_JavaBiConstraintStream[A,B]', package: str, - a_type: Type[A], b_type: Type[B]): - self.delegate = delegate - self.package = package - self.a_type = a_type - self.b_type = b_type - - def get_constraint_factory(self): - """ - The ConstraintFactory that build this. - """ - return ConstraintFactory(self.delegate.getConstraintFactory()) - - def filter(self, predicate: Callable[[A, B], bool]) -> 'BiConstraintStream[A,B]': - """ - Exhaustively test each fact against the predicate and match if the predicate returns ``True``. - """ - translated_predicate = predicate_cast(predicate, self.a_type, self.b_type) - return BiConstraintStream(self.delegate.filter(translated_predicate), self.package, - - self.a_type, - self.b_type) - - def join(self, unistream_or_type: Union[UniConstraintStream[C_], Type[C_]], - *joiners: 'TriJoiner[A,B,C_]') -> 'TriConstraintStream[A,B,C_]': - """ - Create a new `TriConstraintStream` for every combination of A, B and C that satisfies all specified joiners. - """ - c_type = None - if isinstance(unistream_or_type, UniConstraintStream): - c_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - c_type = get_class(unistream_or_type) - unistream_or_type = c_type - - join_result = self.delegate.join(unistream_or_type, extract_joiners(joiners, - self.a_type, self.b_type, c_type)) - return TriConstraintStream(join_result, self.package, - self.a_type, self.b_type, c_type) - - @overload - def if_exists(self, item_type: Type[C_], *joiners: 'TriJoiner[A, B, C_]') -> 'BiConstraintStream[A,B]': - ... - - @overload - def if_exists(self, other_stream: 'UniConstraintStream[C_]', *joiners: 'TriJoiner[A, B, C_]') \ - -> 'BiConstraintStream[A,B]': - ... - - def if_exists(self, unistream_or_type: Union['UniConstraintStream[C_]', Type[C_]], - *joiners: 'TriJoiner[A, B, C_]') -> 'BiConstraintStream[A,B]': - """ - Create a new `BiConstraintStream` for every A, B where C exists that satisfies all specified joiners. - """ - c_type = None - if isinstance(unistream_or_type, UniConstraintStream): - c_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - c_type = get_class(unistream_or_type) - unistream_or_type = c_type - return BiConstraintStream(self.delegate.ifExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, c_type)), - self.package, self.a_type, self.b_type) - - def if_exists_including_unassigned(self, item_type: Type[C_], *joiners: 'TriJoiner[A, B, C_]') -> \ - 'BiConstraintStream[A,B]': - """ - Create a new `BiConstraintStream` for every A, B where C exists that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return BiConstraintStream(self.delegate.ifExistsIncludingUnassigned(item_type, extract_joiners(joiners, - self.a_type, - self.b_type, - item_type)), - self.package, self.a_type, self.b_type) - - @overload - def if_not_exists(self, item_type: Type[C_], *joiners: 'TriJoiner[A, B, C_]') -> 'BiConstraintStream[A,B]': - ... - - @overload - def if_not_exists(self, other_stream: 'UniConstraintStream[C_]', *joiners: 'TriJoiner[A, B, C_]')\ - -> 'BiConstraintStream[A,B]': - ... - - def if_not_exists(self, unistream_or_type: Union['UniConstraintStream[C_]', Type[C_]], - *joiners: 'TriJoiner[A, B, C_]') -> 'BiConstraintStream[A,B]': - """ - Create a new `BiConstraintStream` for every A, B where C does not exist that satisfies all specified joiners. - """ - c_type = None - if isinstance(unistream_or_type, UniConstraintStream): - c_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - c_type = get_class(unistream_or_type) - unistream_or_type = c_type - return BiConstraintStream(self.delegate.ifNotExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, c_type)), - self.package, self.a_type, self.b_type) - - def if_not_exists_including_unassigned(self, item_type: Type[C_], *joiners: 'TriJoiner[A, B, C_]') -> \ - 'BiConstraintStream[A,B]': - """ - Create a new `BiConstraintStream` for every A, B where C does not exist that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return BiConstraintStream(self.delegate.ifNotExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, self.b_type, - item_type)), - self.package, self.a_type, self.b_type) - - @overload - def group_by(self, key_mapping: Callable[[A, B], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, collector: 'BiConstraintCollector[A, B, Any, A_]') -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], - second_key_mapping: Callable[[A, B], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B], A_], - collector: 'BiConstraintCollector[A, B, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_collector: 'BiConstraintCollector[A, B, Any, A_]', - second_collector: 'BiConstraintCollector[A, B, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], second_key_mapping: Callable[[A, B], B_], - third_key_mapping: Callable[[A, B], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], second_key_mapping: Callable[[A, B], B_], - collector: 'BiConstraintCollector[A, B, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B], A_], first_collector: 'BiConstraintCollector[A, B, Any, B_]', - second_collector: 'BiConstraintCollector[A, B, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_collector: 'BiConstraintCollector[A, B, Any, A_]', - second_collector: 'BiConstraintCollector[A, B, Any, B_]', - third_collector: 'BiConstraintCollector[A, B, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], second_key_mapping: Callable[[A, B], B_], - third_key_mapping: Callable[[A, B], C_], - fourth_key_mapping: Callable[[A, B], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], second_key_mapping: Callable[[A, B], B_], - third_key_mapping: Callable[[A, B], C_], - collector: 'BiConstraintCollector[A, B, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B], A_], second_key_mapping: Callable[[A, B], B_], - first_collector: 'BiConstraintCollector[A, B, Any, C_]', - second_collector: 'BiConstraintCollector[A, B, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B], A_], first_collector: 'BiConstraintCollector[A, B, Any, B_]', - second_collector: 'BiConstraintCollector[A, B, Any, C_]', - third_collector: 'BiConstraintCollector[A, B, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_collector: 'BiConstraintCollector[A, B, Any, A_]', - second_collector: 'BiConstraintCollector[A, B, Any, B_]', - third_collector: 'BiConstraintCollector[A, B, Any, C_]', - fourth_collector: 'BiConstraintCollector[A, B, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def group_by(self, *args): - """ - Collect items into groups using the group_key_function(s) and optionally aggregate the group's items into a - result. - - The syntax of group_by is zero to four group_key functions, followed by zero to four collectors. At most - four arguments can be passed to group_by. - - If no group_key function is passed to group_by, all items in the stream are aggregated into a single result - by the passed constraint collectors. - - Returns - ------- - UniConstraintStream | BiConstraintStream | TriConstraintStream | QuadConstraintStream - The type of stream returned depends on the number of arguments passed: - - - 1 -> UniConstraintStream - - - 2 -> BiConstraintStream - - - 3 -> TriConstraintStream - - - 4 -> QuadConstraintStream - - Examples - -------- - - Count the items in this stream; returns Uni[int] - - >>> group_by(ConstraintCollectors.count()) - - Count the number of shifts each employee has; returns Bi[Employee] - - >>> group_by(lambda shift: shift.employee, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Get the dates of the first and last shift of each employee; returns Tri[Employee, datetime.date, datetime.date] - - >>> group_by(lambda shift: shift.employee, - ... ConstraintCollectors.min(lambda shift: shift.date) - ... ConstraintCollectors.max(lambda shift: shift.date)) - """ - return perform_group_by(self.delegate, self.package, args, self.a_type, self.b_type) - - @overload - def map(self, mapping_function: Callable[[A, B], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B], A_], - mapping_function2: Callable[[A, B], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B], A_], mapping_function2: Callable[[A, B], B_], - mapping_function3: Callable[[A, B], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B], A_], mapping_function2: Callable[[A, B], B_], - mapping_function3: Callable[[A, B], C_], - mapping_function4: Callable[[A, B], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def map(self, *mapping_functions): - """ - Transforms the stream in such a way that tuples are remapped using the given function. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for map.') - if len(mapping_functions) > 4: - raise ValueError(f'At most four mapping functions can be passed to map (got {len(mapping_functions)}).') - translated_functions = tuple(map(lambda mapping_function: function_cast(mapping_function, self.a_type, - self.b_type), - mapping_functions)) - if len(mapping_functions) == 1: - return UniConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return BiConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object')) - if len(mapping_functions) == 3: - return TriConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - if len(mapping_functions) == 4: - return QuadConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - @overload - def expand(self, mapping_function: Callable[[A, B], C_]) -> 'TriConstraintStream[A, B, C_]': - ... - - @overload - def expand(self, mapping_function: Callable[[A, B], C_], - mapping_function2: Callable[[A, B], D_]) -> 'QuadConstraintStream[A, B, C_, D_]': - ... - - def expand(self, *mapping_functions): - """ - Tuple expansion is a special case of tuple mapping - which only increases stream cardinality and can not introduce duplicate tuples. - It enables you to add extra facts to each tuple in a constraint stream by applying a mapping function to it. - This is useful in situations where an expensive computations needs to be cached for use later in the stream. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for expand.') - if len(mapping_functions) > 2: - raise ValueError( - f'At most two mapping functions can be passed to expand on a BiStream (got {len(mapping_functions)}).') - translated_functions = tuple( - map(lambda mapping_function: function_cast(mapping_function, self.a_type, self.b_type), - mapping_functions)) - if len(mapping_functions) == 1: - return TriConstraintStream(self.delegate.expand(*translated_functions), self.package, - - self.a_type, self.b_type, JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return QuadConstraintStream(self.delegate.expand(*translated_functions), self.package, - - self.a_type, self.b_type, JClass('java.lang.Object'), - JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - def flatten_last(self, flattening_function: Callable[[B], B_]) -> 'BiConstraintStream[A,B_]': - """ - Takes each tuple and applies a mapping on it, which turns the tuple into an Iterable. - """ - translated_function = function_cast(flattening_function, self.b_type) - return BiConstraintStream(self.delegate.flattenLast(translated_function), self.package, - - self.a_type, JClass('java.lang.Object')) - - def distinct(self) -> 'BiConstraintStream[A,B]': - """ - Transforms the stream in such a way that all the tuples going through it are distinct. - """ - return BiConstraintStream(self.delegate.distinct(), self.package, - self.a_type, self.b_type) - - @overload - def concat(self, other: 'UniConstraintStream[A]') -> 'BiConstraintStream[A, B]': - ... - - @overload - def concat(self, other: 'UniConstraintStream[A]', padding_b: Callable[[A], B]) -> 'BiConstraintStream[A, B]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]') -> 'BiConstraintStream[A, B]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B, C_]') -> 'TriConstraintStream[A, B, C_]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B, C_]', padding_c: Callable[[A, B], C_]) \ - -> 'TriConstraintStream[A, B, C_]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B, C_, D_]') -> 'QuadConstraintStream[A, B, C_, D_]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B, C_, D_]', padding_c: Callable[[A, B], C_], - padding_d: Callable[[A, B], D_]) -> 'QuadConstraintStream[A, B, C_, D_]': - ... - - def concat(self, other, padding_b=None, padding_c=None, padding_d=None): - """ - The concat building block allows you - to create a constraint stream containing tuples of two other constraint streams. - If join acts like a cartesian product of two lists, concat acts like a concatenation of two lists. - Unlike union of sets, concatenation of lists repeats duplicated elements. - If the two constraint concatenating streams share tuples, which happens e.g. - when they come from the same source of data, the tuples will be repeated downstream. - If this is undesired, use the distinct building block. - """ - specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d]) - if isinstance(other, UniConstraintStream): - if specified_count == 0: - return BiConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type) - elif specified_count != 1: - raise ValueError(f'Concatenating Bi and UniConstraintStream requires one padding function, ' - f'got {specified_count} instead.') - elif padding_b is None: - raise ValueError(f'Concatenating Bi and UniConstraintStream requires padding_b to be provided.') - return BiConstraintStream(self.delegate.concat(other.delegate, padding_b), self.package, - self.a_type, self.b_type) - elif isinstance(other, BiConstraintStream): - if specified_count == 0: - return BiConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type) - else: - raise ValueError(f'Concatenating BiConstraintStreams requires no padding function, ' - f'got {specified_count} instead.') - elif isinstance(other, TriConstraintStream): - if specified_count == 0: - return TriConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, other.c_type) - elif specified_count != 1: - raise ValueError(f'Concatenating Bi and TriConstraintStream requires one padding function, ' - f'got {specified_count} instead.') - elif padding_c is None: - raise ValueError(f'Concatenating Bi and TriConstraintStream requires padding_c to be provided.') - return TriConstraintStream(self.delegate.concat(other.delegate, padding_c), self.package, - self.a_type, self.b_type, other.c_type) - elif isinstance(other, QuadConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, other.c_type, other.d_type) - elif specified_count != 2: - raise ValueError(f'Concatenating Bi and QuadConstraintStream requires two padding functions, ' - f'got {specified_count} instead.') - elif padding_b is not None: - raise ValueError(f'Concatenating Bi and QuadConstraintStream requires ' - f'padding_c and padding_d to be provided.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_c, padding_d), self.package, - self.a_type, self.b_type, other.c_type, other.d_type) - else: - raise RuntimeError(f'Unhandled constraint stream type {type(other)}.') - - @overload - def complement(self, cls: type[A]) -> 'BiConstraintStream[A, B]': - ... - - @overload - def complement(self, cls: type[A], padding: Callable[[A], B]) -> 'BiConstraintStream[A, B]': - ... - - def complement(self, cls: type[A], padding=None): - """ - Adds to the stream all instances of a given class which are not yet present in it. - These instances must be present in the solution, - which means the class needs to be either a planning entity or a problem fact. - - The instances will be read from the first element of the input tuple. - When an output tuple needs to be created for the newly inserted instances, - the first element will be the new instance. - The rest of the tuple will be padded with the result of the padding function. - - Parameters - ---------- - cls : Type[A] - the type of the instances to add to the stream. - - padding : Callable[[A], B] - a function that computes the padding value for the second fact in the new tuple. - """ - if None is padding: - result = self.delegate.complement(get_class(cls)) - return BiConstraintStream(result, self.package, self.a_type, self.b_type) - java_padding = function_cast(padding, self.a_type) - result = self.delegate.complement(get_class(cls), java_padding) - return BiConstraintStream(result, self.package, self.a_type, self.b_type) - - def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.penalizeLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - - def penalize_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.penalizeBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.rewardLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - def reward_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.rewardBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.impactLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - - def impact_decimal(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.impactBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - def penalize_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.penalizeConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - def penalize_configurable_decimal(self, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.penalizeConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - def reward_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.rewardConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - def reward_configurable_decimal(self, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.rewardConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - def impact_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - if match_weigher is None: - return BiConstraintBuilder(self.delegate.impactConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.impactConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) - - def impact_configurable_decimal(self, match_weigher: Callable[[A, B], Decimal] = None) -> \ - 'BiConstraintBuilder[A, B, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - BiConstraintBuilder - a `BiConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return BiConstraintBuilder(self.delegate.impactConfigurable(), self.a_type, self.b_type) - else: - return BiConstraintBuilder(self.delegate.impactConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - return_type=BigDecimal)), - self.a_type, self.b_type) - - -class TriConstraintStream(Generic[A, B, C]): - """ - A ConstraintStream that matches three facts. - """ - delegate: '_JavaTriConstraintStream[A,B,C]' - package: str - a_type: Type[A] - b_type: Type[B] - c_type: Type[C] - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - def __init__(self, delegate: '_JavaTriConstraintStream[A,B,C]', package: str, - a_type: Type[A], b_type: Type[B], - c_type: Type[C]): - self.delegate = delegate - self.package = package - self.a_type = a_type - self.b_type = b_type - self.c_type = c_type - - def get_constraint_factory(self): - """ - The ConstraintFactory that build this. - """ - return ConstraintFactory(self.delegate.getConstraintFactory()) - - def filter(self, predicate: Callable[[A, B, C], bool]) -> 'TriConstraintStream[A,B,C]': - """ - Exhaustively test each fact against the predicate and match if the predicate returns ``True``. - """ - translated_predicate = predicate_cast(predicate, self.a_type, self.b_type, self.c_type) - return TriConstraintStream(self.delegate.filter(translated_predicate), self.package, - self.a_type, - self.b_type, self.c_type) - - def join(self, unistream_or_type: Union[UniConstraintStream[D_], Type[D_]], - *joiners: 'QuadJoiner[A, B, C, D_]') -> 'QuadConstraintStream[A,B,C,D_]': - """ - Create a new `QuadConstraintStream` for every combination of A, B and C that satisfies all specified joiners. - """ - d_type = None - if isinstance(unistream_or_type, UniConstraintStream): - d_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - d_type = get_class(unistream_or_type) - unistream_or_type = d_type - - join_result = self.delegate.join(unistream_or_type, extract_joiners(joiners, - self.a_type, self.b_type, self.c_type, - d_type)) - return QuadConstraintStream(join_result, self.package, - self.a_type, self.b_type, self.c_type, d_type) - - @overload - def if_exists(self, item_type: Type[D_], *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - ... - - @overload - def if_exists(self, other_stream: 'UniConstraintStream[D_]', *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - ... - - def if_exists(self, unistream_or_type: Union['UniConstraintStream[D_]', Type[D_]], - *joiners: 'QuadJoiner[A, B, C, D_]') -> 'TriConstraintStream[A,B,C]': - """ - Create a new `TriConstraintStream` for every A, B, C where D exists that satisfies all specified joiners. - """ - d_type = None - if isinstance(unistream_or_type, UniConstraintStream): - d_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - d_type = get_class(unistream_or_type) - unistream_or_type = d_type - return TriConstraintStream(self.delegate.ifExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, self.c_type, - d_type)), - self.package, self.a_type, self.b_type, self.c_type) - - def if_exists_including_unassigned(self, item_type: Type[D_], *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - """ - Create a new `TriConstraintStream` for every A, B where D exists that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return TriConstraintStream(self.delegate.ifExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, self.b_type, - self.c_type, item_type)), - self.package, self.a_type, self.b_type, self.c_type) - - @overload - def if_not_exists(self, item_type: Type[D_], *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - ... - - @overload - def if_not_exists(self, other_stream: 'UniConstraintStream[D_]', *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - ... - - def if_not_exists(self, unistream_or_type: Union['UniConstraintStream[D_]', Type[D_]], - *joiners: 'QuadJoiner[A, B, C, D_]') -> 'TriConstraintStream[A,B,C]': - """ - Create a new `TriConstraintStream` for every A, B, C where D does not exist - that satisfies all specified joiners. - """ - d_type = None - if isinstance(unistream_or_type, UniConstraintStream): - d_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - d_type = get_class(unistream_or_type) - unistream_or_type = d_type - return TriConstraintStream(self.delegate.ifNotExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, self.c_type, - d_type)), - self.package, self.a_type, self.b_type, self.c_type) - - def if_not_exists_including_unassigned(self, item_type: Type[D_], *joiners: 'QuadJoiner[A, B, C, D_]') -> \ - 'TriConstraintStream[A,B,C]': - """ - Create a new `TriConstraintStream` for every A, B, C where D does not exist that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return TriConstraintStream(self.delegate.ifNotExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, - self.b_type, - self.c_type, - item_type)), - self.package, self.a_type, self.b_type, self.c_type) - - @overload - def group_by(self, key_mapping: Callable[[A, B, C], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, collector: 'TriConstraintCollector[A, B, C, Any, A_]') -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], - second_key_mapping: Callable[[A, B, C], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C], A_], - collector: 'TriConstraintCollector[A, B, C, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_collector: 'TriConstraintCollector[A, B, C, Any, A_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], second_key_mapping: Callable[[A, B, C], B_], - third_key_mapping: Callable[[A, B, C], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], second_key_mapping: Callable[[A, B, C], B_], - collector: 'TriConstraintCollector[A, B, C, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C], A_], - first_collector: 'TriConstraintCollector[A, B, C, Any, B_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_collector: 'TriConstraintCollector[A, B, C, Any, A_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, B_]', - third_collector: 'TriConstraintCollector[A, B, C, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], second_key_mapping: Callable[[A, B, C], B_], - third_key_mapping: Callable[[A, B, C], C_], - fourth_key_mapping: Callable[[A, B, C], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], second_key_mapping: Callable[[A, B, C], B_], - third_key_mapping: Callable[[A, B, C], C_], - collector: 'TriConstraintCollector[A, B, C, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C], A_], second_key_mapping: Callable[[A, B, C], B_], - first_collector: 'TriConstraintCollector[A, B, C, Any, C_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, D_]') -> \ - 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C], A_], - first_collector: 'TriConstraintCollector[A, B, C, Any, B_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, C_]', - third_collector: 'TriConstraintCollector[A, B, C, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_collector: 'TriConstraintCollector[A, B, C, Any, A_]', - second_collector: 'TriConstraintCollector[A, B, C, Any, B_]', - third_collector: 'TriConstraintCollector[A, B, C, Any, C_]', - fourth_collector: 'TriConstraintCollector[A, B, C, Any, D_]') -> \ - 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def group_by(self, *args): - """ - Collect items into groups using the group_key_function(s) and optionally aggregate the group's items into a - result. - - The syntax of group_by is zero to four group_key functions, followed by zero to four collectors. At most - four arguments can be passed to group_by. - - If no group_key function is passed to group_by, all items in the stream are aggregated into a single result - by the passed constraint collectors. - - Returns - ------- - UniConstraintStream | BiConstraintStream | TriConstraintStream | QuadConstraintStream - The type of stream returned depends on the number of arguments passed: - - - 1 -> UniConstraintStream - - - 2 -> BiConstraintStream - - - 3 -> TriConstraintStream - - - 4 -> QuadConstraintStream - - Examples - -------- - - Count the items in this stream; returns Uni[int] - - >>> group_by(ConstraintCollectors.count()) - - Count the number of shifts each employee has; returns Bi[Employee] - - >>> group_by(lambda shift: shift.employee, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Get the dates of the first and last shift of each employee; returns Tri[Employee, datetime.date, datetime.date] - - >>> group_by(lambda shift: shift.employee, - ... ConstraintCollectors.min(lambda shift: shift.date) - ... ConstraintCollectors.max(lambda shift: shift.date)) - """ - return perform_group_by(self.delegate, self.package, args, self.a_type, self.b_type, self.c_type) - - @overload - def map(self, mapping_function: Callable[[A, B, C], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C], A_], - mapping_function2: Callable[[A, B, C], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C], A_], mapping_function2: Callable[[A, B, C], B_], - mapping_function3: Callable[[A, B, C], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C], A_], mapping_function2: Callable[[A, B, C], B_], - mapping_function3: Callable[[A, B, C], C_], - mapping_function4: Callable[[A, B, C], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def map(self, *mapping_functions): - """ - Transforms the stream in such a way that tuples are remapped using the given function. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for map.') - if len(mapping_functions) > 4: - raise ValueError(f'At most four mapping functions can be passed to map (got {len(mapping_functions)}).') - translated_functions = tuple(map(lambda mapping_function: function_cast(mapping_function, - self.a_type, self.b_type, self.c_type), - mapping_functions)) - if len(mapping_functions) == 1: - return UniConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return BiConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object')) - if len(mapping_functions) == 3: - return TriConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - if len(mapping_functions) == 4: - return QuadConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - def expand(self, mapping_function: Callable[[A, B, C], D_]) -> 'QuadConstraintStream[A, B, C, D_]': - """ - Tuple expansion is a special case of tuple mapping - which only increases stream cardinality and can not introduce duplicate tuples. - It enables you to add extra facts to each tuple in a constraint stream by applying a mapping function to it. - This is useful in situations where an expensive computations needs to be cached for use later in the stream. - """ - translated_function = function_cast(mapping_function, self.a_type, self.b_type, self.c_type) - return QuadConstraintStream(self.delegate.expand(translated_function), self.package, - - self.a_type, self.b_type, self.c_type, JClass('java.lang.Object')) - - def flatten_last(self, flattening_function: Callable[[C], C_]) -> 'TriConstraintStream[A,B,C_]': - """ - Takes each tuple and applies a mapping on it, which turns the tuple into an Iterable. - """ - translated_function = function_cast(flattening_function, self.c_type) - return TriConstraintStream(self.delegate.flattenLast(translated_function), self.package, - - self.a_type, self.b_type, JClass('java.lang.Object')) - - def distinct(self) -> 'TriConstraintStream[A, B, C]': - """ - Transforms the stream in such a way that all the tuples going through it are distinct. - """ - return TriConstraintStream(self.delegate.distinct(), self.package, - self.a_type, - self.b_type, self.c_type) - - @overload - def concat(self, other: 'UniConstraintStream[A]') -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def concat(self, other: 'UniConstraintStream[A]', padding_b: Callable[[A], B], padding_c: Callable[[A], C]) \ - -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]') -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]', padding_c: Callable[[A, B], C]) \ - -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B, C]') -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B, C, D_]') -> 'QuadConstraintStream[A, B, C, D_]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B, C, D_]', padding_d: Callable[[A, B, C], D_]) \ - -> 'QuadConstraintStream[A, B, C, D_]': - ... - - def concat(self, other, padding_b=None, padding_c=None, padding_d=None): - """ - The concat building block allows you - to create a constraint stream containing tuples of two other constraint streams. - If join acts like a cartesian product of two lists, concat acts like a concatenation of two lists. - Unlike union of sets, concatenation of lists repeats duplicated elements. - If the two constraint concatenating streams share tuples, which happens e.g. - when they come from the same source of data, the tuples will be repeated downstream. - If this is undesired, use the distinct building block. - """ - specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d]) - if isinstance(other, UniConstraintStream): - if specified_count == 0: - return TriConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type) - elif specified_count != 2: - raise ValueError(f'Concatenating Tri and UniConstraintStream requires 2 padding functions, ' - f'got {specified_count} instead.') - elif padding_d is not None: - raise ValueError(f'Concatenating Tri and UniConstraintStream requires ' - f'padding_b and padding_c to be provided.') - return TriConstraintStream(self.delegate.concat(other.delegate, padding_b, padding_c), self.package, - self.a_type, self.b_type, self.c_type) - elif isinstance(other, BiConstraintStream): - if specified_count == 0: - return TriConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type) - elif specified_count != 1: - raise ValueError(f'Concatenating Tri and BiConstraintStream requires 1 padding function, ' - f'got {specified_count} instead.') - elif padding_c is None: - raise ValueError(f'Concatenating Tri and BiConstraintStream requires padding_c to be provided.') - return TriConstraintStream(self.delegate.concat(other.delegate, padding_c), self.package, - self.a_type, self.b_type, self.c_type) - elif isinstance(other, TriConstraintStream): - if specified_count == 0: - return TriConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type) - else: - raise ValueError(f'Concatenating TriConstraintStreams requires no padding functions, ' - f'got {specified_count} instead.') - elif isinstance(other, QuadConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type, other.d_type) - elif specified_count != 1: - raise ValueError(f'Concatenating Tri and QuadConstraintStream requires 1 padding function, ' - f'got {specified_count} instead.') - elif padding_d is None: - raise ValueError(f'Concatenating Tri and QuadConstraintStream requires padding_d to be provided.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_d), self.package, - self.a_type, self.b_type, self.c_type, other.d_type) - else: - raise RuntimeError(f'Unhandled constraint stream type {type(other)}.') - - @overload - def complement(self, cls: type[A]) -> 'TriConstraintStream[A, B, C]': - ... - - @overload - def complement(self, cls: type[A], padding_b: Callable[[A], B], padding_c: Callable[[A], C]) \ - -> 'TriConstraintStream[A, B, C]': - ... - - def complement(self, cls: type[A], padding_b=None, padding_c=None): - """ - Adds to the stream all instances of a given class which are not yet present in it. - These instances must be present in the solution, - which means the class needs to be either a planning entity or a problem fact. - - The instances will be read from the first element of the input tuple. - When an output tuple needs to be created for the newly inserted instances, - the first element will be the new instance. - The rest of the tuple will be padded with the result of the padding function, - applied on the new instance. - - Padding functions are optional, but if one is provided, then both must-be provided. - - Parameters - ---------- - cls : Type[A] - the type of the instances to add to the stream. - - padding_b : Callable[[A], B] - a function that computes the padding value for the second fact in the new tuple. - - padding_c : Callable[[A], C] - a function that computes the padding value for the third fact in the new tuple. - """ - if None == padding_b == padding_c: - result = self.delegate.complement(get_class(cls)) - return TriConstraintStream(result, self.package, self.a_type, self.b_type, self.c_type) - specified_count = sum(x is not None for x in [padding_b, padding_c]) - if specified_count != 0: - raise ValueError(f'If a padding function is provided, both are expected, got {specified_count} instead.') - java_padding_b = function_cast(padding_b, self.a_type) - java_padding_c = function_cast(padding_c, self.a_type) - result = self.delegate.complement(get_class(cls), java_padding_b, java_padding_c) - return TriConstraintStream(result, self.package, self.a_type, self.b_type, self.c_type) - - def penalize(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C], int] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.penalize(constraint_weight), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.penalizeLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - def penalize_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.penalize(constraint_weight), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.penalizeBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B, C], int] = None) -> \ - 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type, - self.c_type) - else: - return TriConstraintBuilder(self.delegate.rewardLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - def reward_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.reward(constraint_weight), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.rewardBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - def impact(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C], int] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.impact(constraint_weight), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.impactLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - def impact_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.impact(constraint_weight), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.impactBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - def penalize_configurable(self, match_weigher: Callable[[A, B, C], int] = None) \ - -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.penalizeConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.penalizeConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - def penalize_configurable_decimal(self, match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.penalizeConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.penalizeConfigurableBigDecimal( - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - def reward_configurable(self, match_weigher: Callable[[A, B, C], int] = None) -> \ - 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.rewardConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.rewardConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - - def reward_configurable_decimal(self, match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.rewardConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.rewardConfigurableBigDecimal( - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - def impact_configurable(self, match_weigher: Callable[[A, B, C], int] = None) \ - -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - if match_weigher is None: - return TriConstraintBuilder(self.delegate.impactConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.impactConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) - - def impact_configurable_decimal(self, match_weigher: Callable[[A, B, C], Decimal] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - TriConstraintBuilder - a `TriConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return TriConstraintBuilder(self.delegate.impactConfigurable(), - self.a_type, self.b_type, self.c_type) - else: - return TriConstraintBuilder(self.delegate.impactConfigurableBigDecimal( - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type) - - -class QuadConstraintStream(Generic[A, B, C, D]): - """ - A ConstraintStream that matches four facts. - """ - delegate: '_JavaQuadConstraintStream[A,B,C,D]' - package: str - a_type: Type[A] - b_type: Type[B] - c_type: Type[C] - d_type: Type[D] - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - def __init__(self, delegate: '_JavaQuadConstraintStream[A,B,C,D]', package: str, - a_type: Type[A], b_type: Type[B], - c_type: Type[C], d_type: Type[D]): - self.delegate = delegate - self.package = package - self.a_type = a_type - self.b_type = b_type - self.c_type = c_type - self.d_type = d_type - - def get_constraint_factory(self): - """ - The ConstraintFactory that build this. - """ - return ConstraintFactory(self.delegate.getConstraintFactory()) - - def filter(self, predicate: Callable[[A, B, C, D], bool]) -> 'QuadConstraintStream[A,B,C,D]': - """ - Exhaustively test each fact against the predicate and match if the predicate returns ``True``. - """ - translated_predicate = predicate_cast(predicate, self.a_type, self.b_type, self.c_type, self.d_type) - return QuadConstraintStream(self.delegate.filter(translated_predicate), self.package, - self.a_type, - self.b_type, self.c_type, self.d_type) - - @overload - def if_exists(self, item_type: Type[E_], *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - ... - - @overload - def if_exists(self, other_stream: 'UniConstraintCollector[E_]', *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - ... - - def if_exists(self, unistream_or_type: Union['UniConstraintStream[E_]', Type[E_]], - *joiners: 'PentaJoiner[A, B, C, D, E_]') -> 'QuadConstraintStream[A,B,C,D]': - """ - Create a new `QuadConstraintStream` for every A, B, C, D where E exists that satisfies all specified joiners. - """ - e_type = None - if isinstance(unistream_or_type, UniConstraintStream): - e_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - e_type = get_class(unistream_or_type) - unistream_or_type = e_type - return QuadConstraintStream(self.delegate.ifExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, self.c_type, - self.d_type, e_type)), - self.package, self.a_type, self.b_type, self.c_type, self.d_type) - - def if_exists_including_unassigned(self, item_type: Type[E_], *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - """ - Create a new `QuadConstraintStream` for every A, B, C, D where E exists that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return QuadConstraintStream(self.delegate.ifExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - item_type)), - self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - - @overload - def if_not_exists(self, item_type: Type[E_], *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - ... - - @overload - def if_not_exists(self, other_stream: 'UniConstraintCollector[E_]', *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - ... - - def if_not_exists(self, unistream_or_type: Union['UniConstraintStream[E_]', Type[E_]], - *joiners: 'PentaJoiner[A, B, C, D, E_]') -> 'QuadConstraintStream[A,B,C,D]': - """ - Create a new `QuadConstraintStream` for every A, B, C, D where E does not exist - that satisfies all specified joiners. - """ - e_type = None - if isinstance(unistream_or_type, UniConstraintStream): - e_type = unistream_or_type.a_type - unistream_or_type = unistream_or_type.delegate - else: - e_type = get_class(unistream_or_type) - unistream_or_type = e_type - return QuadConstraintStream(self.delegate.ifNotExists(unistream_or_type, - extract_joiners(joiners, - self.a_type, self.b_type, self.c_type, - self.d_type, e_type)), - self.package, self.a_type, self.b_type, self.c_type, self.d_type) - - def if_not_exists_including_unassigned(self, item_type: Type[E_], *joiners: 'PentaJoiner[A, B, C, D, E_]') -> \ - 'QuadConstraintStream[A,B,C,D]': - """ - Create a new `QuadConstraintStream` for every A, B, C, - D where E does not exist that satisfies all specified joiners. - """ - item_type = get_class(item_type) - return QuadConstraintStream(self.delegate.ifNotExistsIncludingUnassigned(item_type, - extract_joiners(joiners, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - item_type)), - self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - - @overload - def group_by(self, key_mapping: Callable[[A, B, C, D], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, collector: 'QuadConstraintCollector[A, B, C, D, Any, A_]') -> 'UniConstraintStream[A_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], - second_key_mapping: Callable[[A, B, C, D], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C, D], A_], - collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_collector: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]') -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], second_key_mapping: Callable[[A, B, C, D], B_], - third_key_mapping: Callable[[A, B, C, D], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], second_key_mapping: Callable[[A, B, C, D], B_], - collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C, D], A_], - first_collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_collector: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - third_collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]') -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], second_key_mapping: Callable[[A, B, C, D], B_], - third_key_mapping: Callable[[A, B, C, D], C_], - fourth_key_mapping: Callable[[A, B, C, D], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], second_key_mapping: Callable[[A, B, C, D], B_], - third_key_mapping: Callable[[A, B, C, D], C_], - collector: 'QuadConstraintCollector[A, B, C, D, Any, D_]') -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_key_mapping: Callable[[A, B, C, D], A_], second_key_mapping: Callable[[A, B, C, D], B_], - first_collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, D_]') -> \ - 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, key_mapping: Callable[[A, B, C, D], A_], - first_collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]', - third_collector: 'QuadConstraintCollector[A, B, C, D, Any, D_]') -> \ - 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - @overload - def group_by(self, first_collector: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - second_collector: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - third_collector: 'QuadConstraintCollector[A, B, C, D, Any, C_]', - fourth_collector: 'QuadConstraintCollector[A, B, C, D, Any, D_]') -> \ - 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def group_by(self, *args): - """ - Collect items into groups using the group_key_function(s) and optionally aggregate the group's items into a - result. - - The syntax of group_by is zero to four group_key functions, followed by zero to four collectors. At most - four arguments can be passed to group_by. - - If no group_key function is passed to group_by, all items in the stream are aggregated into a single result - by the passed constraint collectors. - - Returns - ------- - UniConstraintStream | BiConstraintStream | TriConstraintStream | QuadConstraintStream - The type of stream returned depends on the number of arguments passed: - - - 1 -> UniConstraintStream - - - 2 -> BiConstraintStream - - - 3 -> TriConstraintStream - - - 4 -> QuadConstraintStream - - Examples - -------- - - Count the items in this stream; returns Uni[int] - - >>> group_by(ConstraintCollectors.count()) - - Count the number of shifts each employee has; returns Bi[Employee] - - >>> group_by(lambda shift: shift.employee, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Count the number of shifts each employee has on a date; returns Tri[Employee, datetime.date, int] - - >>> group_by(lambda shift: shift.employee, lambda shift: shift.date, ConstraintCollectors.count()) - - Get the dates of the first and last shift of each employee; returns Tri[Employee, datetime.date, datetime.date] - - >>> group_by(lambda shift: shift.employee, - ... ConstraintCollectors.min(lambda shift: shift.date) - ... ConstraintCollectors.max(lambda shift: shift.date)) - """ - return perform_group_by(self.delegate, self.package, args, self.a_type, self.b_type, self.c_type, self.d_type) - - @overload - def map(self, mapping_function: Callable[[A, B, C, D], A_]) -> 'UniConstraintStream[A_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C, D], A_], - mapping_function2: Callable[[A, B, C, D], B_]) -> 'BiConstraintStream[A_, B_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C, D], A_], mapping_function2: Callable[[A, B, C, D], B_], - mapping_function3: Callable[[A, B, C, D], C_]) -> 'TriConstraintStream[A_, B_, C_]': - ... - - @overload - def map(self, mapping_function: Callable[[A, B, C, D], A_], mapping_function2: Callable[[A, B, C, D], B_], - mapping_function3: Callable[[A, B, C, D], C_], - mapping_function4: Callable[[A, B, C, D], D_]) -> 'QuadConstraintStream[A_, B_, C_, D_]': - ... - - def map(self, *mapping_functions): - """ - Transforms the stream in such a way that tuples are remapped using the given function. - """ - if len(mapping_functions) == 0: - raise ValueError(f'At least one mapping function is required for map.') - if len(mapping_functions) > 4: - raise ValueError(f'At most four mapping functions can be passed to map (got {len(mapping_functions)}).') - translated_functions = tuple(map(lambda mapping_function: function_cast(mapping_function, self.a_type, - self.b_type, self.c_type, self.d_type), - mapping_functions)) - if len(mapping_functions) == 1: - return UniConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object')) - if len(mapping_functions) == 2: - return BiConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object')) - if len(mapping_functions) == 3: - return TriConstraintStream(self.delegate.map(*translated_functions), self.package, - - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - if len(mapping_functions) == 4: - return QuadConstraintStream(self.delegate.map(*translated_functions), self.package, - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object')) - raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.') - - def flatten_last(self, flattening_function) -> 'QuadConstraintStream[A,B,C,D]': - """ - Takes each tuple and applies a mapping on it, which turns the tuple into an Iterable. - """ - translated_function = function_cast(flattening_function, self.d_type) - return QuadConstraintStream(self.delegate.flattenLast(translated_function), self.package, - self.a_type, self.b_type, self.c_type, JClass('java.lang.Object')) - - def distinct(self) -> 'QuadConstraintStream[A,B,C,D]': - """ - Transforms the stream in such a way that all the tuples going through it are distinct. - """ - return QuadConstraintStream(self.delegate.distinct(), self.package, - self.a_type, - self.b_type, self.c_type, self.d_type) - - @overload - def concat(self, other: 'UniConstraintStream[A]') -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'UniConstraintStream[A]', padding_b: Callable[[A], B], padding_c: Callable[[A], C], - padding_d: Callable[[A], D]) -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]') -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]', padding_c: Callable[[A, B], C], - padding_d: Callable[[A, B], D]) -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'BiConstraintStream[A, B]') -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B, C]') -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'TriConstraintStream[A, B, C]', padding_d: Callable[[A, B, C], D]) \ - -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def concat(self, other: 'QuadConstraintStream[A, B, C, D]') -> 'QuadConstraintStream[A, B, C, D]': - ... - - def concat(self, other, padding_b=None, padding_c=None, padding_d=None): - """ - The concat building block allows you - to create a constraint stream containing tuples of two other constraint streams. - If join acts like a cartesian product of two lists, concat acts like a concatenation of two lists. - Unlike union of sets, concatenation of lists repeats duplicated elements. - If the two constraint concatenating streams share tuples, which happens e.g. - when they come from the same source of data, the tuples will be repeated downstream. - If this is undesired, use the distinct building block. - """ - specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d]) - if isinstance(other, UniConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif specified_count != 3: - raise ValueError(f'Concatenating Uni and QuadConstraintStream requires 3 padding functions, ' - f'got {specified_count} instead.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_b, padding_c, padding_d), - self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif isinstance(other, BiConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif specified_count != 2: - raise ValueError(f'Concatenating Bi and QuadConstraintStream requires 2 padding functions, ' - f'got {specified_count} instead.') - elif padding_b is not None: - raise ValueError(f'Concatenating Bi and QuadConstraintStream requires ' - f'padding_c and padding_d to be provided.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_c, padding_d), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif isinstance(other, TriConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif specified_count != 1: - raise ValueError(f'Concatenating Tri and QuadConstraintStream requires 1 padding function, ' - f'got {specified_count} instead.') - elif padding_d is None: - raise ValueError(f'Concatenating Bi and QuadConstraintStream requires padding_d to be provided.') - return QuadConstraintStream(self.delegate.concat(other.delegate, padding_d), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - elif isinstance(other, QuadConstraintStream): - if specified_count == 0: - return QuadConstraintStream(self.delegate.concat(other.delegate), self.package, - self.a_type, self.b_type, self.c_type, self.d_type) - else: - raise ValueError(f'Concatenating QuadConstraintStreams requires no padding functions, ' - f'got {specified_count} instead.') - else: - raise RuntimeError(f'Unhandled constraint stream type {type(other)}.') - - @overload - def complement(self, cls: type[A]) -> 'QuadConstraintStream[A, B, C, D]': - ... - - @overload - def complement(self, cls: type[A], padding_b: Callable[[A], B], padding_c: Callable[[A], C], - padding_d: Callable[[A], D]) -> 'QuadConstraintStream[A, B, C, D]': - ... - - def complement(self, cls: type[A], padding_b=None, padding_c=None, padding_d=None): - """ - Adds to the stream all instances of a given class which are not yet present in it. - These instances must be present in the solution, - which means the class needs to be either a planning entity or a problem fact. - - The instances will be read from the first element of the input tuple. - When an output tuple needs to be created for the newly inserted instances, - the first element will be the new instance. - The rest of the tuple will be padded with the result of the padding function, - applied on the new instance. - - Padding functions are optional, but if one is provided, then all three must-be provided. - - Parameters - ---------- - cls : Type[A] - the type of the instances to add to the stream. - - padding_b : Callable[[A], B] - a function that computes the padding value for the second fact in the new tuple. - - padding_c : Callable[[A], C] - a function that computes the padding value for the third fact in the new tuple. - - padding_d : Callable[[A], D] - a function that computes the padding value for the fourth fact in the new tuple. - """ - if None == padding_b == padding_c == padding_d: - result = self.delegate.complement(get_class(cls)) - return QuadConstraintStream(result, self.package, self.a_type, self.b_type, self.c_type, self.d_type) - specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d]) - if specified_count != 0: - raise ValueError(f'If a padding function is provided, all 3 are expected, got {specified_count} instead.') - java_padding_b = function_cast(padding_b, self.a_type) - java_padding_c = function_cast(padding_c, self.a_type) - java_padding_d = function_cast(padding_d, self.a_type) - result = self.delegate.complement(get_class(cls), java_padding_b, java_padding_c, java_padding_d) - return QuadConstraintStream(result, self.package, self.a_type, self.b_type, self.c_type, self.d_type) - - def penalize(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], int] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.penalize(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.penalizeLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def penalize_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Applies a negative Score impact, subtracting the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.penalize(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.penalizeBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def reward(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], int] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.reward(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.rewardLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def reward_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Applies a positive Score impact, adding the constraint_weight multiplied by the match weight, - and returns a builder to apply optional constraint properties. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.reward(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.rewardBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def impact(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], int] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.impact(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.impactLong(constraint_weight, - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def impact_decimal(self, constraint_weight: ScoreType, - match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively or negatively impacts the `Score` by `constraint_weight` multiplied by match weight for each match - and returns a builder to apply optional constraint properties. - Use `penalize` or `reward` instead, unless this constraint can both have positive and negative weights. - - Parameters - ---------- - constraint_weight : Score - the weight of the constraint. - - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.impact(constraint_weight), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.impactBigDecimal(constraint_weight, - function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def penalize_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ - -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.penalizeConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.penalizeConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def penalize_configurable_decimal(self, match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Negatively impacts the Score, subtracting the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `penalize` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.penalizeConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.penalizeConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def reward_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ - -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.rewardConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.rewardConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def reward_configurable_decimal(self, match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `reward` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.rewardConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.rewardConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def impact_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ - -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], int] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.impactConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.impactConfigurableLong( - to_long_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) - - def impact_configurable_decimal(self, match_weigher: Callable[[A, B, C, D], Decimal] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': - """ - Positively or negatively impacts the Score, adding the ConstraintWeight for each match, - and returns a builder to apply optional constraint properties. - The constraint weight comes from a `ConstraintWeight` annotated member on the `constraint_configuration`, - so end users can change the constraint weights dynamically. - This constraint may be deactivated if the `ConstraintWeight` is zero. - If there is no `constraint_configuration`, use `impact` instead. - - Parameters - ---------- - match_weigher : Callable[[A, B, C, D], Decimal] - a function that computes the weight of a match. - If absent, each match has weight ``1``. - - Returns - ------- - QuadConstraintBuilder - a `QuadConstraintBuilder` - """ - from java.math import BigDecimal - if match_weigher is None: - return QuadConstraintBuilder(self.delegate.impactConfigurable(), - self.a_type, self.b_type, self.c_type, self.d_type) - else: - return QuadConstraintBuilder(self.delegate.impactConfigurableBigDecimal(function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type, - return_type=BigDecimal)), - self.a_type, self.b_type, self.c_type, self.d_type) - - -# Must be on the bottom, .group_by depends on this module -from ._constraint_factory import * -from ._joiners import * -from ._group_by import * -from ._constraint_builder import * -from ._function_translator import * - -__all__ = [ - 'UniConstraintStream', - 'BiConstraintStream', - 'TriConstraintStream', - 'QuadConstraintStream' -] diff --git a/timefold-solver-python-core/src/main/python/score/_function_translator.py b/timefold-solver-python-core/src/main/python/score/_function_translator.py deleted file mode 100644 index 18daf809..00000000 --- a/timefold-solver-python-core/src/main/python/score/_function_translator.py +++ /dev/null @@ -1,254 +0,0 @@ -from .._jpype_type_conversions import (_convert_to_java_compatible_object, PythonFunction, PythonBiFunction, - PythonTriFunction, PythonQuadFunction, PythonPentaFunction, PythonToIntFunction, - PythonToIntBiFunction, PythonToIntTriFunction, PythonToIntQuadFunction, - PythonToLongFunction, PythonToLongBiFunction, PythonToLongTriFunction, - PythonToLongQuadFunction, PythonPredicate, PythonBiPredicate, - PythonTriPredicate, PythonQuadPredicate, PythonPentaPredicate) -from _jpyinterpreter import translate_python_bytecode_to_java_bytecode, check_current_python_version_supported -import jpype.imports # noqa -from jpype import JImplements, JOverride -import inspect -import logging - - -logger = logging.getLogger('timefold.solver') - - -def _check_if_bytecode_translation_possible(): - check_current_python_version_supported() - - -@JImplements('java.util.function.Predicate', deferred=True) -class PythonPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument): - return self.delegate(argument) - - -@JImplements('java.util.function.BiPredicate', deferred=True) -class PythonBiPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2): - return self.delegate(argument1, argument2) - - -@JImplements('ai.timefold.solver.core.api.function.TriPredicate', deferred=True) -class PythonTriPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3): - return self.delegate(argument1, argument2, argument3) - - -@JImplements('ai.timefold.solver.core.api.function.QuadPredicate', deferred=True) -class PythonQuadPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3, argument4): - return self.delegate(argument1, argument2, argument3, argument4) - - -@JImplements('ai.timefold.solver.core.api.function.PentaPredicate', deferred=True) -class PythonPentaPredicate: - def __init__(self, delegate): - self.delegate = delegate - - @JOverride - def test(self, argument1, argument2, argument3, argument4, argument5): - return self.delegate(argument1, argument2, argument3, argument4, argument5) - - -def _check_if_type_args_are_python_object_wrappers(type_args): - from ai.timefold.jpyinterpreter.types.wrappers import PythonObjectWrapper - - for cls in type_args: - if PythonObjectWrapper.class_.isAssignableFrom(cls): - return True - - return False - - -def function_cast(function, *type_args, return_type=None): - arg_count = len(inspect.signature(function).parameters) - if len(type_args) != arg_count: - raise ValueError(f'Invalid function: expected {len(type_args)} arguments but got {arg_count}') - - if _check_if_type_args_are_python_object_wrappers(type_args): - return default_function_cast(function, arg_count) - - from java.util.function import Function, BiFunction - from ai.timefold.solver.core.api.function import TriFunction, QuadFunction, PentaFunction - from ai.timefold.jpyinterpreter import PythonLikeObject - - if return_type is None: - return_type = PythonLikeObject - - try: - _check_if_bytecode_translation_possible() - if arg_count == 1: - return translate_python_bytecode_to_java_bytecode(function, Function, *type_args, return_type) - elif arg_count == 2: - return translate_python_bytecode_to_java_bytecode(function, BiFunction, *type_args, return_type) - elif arg_count == 3: - return translate_python_bytecode_to_java_bytecode(function, TriFunction, *type_args, return_type) - elif arg_count == 4: - return translate_python_bytecode_to_java_bytecode(function, QuadFunction, *type_args, return_type) - elif arg_count == 5: - return translate_python_bytecode_to_java_bytecode(function, PentaFunction, *type_args, return_type) - except: # noqa - return default_function_cast(function, arg_count) - - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def default_function_cast(function, arg_count): - if arg_count == 1: - return PythonFunction(lambda a: _convert_to_java_compatible_object(function(a))) - elif arg_count == 2: - return PythonBiFunction(lambda a, b: _convert_to_java_compatible_object(function(a, b))) - elif arg_count == 3: - return PythonTriFunction(lambda a, b, c: _convert_to_java_compatible_object(function(a, b, c))) - elif arg_count == 4: - return PythonQuadFunction(lambda a, b, c, d: _convert_to_java_compatible_object(function(a, b, c, d))) - elif arg_count == 5: - return PythonPentaFunction(lambda a, b, c, d, e: _convert_to_java_compatible_object(function(a, b, c, d, e))) - else: - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def predicate_cast(predicate, *type_args): - arg_count = len(inspect.signature(predicate).parameters) - if len(type_args) != arg_count: - raise ValueError(f'Invalid function: expected {len(type_args)} arguments but got {arg_count}') - - if _check_if_type_args_are_python_object_wrappers(type_args): - return default_predicate_cast(predicate, arg_count) - - from java.util.function import Predicate, BiPredicate - from ai.timefold.solver.core.api.function import TriPredicate, QuadPredicate, PentaPredicate - try: - _check_if_bytecode_translation_possible() - if arg_count == 1: - return translate_python_bytecode_to_java_bytecode(predicate, Predicate, *type_args) - elif arg_count == 2: - return translate_python_bytecode_to_java_bytecode(predicate, BiPredicate, *type_args) - elif arg_count == 3: - return translate_python_bytecode_to_java_bytecode(predicate, TriPredicate, *type_args) - elif arg_count == 4: - return translate_python_bytecode_to_java_bytecode(predicate, QuadPredicate, *type_args) - elif arg_count == 5: - return translate_python_bytecode_to_java_bytecode(predicate, PentaPredicate, *type_args) - except: # noqa - all_translated_successfully = False - return default_predicate_cast(predicate, arg_count) - - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def default_predicate_cast(predicate, arg_count): - if arg_count == 1: - return PythonPredicate(predicate) - elif arg_count == 2: - return PythonBiPredicate(predicate) - elif arg_count == 3: - return PythonTriPredicate(predicate) - elif arg_count == 4: - return PythonQuadPredicate(predicate) - elif arg_count == 5: - return PythonPentaPredicate(predicate) - else: - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def to_int_function_cast(function, *type_args): - arg_count = len(inspect.signature(function).parameters) - if len(type_args) != arg_count: - raise ValueError(f'Invalid function: expected {len(type_args)} arguments but got {arg_count}') - - if _check_if_type_args_are_python_object_wrappers(type_args): - return default_to_int_function_cast(function, arg_count) - - from java.util.function import ToIntFunction, ToIntBiFunction - from ai.timefold.solver.core.api.function import ToIntTriFunction, ToIntQuadFunction - try: - _check_if_bytecode_translation_possible() - if arg_count == 1: - return translate_python_bytecode_to_java_bytecode(function, ToIntFunction, *type_args) - elif arg_count == 2: - return translate_python_bytecode_to_java_bytecode(function, ToIntBiFunction, *type_args) - elif arg_count == 3: - return translate_python_bytecode_to_java_bytecode(function, ToIntTriFunction, *type_args) - elif arg_count == 4: - return translate_python_bytecode_to_java_bytecode(function, ToIntQuadFunction, *type_args) - except: # noqa - return default_to_int_function_cast(function, arg_count) - - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def default_to_int_function_cast(function, arg_count): - if arg_count == 1: - return PythonToIntFunction(lambda a: _convert_to_java_compatible_object(function(a))) - elif arg_count == 2: - return PythonToIntBiFunction(lambda a, b: _convert_to_java_compatible_object(function(a, b))) - elif arg_count == 3: - return PythonToIntTriFunction(lambda a, b, c: _convert_to_java_compatible_object(function(a, b, c))) - elif arg_count == 4: - return PythonToIntQuadFunction(lambda a, b, c, d: _convert_to_java_compatible_object(function(a, b, c, d))) - else: - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def to_long_function_cast(function, *type_args): - arg_count = len(inspect.signature(function).parameters) - if len(type_args) != arg_count: - raise ValueError(f'Invalid function: expected {len(type_args)} arguments but got {arg_count}') - - if _check_if_type_args_are_python_object_wrappers(type_args): - return default_to_long_function_cast(function, arg_count) - - from java.util.function import ToLongFunction, ToLongBiFunction - from ai.timefold.solver.core.api.function import ToLongTriFunction, ToLongQuadFunction - try: - _check_if_bytecode_translation_possible() - if arg_count == 1: - return translate_python_bytecode_to_java_bytecode(function, ToLongFunction, *type_args) - elif arg_count == 2: - return translate_python_bytecode_to_java_bytecode(function, ToLongBiFunction, *type_args) - elif arg_count == 3: - return translate_python_bytecode_to_java_bytecode(function, ToLongTriFunction, *type_args) - elif arg_count == 4: - return translate_python_bytecode_to_java_bytecode(function, ToLongQuadFunction, *type_args) - except: # noqa - return default_to_long_function_cast(function, arg_count) - - raise ValueError(f'Unexpected argument count: {arg_count}') - - -def default_to_long_function_cast(function, arg_count): - if arg_count == 1: - return PythonToLongFunction(lambda a: _convert_to_java_compatible_object(function(a))) - elif arg_count == 2: - return PythonToLongBiFunction(lambda a, b: _convert_to_java_compatible_object(function(a, b))) - elif arg_count == 3: - return PythonToLongTriFunction(lambda a, b, c: _convert_to_java_compatible_object(function(a, b, c))) - elif arg_count == 4: - return PythonToLongQuadFunction(lambda a, b, c, d: _convert_to_java_compatible_object(function(a, b, c, d))) - else: - raise ValueError(f'Unexpected argument count: {arg_count}') - - -__all__ = ['predicate_cast', - 'function_cast', - 'to_int_function_cast', - 'to_long_function_cast'] diff --git a/timefold-solver-python-core/src/main/python/score/_group_by.py b/timefold-solver-python-core/src/main/python/score/_group_by.py deleted file mode 100644 index f2626c90..00000000 --- a/timefold-solver-python-core/src/main/python/score/_group_by.py +++ /dev/null @@ -1,1146 +0,0 @@ -import dataclasses -from jpype import JClass -from typing import Callable, Any, Sequence, TypeVar, List, Set, Dict, TYPE_CHECKING, overload -if TYPE_CHECKING: - from ai.timefold.solver.core.api.score.stream.common import SequenceChain - from ai.timefold.solver.core.api.score.stream.common import LoadBalance - from ai.timefold.solver.core.api.score.stream.uni import UniConstraintCollector - from ai.timefold.solver.core.api.score.stream.bi import BiConstraintCollector - from ai.timefold.solver.core.api.score.stream.tri import TriConstraintCollector - from ai.timefold.solver.core.api.score.stream.quad import QuadConstraintCollector - - -@dataclasses.dataclass -class NoArgsConstraintCollector: - collector_creator: Callable - - -@dataclasses.dataclass -class GroupMappingSingleArgConstraintCollector: - collector_creator: Callable - group_mapping: Callable - - -@dataclasses.dataclass -class KeyValueMappingConstraintCollector: - collector_creator: Callable - key_mapping: Callable - value_mapping: Callable - - -@dataclasses.dataclass -class GroupIntMappingSingleArgConstraintCollector: - collector_creator: Callable - group_mapping: Callable - - -@dataclasses.dataclass -class GroupMappingIntMappingTwoArgConstraintCollector: - collector_creator: Callable - group_mapping: Callable - index_mapping: Callable - - -@dataclasses.dataclass -class ComposeConstraintCollector: - collector_creator: Callable - subcollectors: Sequence[Any] - compose_function: Callable - - -@dataclasses.dataclass -class ConditionalConstraintCollector: - collector_creator: Callable - predicate: Callable - delegate: Any - - -@dataclasses.dataclass -class CollectAndThenCollector: - collector_creator: Callable - delegate_collector: Any - mapping_function: Callable - - -@dataclasses.dataclass -class LoadBalanceCollector: - collector_creator: Callable - balanced_item_function: Callable - load_function: Callable | None - initial_load_function: Callable | None - - -def extract_collector(collector_info, *type_arguments): - if isinstance(collector_info, NoArgsConstraintCollector): - return collector_info.collector_creator() - elif isinstance(collector_info, GroupMappingSingleArgConstraintCollector): - return collector_info.collector_creator(function_cast(collector_info.group_mapping, *type_arguments)) - elif isinstance(collector_info, KeyValueMappingConstraintCollector): - return collector_info.collector_creator(function_cast(collector_info.key_mapping, *type_arguments), - function_cast(collector_info.value_mapping, *type_arguments)) - elif isinstance(collector_info, GroupIntMappingSingleArgConstraintCollector): - return collector_info.collector_creator(to_int_function_cast(collector_info.group_mapping, *type_arguments)) - elif isinstance(collector_info, GroupMappingIntMappingTwoArgConstraintCollector): - return collector_info.collector_creator(function_cast(collector_info.group_mapping, *type_arguments), - to_int_function_cast(collector_info.index_mapping, - JClass('java.lang.Object'))) - elif isinstance(collector_info, ComposeConstraintCollector): - subcollectors = tuple(map(lambda subcollector_info: extract_collector(subcollector_info, *type_arguments), - collector_info.subcollectors)) - compose_parameters = (JClass('java.lang.Object'),) * len(subcollectors) - compose_function = function_cast(collector_info.compose_function, *compose_parameters) - return collector_info.collector_creator(*subcollectors, compose_function) - elif isinstance(collector_info, ConditionalConstraintCollector): - delegate_collector = extract_collector(collector_info.delegate, *type_arguments) - predicate = predicate_cast(collector_info.predicate, *type_arguments) - return collector_info.collector_creator(predicate, delegate_collector) - elif isinstance(collector_info, CollectAndThenCollector): - delegate_collector = extract_collector(collector_info.delegate_collector, *type_arguments) - mapping_function = function_cast(collector_info.mapping_function, JClass('java.lang.Object')) - return collector_info.collector_creator(delegate_collector, mapping_function) - elif isinstance(collector_info, LoadBalanceCollector): - balanced_item_function = function_cast(collector_info.balanced_item_function, *type_arguments) - if collector_info.load_function is None: - return collector_info.collector_creator(balanced_item_function) - load_function = function_cast(collector_info.load_function, *type_arguments) - if collector_info.initial_load_function is None: - return collector_info.collector_creator(balanced_item_function, load_function) - initial_load_function = function_cast(collector_info.initial_load_function, *type_arguments) - return collector_info.collector_creator(balanced_item_function, load_function, initial_load_function) - else: - raise ValueError(f'Invalid Collector: {collector_info}. ' - f'Create Collectors via timefold.solver.constraint.ConstraintCollectors.') - - -def perform_group_by(constraint_stream, package, group_by_args, *type_arguments): - actual_group_by_args = [] - for i in range(len(group_by_args)): - if callable(group_by_args[i]): - actual_group_by_args.append(function_cast(group_by_args[i], *type_arguments)) - else: - collector_info = group_by_args[i] - created_collector = extract_collector(collector_info, *type_arguments) - actual_group_by_args.append(created_collector) - - if len(group_by_args) is 1: - return UniConstraintStream(constraint_stream.groupBy(*actual_group_by_args), package, - JClass('java.lang.Object')) - elif len(group_by_args) is 2: - return BiConstraintStream(constraint_stream.groupBy(*actual_group_by_args), package, - JClass('java.lang.Object'), - JClass('java.lang.Object')) - elif len(group_by_args) is 3: - return TriConstraintStream(constraint_stream.groupBy(*actual_group_by_args), package, - JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object')) - elif len(group_by_args) is 4: - return QuadConstraintStream(constraint_stream.groupBy(*actual_group_by_args), package, - JClass('java.lang.Object'), - JClass('java.lang.Object'), JClass('java.lang.Object'), - JClass('java.lang.Object')) - else: - raise ValueError - - -class ConstraintCollectors: - """ - Creates an UniConstraintCollector, BiConstraintCollector, ... - instances for use in `UniConstraintStream.group_by`, ... - """ - # Method parameter type variables - A = TypeVar('A') - B = TypeVar('B') - C = TypeVar('C') - D = TypeVar('D') - E = TypeVar('E') - Balanced = TypeVar('Balanced') - - # Method return type variables - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - Balanced_ = TypeVar('Balanced_') - - @staticmethod - def _delegate(): - from .._timefold_java_interop import ensure_init - ensure_init() - from ai.timefold.solver.core.api.score.stream import ConstraintCollectors - return ConstraintCollectors - - @overload # noqa - @staticmethod - def average(group_value_mapping: Callable[[A], int]) -> 'UniConstraintCollector[A, Any, int]': - ... - - @overload # noqa - @staticmethod - def average(group_value_mapping: Callable[[A, B], int]) -> 'BiConstraintCollector[A, B, Any, int]': - ... - - @overload # noqa - @staticmethod - def average(group_value_mapping: Callable[[A, B, C], int]) -> 'TriConstraintCollector[A, B, C, Any, int]': - ... - - @overload # noqa - @staticmethod - def average(group_value_mapping: Callable[[A, B, C, D], int]) -> 'QuadConstraintCollector[A, B, C, D, Any, int]': - ... - - @staticmethod - def average(group_value_mapping): - """ - Returns a collector that calculates an average of an int property of the elements that are being grouped. - """ - return GroupIntMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().average, - group_value_mapping) - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'UniConstraintCollector[A, Any, A_]', - sub_collector_2: 'UniConstraintCollector[A, Any, B_]', - compose_function: Callable[[A_, B_], C_]) -> 'UniConstraintCollector[A, Any, C_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'UniConstraintCollector[A, Any, A_]', - sub_collector_2: 'UniConstraintCollector[A, Any, B_]', - sub_collector_3: 'UniConstraintCollector[A, Any, C_]', - compose_function: Callable[[A_, B_, C_], D_]) -> 'UniConstraintCollector[A, Any, D_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'UniConstraintCollector[A, Any, A_]', - sub_collector_2: 'UniConstraintCollector[A, Any, B_]', - sub_collector_3: 'UniConstraintCollector[A, Any, C_]', - sub_collector_4: 'UniConstraintCollector[A, Any, D_]', - compose_function: Callable[[A_, B_, C_, D_], E_]) -> 'UniConstraintCollector[A, Any, E_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'BiConstraintCollector[A, B, Any, A_]', - sub_collector_2: 'BiConstraintCollector[A, B, Any, B_]', - compose_function: Callable[[A_, B_], C_]) -> 'BiConstraintCollector[A, B, Any, C_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'BiConstraintCollector[A, B, Any, A_]', - sub_collector_2: 'BiConstraintCollector[A, B, Any, B_]', - sub_collector_3: 'BiConstraintCollector[A, B, Any, C_]', - compose_function: Callable[[A_, B_, C_], D_]) -> 'BiConstraintCollector[A, B, Any, D_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'BiConstraintCollector[A, B, Any, A_]', - sub_collector_2: 'BiConstraintCollector[A, B, Any, B_]', - sub_collector_3: 'BiConstraintCollector[A, B, Any, C_]', - sub_collector_4: 'BiConstraintCollector[A, B, Any, D_]', - compose_function: Callable[[A_, B_, C_, D_], E_]) -> 'BiConstraintCollector[A, B, Any, E_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'TriConstraintCollector[A, B, C, Any, A_]', - sub_collector_2: 'TriConstraintCollector[A, B, C, Any, B_]', - compose_function: Callable[[A_, B_], C_]) -> 'TriConstraintCollector[A, B, C, Any, C_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'TriConstraintCollector[A, B, C, Any, A_]', - sub_collector_2: 'TriConstraintCollector[A, B, C, Any, B_]', - sub_collector_3: 'TriConstraintCollector[A, B, C, Any, C_]', - compose_function: Callable[[A_, B_, C_], D_]) -> 'TriConstraintCollector[A, B, C, Any, D_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'TriConstraintCollector[A, B, C, Any, A_]', - sub_collector_2: 'TriConstraintCollector[A, B, C, Any, B_]', - sub_collector_3: 'TriConstraintCollector[A, B, C, Any, C_]', - sub_collector_4: 'TriConstraintCollector[A, B, C, Any, D_]', - compose_function: Callable[[A_, B_, C_, D_], E_]) -> 'TriConstraintCollector[A, B, C, Any, E_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - sub_collector_2: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - compose_function: Callable[[A_, B_], C_]) -> 'QuadConstraintCollector[A, B, C, D, Any, C_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - sub_collector_2: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - sub_collector_3: 'QuadConstraintCollector[A, B, C, D, Any, C_]', - compose_function: Callable[[A_, B_, C_], D_]) -> 'QuadConstraintCollector[A, B, C, D, Any, D_]': - ... - - @overload # noqa - @staticmethod - def compose(sub_collector_1: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - sub_collector_2: 'QuadConstraintCollector[A, B, C, D, Any, B_]', - sub_collector_3: 'QuadConstraintCollector[A, B, C, D, Any, C_]', - sub_collector_4: 'QuadConstraintCollector[A, B, C, D, Any, D_]', - compose_function: Callable[[A_, B_, C_, D_], E_]) -> 'QuadConstraintCollector[A, B, C, D, Any, E_]': - ... - - @staticmethod - def compose(*args): - """ - Returns a constraint collector the result of which is a composition of other constraint collectors. - """ - if len(args) < 3: # Need at least two collectors + 1 compose function - raise ValueError - collectors = args[:-1] - compose_function = args[-1] - return ComposeConstraintCollector(ConstraintCollectors._delegate().compose, collectors, compose_function) - - @overload # noqa - @staticmethod - def conditionally(predicate: Callable[[A], bool], delegate: 'UniConstraintCollector[A, Any, A_]') -> \ - 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def conditionally(predicate: Callable[[A, B], bool], - delegate: 'BiConstraintCollector[A, B, Any, A_]') -> 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def conditionally(predicate: Callable[[A, B, C], bool], - delegate: 'TriConstraintCollector[A, B, C, Any, A_]') -> \ - 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def conditionally(predicate: Callable[[A, B, C, D], bool], - delegate: 'QuadConstraintCollector[A, B, C, D, Any, A_]') -> \ - 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @staticmethod - def conditionally(predicate, delegate): - """ - Returns a collector that delegates to the underlying collector if and only if the input tuple meets the given - condition. - """ - return ConditionalConstraintCollector(ConstraintCollectors._delegate().conditionally, - predicate, - delegate) - - @overload # noqa - @staticmethod - def collect_and_then(delegate: 'UniConstraintCollector[A, Any, A_]', - mapping_function: Callable[[A_], B_]) -> \ - 'UniConstraintCollector[A, Any, B_]': - ... - - @overload # noqa - @staticmethod - def collect_and_then(delegate: 'BiConstraintCollector[A, B, Any, A_]', - mapping_function: Callable[[A_], B_]) -> 'BiConstraintCollector[A, B, Any, B_]': - ... - - @overload # noqa - @staticmethod - def collect_and_then(delegate: 'TriConstraintCollector[A, B, C, Any, A_]', - mapping_function: Callable[[A_], B_]) -> \ - 'TriConstraintCollector[A, B, C, Any, B_]': - ... - - @overload # noqa - @staticmethod - def collect_and_then(delegate: 'QuadConstraintCollector[A, B, C, D, Any, A_]', - mapping_function: Callable[[A_], B_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, B_]': - ... - - @staticmethod - def collect_and_then(delegate, mapping_function): - """ - Returns a collector that delegates to the underlying collector and maps its result to another value. - """ - return CollectAndThenCollector(ConstraintCollectors._delegate().collectAndThen, - delegate, - mapping_function) - - @staticmethod - def count() -> 'UniConstraintCollector[A, Any, int]': - """ - Returns a collector that counts the number of elements that are being grouped. - """ - return NoArgsConstraintCollector(ConstraintCollectors._delegate().count) # noqa - - @staticmethod - def count_bi() -> 'BiConstraintCollector[A, B, Any, int]': - """ - Returns a collector that counts the number of elements that are being grouped. - """ - return NoArgsConstraintCollector(ConstraintCollectors._delegate().countBi) # noqa - - @staticmethod - def count_tri() -> 'TriConstraintCollector[A, B, C, Any, int]': - """ - Returns a collector that counts the number of elements that are being grouped. - """ - return NoArgsConstraintCollector(ConstraintCollectors._delegate().countTri) # noqa - - @staticmethod - def count_quad() -> 'QuadConstraintCollector[A, B, C, D, Any, int]': - """ - Returns a collector that counts the number of elements that are being grouped. - """ - return NoArgsConstraintCollector(ConstraintCollectors._delegate().countQuad) # noqa - - @overload # noqa - @staticmethod - def count_distinct() -> 'UniConstraintCollector[A, Any, int]': - ... - - @overload # noqa - @staticmethod - def count_distinct(group_value_mapping: Callable[[A], int]) -> 'UniConstraintCollector[A, Any, int]': - ... - - @overload # noqa - @staticmethod - def count_distinct(group_value_mapping: Callable[[A, B], int]) -> 'BiConstraintCollector[A, B, Any, int]': - ... - - @overload # noqa - @staticmethod - def count_distinct(group_value_mapping: Callable[[A, B, C], int]) -> 'TriConstraintCollector[A, B, C, Any, int]': - ... - - @overload # noqa - @staticmethod - def count_distinct(group_value_mapping: Callable[[A, B, C, D], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, int]': - ... - - @staticmethod - def count_distinct(function=None): - """ - Returns a collector that counts the number of unique elements that are being grouped. - """ - if function is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().countDistinct) - else: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().countDistinct, function) - - @overload # noqa - @staticmethod - def max() -> 'UniConstraintCollector[A, Any, A]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A], A_]) -> 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(comparator: Callable[[A, A], int]) -> 'UniConstraintCollector[A, Any, A]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A], A_], comparator: Callable[[A_, A_], int]) -> \ - 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B], A_]) -> 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B], A_], comparator: Callable[[A_, A_], int]) -> \ - 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B, C], A_]) -> 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B, C], A_], comparator: Callable[[A_, A_], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B, C, D], A_]) -> 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @overload # noqa - @staticmethod - def max(group_value_mapping: Callable[[A, B, C, D], A_], comparator: Callable[[A_, A_], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @staticmethod - def max(function=None, comparator=None): - """ - Returns a collector that finds a maximum value in a group of comparable elements. - """ - if function is None and comparator is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().max) - elif function is not None and comparator is None: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().max, function) - elif function is None and comparator is not None: - raise NotImplementedError # TODO - else: - raise NotImplementedError # TODO - - @overload # noqa - @staticmethod - def min() -> 'UniConstraintCollector[A, Any, A]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A], A_]) -> 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(comparator: Callable[[A, A], int]) -> 'UniConstraintCollector[A, Any, A]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A], A_], comparator: Callable[[A_, A_], int]) -> \ - 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B], A_]) -> 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B], A_], comparator: Callable[[A_, A_], int]) -> \ - 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B, C], A_]) -> 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B, C], A_], comparator: Callable[[A_, A_], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B, C, D], A_]) -> 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @overload # noqa - @staticmethod - def min(group_value_mapping: Callable[[A, B, C, D], A_], comparator: Callable[[A_, A_], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @staticmethod - def min(function=None, comparator=None): - """ - Returns a collector that finds a minimum value in a group of comparable elements. - """ - if function is None and comparator is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().min) - elif function is not None and comparator is None: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().min, function) - elif function is None and comparator is not None: - raise NotImplementedError # TODO - else: - raise NotImplementedError # TODO - - @overload # noqa - @staticmethod - def sum(function: Callable[[A], int]) -> 'UniConstraintCollector[A, Any, int]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B], int]) -> 'BiConstraintCollector[A, B, Any, int]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B, C], int]) -> 'TriConstraintCollector[A, B, C, Any, int]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B, C, D], int]) -> 'QuadConstraintCollector[A, B, C, D, Any, int]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A], A_], zero: A_, adder: Callable[[A_, A_], A_], subtractor: Callable[[A_, A_], A_]) \ - -> 'UniConstraintCollector[A, Any, A_]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B], A_], zero: A_, adder: Callable[[A_, A_], A_], - subtractor: Callable[[A_, A_], A_]) \ - -> 'BiConstraintCollector[A, B, Any, A_]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B, C], A_], zero: A_, adder: Callable[[A_, A_], A_], - subtractor: Callable[[A_, A_], A_]) \ - -> 'TriConstraintCollector[A, B, C, Any, A_]': - ... - - @overload # noqa - @staticmethod - def sum(function: Callable[[A, B, C, D], A_], zero: A_, adder: Callable[[A_, A_], A_], - subtractor: Callable[[A_, A_], A_]) \ - -> 'QuadConstraintCollector[A, B, C, D, Any, A_]': - ... - - @staticmethod - def sum(function, zero=None, adder=None, subtractor=None): - """ - Returns a collector that sums an int property of the elements that are being grouped. - """ - if zero is None and adder is None and subtractor is None: - return GroupIntMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().sum, function) - elif zero is not None and adder is not None and subtractor is not None: - raise NotImplementedError # TODO - else: - raise ValueError - - @overload # noqa - @staticmethod - def to_consecutive_sequences(index_map: Callable[[A], int]) -> \ - 'UniConstraintCollector[A, Any, SequenceChain[A, int]]': - ... - - @overload # noqa - @staticmethod - def to_consecutive_sequences(result_map: Callable[[A, B], A_], index_map: Callable[[A_], int]) -> \ - 'BiConstraintCollector[A, B, Any, SequenceChain[A_, int]]': - ... - - @overload # noqa - @staticmethod - def to_consecutive_sequences(result_map: Callable[[A, B, C], A_], index_map: Callable[[A_], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, SequenceChain[A_, int]]': - ... - - @overload # noqa - @staticmethod - def to_consecutive_sequences(result_map: Callable[[A, B, C, D], A_], index_map: Callable[[A_], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, SequenceChain[A_, int]]': - ... - - @staticmethod - def to_consecutive_sequences(result_or_index_map, index_map=None): - """ - Creates a constraint collector that returns SequenceChain about the first fact. - - For instance, [Shift slot=1] [Shift slot=2] [Shift slot=4] [Shift slot=6] returns the following information: - - - Consecutive Lengths: 2, 1, 1 - - Break Lengths: 1, 2 - - Consecutive Items: [[Shift slot=1] [Shift slot=2]], [[Shift slot=4]], [[Shift slot=6]] - """ - if index_map is None: - return GroupIntMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().toConsecutiveSequences, - result_or_index_map) - else: - return GroupMappingIntMappingTwoArgConstraintCollector( - ConstraintCollectors._delegate().toConsecutiveSequences, - result_or_index_map, index_map) - - @overload # noqa - @staticmethod - def to_list() -> 'UniConstraintCollector[A, Any, List[A]]': - ... - - @overload # noqa - @staticmethod - def to_list(group_value_mapping: Callable[[A], A_]) -> 'UniConstraintCollector[A, Any, List[A_]]': - ... - - @overload # noqa - @staticmethod - def to_list(group_value_mapping: Callable[[A, B], A_]) -> 'BiConstraintCollector[A, B, Any, List[A_]]': - ... - - @overload # noqa - @staticmethod - def to_list(group_value_mapping: Callable[[A, B, C], A_]) -> 'TriConstraintCollector[A, B, C, Any, List[A_]]': - ... - - @overload # noqa - @staticmethod - def to_list(group_value_mapping: Callable[[A, B, C, D], A_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, List[A_]]': - ... - - @staticmethod - def to_list(group_value_mapping=None): - """ - Creates constraint collector that returns List of the given element type. - """ - if group_value_mapping is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().toList) - else: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().toList, - group_value_mapping) - - @overload # noqa - @staticmethod - def to_set() -> 'UniConstraintCollector[A, Any, Set[A]]': - ... - - @overload # noqa - @staticmethod - def to_set(group_value_mapping: Callable[[A], A_]) -> 'UniConstraintCollector[A, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_set(group_value_mapping: Callable[[A, B], A_]) -> 'BiConstraintCollector[A, B, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_set(group_value_mapping: Callable[[A, B, C], A_]) -> 'TriConstraintCollector[A, B, C, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_set(group_value_mapping: Callable[[A, B, C, D], A_]) -> 'QuadConstraintCollector[A, B, C, D, Any, Set[A_]]': - ... - - @staticmethod - def to_set(group_value_mapping=None): - """ - Creates constraint collector that returns Set of the same element type as the ConstraintStream. - """ - if group_value_mapping is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().toSet) - else: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().toSet, group_value_mapping) - - @overload # noqa - @staticmethod - def to_sorted_set() -> 'UniConstraintCollector[A, Any, Set[A]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A], A_]) -> 'UniConstraintCollector[A, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(comparator: Callable[[A, A], int]) -> 'UniConstraintCollector[A, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A], A_], comparator: Callable[[A_, A_], int]) -> \ - 'UniConstraintCollector[A, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B], A_]) -> 'BiConstraintCollector[A, B, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B], A_], comparator: Callable[[A_, A_], int]) -> \ - 'BiConstraintCollector[A, B, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B, C], A_]) -> 'TriConstraintCollector[A, B, C, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B, C], A_], comparator: Callable[[A_, A_], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B, C, D], A_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Set[A_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_set(group_value_mapping: Callable[[A, B, C, D], A_], comparator: Callable[[A_, A_], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Set[A_]]': - ... - - @staticmethod - def to_sorted_set(group_value_mapping=None, comparator=None): - """ - Creates constraint collector that returns SortedSet of the same element type as the ConstraintStream. - """ - if group_value_mapping is None and comparator is None: - return NoArgsConstraintCollector(ConstraintCollectors._delegate().toSortedSet) - elif group_value_mapping is not None and comparator is None: - return GroupMappingSingleArgConstraintCollector(ConstraintCollectors._delegate().toSortedSet, - group_value_mapping) - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B, C, D], B_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B, C], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, Set[B_]]]': - ... - - @staticmethod - def to_map(key_mapper, value_mapper, merge_function_or_set_creator=None): - """ - Creates a constraint collector that returns a Map with given keys and values consisting of a Set of mappings. - """ - import inspect - if merge_function_or_set_creator is None: - return KeyValueMappingConstraintCollector(ConstraintCollectors._delegate().toMap, key_mapper, value_mapper) - - arg_count = len(inspect.signature(merge_function_or_set_creator).parameters) - if arg_count == 1: # set_creator - raise NotImplementedError - elif arg_count == 2: # merge_function - raise NotImplementedError - else: - raise ValueError - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A], A_], value_mapper: Callable[[A], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'UniConstraintCollector[A, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B], A_], value_mapper: Callable[[A, B], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'BiConstraintCollector[A, B, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C], A_], value_mapper: Callable[[A, B, C], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'TriConstraintCollector[A, B, C, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B, C, D], B_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, Set[B_]]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B, C], B_], - merge_function: Callable[[B_, B_], B_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, B_]]': - ... - - @overload # noqa - @staticmethod - def to_sorted_map(key_mapper: Callable[[A, B, C, D], A_], value_mapper: Callable[[A, B], B_], - set_creator: Callable[[int], Set[B_]]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, Dict[A_, Set[B_]]]': - ... - - @staticmethod - def to_sorted_map(key_mapper, value_mapper, merge_function_or_set_creator=None): - """ - Creates a constraint collector that returns a SortedMap with given keys and values consisting of a Set of - mappings. - """ - import inspect - if merge_function_or_set_creator is None: - return KeyValueMappingConstraintCollector(ConstraintCollectors._delegate().toSortedMap, key_mapper, - value_mapper) - - arg_count = len(inspect.signature(merge_function_or_set_creator).parameters) - if arg_count == 1: # set_creator - raise NotImplementedError - elif arg_count == 2: # merge_function - raise NotImplementedError - else: - raise ValueError - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A], Balanced_]) -> \ - 'UniConstraintCollector[A, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A], Balanced_], load_function: Callable[[A], int]) -> \ - 'UniConstraintCollector[A, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A], Balanced_], load_function: Callable[[A], int], - initial_load_function: Callable[[A], int]) -> \ - 'UniConstraintCollector[A, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B], Balanced_]) -> \ - 'BiConstraintCollector[A, B, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B], Balanced_], load_function: Callable[[A, B], int]) -> \ - 'BiConstraintCollector[A, B, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B], Balanced_], load_function: Callable[[A, B], int], - initial_load_function: Callable[[A, B], int]) -> \ - 'BiConstraintCollector[A, B, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C], Balanced_]) -> \ - 'TriConstraintCollector[A, B, C, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C], Balanced_], - load_function: Callable[[A, B, C], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C], Balanced_], load_function: Callable[[A, B, C], int], - initial_load_function: Callable[[A, B, C], int]) -> \ - 'TriConstraintCollector[A, B, C, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C, D], Balanced_]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C, D], Balanced_], - load_function: Callable[[A, B, C, D], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, LoadBalance[Balanced_]]': - ... - - @overload # noqa - @staticmethod - def load_balance(balanced_item_function: Callable[[A, B, C, D], Balanced_], - load_function: Callable[[A, B, C, D], int], - initial_load_function: Callable[[A, B, C, D], int]) -> \ - 'QuadConstraintCollector[A, B, C, D, Any, LoadBalance[Balanced_]]': - ... - - @staticmethod - def load_balance(balanced_item_function, load_function=None, initial_load_function=None): - """ - Returns a collector that takes a stream of items and calculates the unfairness measure from them. - The load for every item is provided by the load_function, - with the starting load provided by the initial_load_function. - - When this collector is used in a constraint stream, - it is recommended to use a score type which supports real numbers. - This is so that the unfairness measure keeps its precision - without forcing the other constraints to be multiplied by a large constant, - which would otherwise be required to implement fixed-point arithmetic. - - Parameters - ---------- - balanced_item_function: Callable[[ParameterTypes, ...], Balanced_] - The function that returns the item which should be load-balanced. - load_function: Callable[[ParameterTypes, ...], int] - How much the item should count for in the formula. - initial_load_function: Callable[[ParameterTypes, ...], int] - The initial value of the metric, allowing to provide initial state - without requiring the entire previous planning windows in the working memory. - If this function is provided, load_function must be provided as well. - """ - if load_function is None and initial_load_function is None: - return LoadBalanceCollector(ConstraintCollectors._delegate().loadBalance, balanced_item_function, None, - None) - elif initial_load_function is None: - return LoadBalanceCollector(ConstraintCollectors._delegate().loadBalance, balanced_item_function, - load_function, None) - elif load_function is None: - raise ValueError("load_function cannot be None if initial_load_function is not None") - else: - return LoadBalanceCollector(ConstraintCollectors._delegate().loadBalance, balanced_item_function, - load_function, initial_load_function) - - -# Must be at the bottom, constraint_stream depends on this module -from ._constraint_stream import * -from ._function_translator import * -__all__ = [ - 'NoArgsConstraintCollector', - 'GroupMappingSingleArgConstraintCollector', - 'KeyValueMappingConstraintCollector', - 'GroupIntMappingSingleArgConstraintCollector', - 'GroupMappingIntMappingTwoArgConstraintCollector', - 'ComposeConstraintCollector', - 'ConditionalConstraintCollector', - 'CollectAndThenCollector', - 'ConstraintCollectors', - 'perform_group_by' -] diff --git a/timefold-solver-python-core/src/main/python/score/_incremental_score_calculator.py b/timefold-solver-python-core/src/main/python/score/_incremental_score_calculator.py deleted file mode 100644 index 9168763e..00000000 --- a/timefold-solver-python-core/src/main/python/score/_incremental_score_calculator.py +++ /dev/null @@ -1,199 +0,0 @@ -from _jpyinterpreter import add_java_interface -from typing import TYPE_CHECKING -from abc import ABC, abstractmethod - - -@add_java_interface('ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator') -class IncrementalScoreCalculator(ABC): - """ - Used for incremental Python `Score` calculation. - This is much faster than `easy_score_calculator` but requires much more code to implement too. - - Any implementation is naturally stateful. - """ - @abstractmethod - def after_entity_added(self, entity) -> None: - ... - - @abstractmethod - def after_entity_removed(self, entity) -> None: - ... - - def after_list_variable_changed(self, entity, variable_name: str, start: int, end: int) -> None: - ... - - def after_list_variable_element_assigned(self, variable_name: str, element) -> None: - ... - - def after_list_variable_element_unassigned(self, variable_name: str, element) -> None: - ... - - @abstractmethod - def after_variable_changed(self, entity, variable_name: str) -> None: - ... - - @abstractmethod - def before_entity_added(self, entity) -> None: - ... - - @abstractmethod - def before_entity_removed(self, entity) -> None: - ... - - def before_list_variable_changed(self, entity, variable_name: str, start: int, end: int) -> None: - ... - - def before_list_variable_element_assigned(self, variable_name: str, element) -> None: - ... - - def before_list_variable_element_unassigned(self, variable_name: str, element) -> None: - ... - - @abstractmethod - def before_variable_changed(self, entity, variable_name: str) -> None: - ... - - @abstractmethod - def calculate_score(self): - """ - Notes - ----- - This method is only called if the `Score` cannot be predicted. - The `Score` can be predicted for example after an undo move. - """ - ... - - @abstractmethod - def reset_working_solution(self, solution) -> None: - """ - Notes - ----- - There are no `before_entity_added` and `after_entity_added` - calls for entities that are already present in the working solution. - """ - ... - - -@add_java_interface('ai.timefold.solver.core.api.score.calculator.ConstraintMatchAwareIncrementalScoreCalculator') -class ConstraintMatchAwareIncrementalScoreCalculator(IncrementalScoreCalculator): - """ - Allows an `IncrementalScoreCalculator` - to report `ConstraintMatchTotal` for explaining a score (= which score constraints match for how much) - and also for score corruption analysis. - """ - @abstractmethod - def get_constraint_match_totals(self) -> list: - """ - Notes - ----- - If a constraint is present in the problem but resulted in no matches, - it should still be present with a `ConstraintMatchTotal.constraint_match_set` size of 0. - """ - ... - - @abstractmethod - def get_indictment_map(self) -> dict | None: - """ - Returns ``None`` if it should to be calculated non-incrementally from `get_constraint_match_totals`. - """ - ... - - @abstractmethod - def reset_working_solution(self, solution, constraint_match_enabled=False) -> None: - """ - Allows for increased performance because it only tracks if `constraint_match_enabled` is true. - """ - ... - - -if not TYPE_CHECKING: - # Use type(self).method(self, ...) - # Since if the arguments are typed, they might have different signatures and - # thus not override one another, meaning the generated interface method will - # call the original method and not the "overridden" one! - def afterEntityAdded(self, entity) -> None: - type(self).after_entity_added(self, entity) - - IncrementalScoreCalculator.afterEntityAdded = afterEntityAdded - - def afterEntityRemoved(self, entity) -> None: - type(self).after_entity_removed(self, entity) - - IncrementalScoreCalculator.afterEntityRemoved = afterEntityRemoved - - def afterListVariableChanged(self, entity, variable_name, start, end) -> None: - type(self).after_list_variable_changed(self, entity, variable_name, start, end) - - IncrementalScoreCalculator.afterListVariableChanged = afterListVariableChanged - - def afterListVariableElementAssigned(self, variable_name, element) -> None: - type(self).after_list_variable_element_assigned(self, variable_name, element) - - IncrementalScoreCalculator.afterListVariableElementAssigned = afterListVariableElementAssigned - - def afterListVariableElementUnassigned(self, variable_name, element) -> None: - type(self).after_list_variable_element_unassigned(self, variable_name, element) - - IncrementalScoreCalculator.afterListVariableElementUnassigned = afterListVariableElementUnassigned - - def afterVariableChanged(self, entity, variable_name) -> None: - type(self).after_variable_changed(self, entity, variable_name) - - IncrementalScoreCalculator.afterVariableChanged = afterVariableChanged - - def beforeEntityAdded(self, entity) -> None: - type(self).before_entity_added(self, entity) - - IncrementalScoreCalculator.beforeEntityAdded = beforeEntityAdded - - def beforeEntityRemoved(self, entity) -> None: - type(self).before_entity_removed(self, entity) - - IncrementalScoreCalculator.beforeEntityRemoved = beforeEntityRemoved - - def beforeListVariableChanged(self, entity, variable_name, start, end) -> None: - type(self).before_list_variable_changed(self, entity, variable_name, start, end) - - IncrementalScoreCalculator.beforeListVariableChanged = beforeListVariableChanged - - def beforeListVariableElementAssigned(self, variable_name, element) -> None: - type(self).before_list_variable_element_assigned(self, variable_name, element) - - IncrementalScoreCalculator.beforeListVariableElementAssigned = beforeListVariableElementAssigned - - def beforeListVariableElementUnassigned(self, variable_name, element) -> None: - type(self).before_list_variable_element_unassigned(self, variable_name, element) - - IncrementalScoreCalculator.beforeListVariableElementUnassigned = beforeListVariableElementUnassigned - - def beforeVariableChanged(self, entity, variable_name) -> None: - type(self).before_variable_changed(self, entity, variable_name) - - IncrementalScoreCalculator.beforeVariableChanged = beforeVariableChanged - - def calculateScore(self): - return type(self).calculate_score(self)._to_java_score() - - IncrementalScoreCalculator.calculateScore = calculateScore - - def resetWorkingSolution(self, solution) -> None: - type(self).reset_working_solution(self, solution) - - IncrementalScoreCalculator.resetWorkingSolution = resetWorkingSolution - - def getConstraintMatchTotals(self) -> list: - return type(self).get_constraint_match_totals(self) - - ConstraintMatchAwareIncrementalScoreCalculator.getConstraintMatchTotals = getConstraintMatchTotals - - def getIndictmentMap(self) -> dict: - return type(self).get_indictment_map(self) - - ConstraintMatchAwareIncrementalScoreCalculator.getIndictmentMap = getIndictmentMap - - def resetWorkingSolution(self, solution, constraint_match_enabled=False) -> None: - type(self).reset_working_solution(self, solution, constraint_match_enabled) - - ConstraintMatchAwareIncrementalScoreCalculator.resetWorkingSolution = resetWorkingSolution - -__all__ = ['IncrementalScoreCalculator', 'ConstraintMatchAwareIncrementalScoreCalculator'] diff --git a/timefold-solver-python-core/src/main/python/score/_joiners.py b/timefold-solver-python-core/src/main/python/score/_joiners.py deleted file mode 100644 index 6defa1fd..00000000 --- a/timefold-solver-python-core/src/main/python/score/_joiners.py +++ /dev/null @@ -1,403 +0,0 @@ -from ._function_translator import * -import dataclasses -import jpype.imports # noqa -from typing import TYPE_CHECKING, Callable, TypeVar, overload - -if TYPE_CHECKING: - from ai.timefold.solver.core.api.score.stream.bi import BiJoiner - from ai.timefold.solver.core.api.score.stream.tri import TriJoiner - from ai.timefold.solver.core.api.score.stream.quad import QuadJoiner - from ai.timefold.solver.core.api.score.stream.penta import PentaJoiner - - -@dataclasses.dataclass -class SamePropertyUniJoiner: - joiner_creator: Callable - join_function: Callable - - -@dataclasses.dataclass -class PropertyJoiner: - joiner_creator: Callable - left_join_function: Callable - right_join_function: Callable - - -@dataclasses.dataclass -class SameOverlappingPropertyUniJoiner: - joiner_creator: Callable - start_function: Callable - end_function: Callable - - -@dataclasses.dataclass -class OverlappingPropertyJoiner: - joiner_creator: Callable - left_start_function: Callable - left_end_function: Callable - right_start_function: Callable - right_end_function: Callable - - -@dataclasses.dataclass -class FilteringJoiner: - joiner_creator: Callable - filter_function: Callable - - -def extract_joiners(joiner_tuple, *stream_types): - from ai.timefold.solver.core.api.score.stream.bi import BiJoiner - from ai.timefold.solver.core.api.score.stream.tri import TriJoiner - from ai.timefold.solver.core.api.score.stream.quad import QuadJoiner - from ai.timefold.solver.core.api.score.stream.penta import PentaJoiner - - if len(joiner_tuple) == 1 and (isinstance(joiner_tuple[0], list) or isinstance(joiner_tuple[0], tuple)): - joiner_tuple = joiner_tuple[0] # Joiners was passed as a list of Joiners instead of varargs - array_size = len(joiner_tuple) - output_array = None - array_type = None - if len(stream_types) == 2: - array_type = BiJoiner - output_array = BiJoiner[array_size] - elif len(stream_types) == 3: - array_type = TriJoiner - output_array = TriJoiner[array_size] - elif len(stream_types) == 4: - array_type = QuadJoiner - output_array = QuadJoiner[array_size] - elif len(stream_types) == 5: - array_type = PentaJoiner - output_array = PentaJoiner[array_size] - else: - raise ValueError - - for i in range(array_size): - joiner_info = joiner_tuple[i] - created_joiner = None - if isinstance(joiner_info, SamePropertyUniJoiner): - property_function = function_cast(joiner_info.join_function, stream_types[0]) - created_joiner = joiner_info.joiner_creator(property_function) - elif isinstance(joiner_info, PropertyJoiner): - left_property_function = function_cast(joiner_info.left_join_function, *stream_types[:-1]) - right_property_function = function_cast(joiner_info.right_join_function, stream_types[-1]) - created_joiner = joiner_info.joiner_creator(left_property_function, right_property_function) - elif isinstance(joiner_info, SameOverlappingPropertyUniJoiner): - start_function = function_cast(joiner_info.start_function, stream_types[0]) - end_function = function_cast(joiner_info.end_function, stream_types[0]) - created_joiner = joiner_info.joiner_creator(start_function, end_function) - elif isinstance(joiner_info, OverlappingPropertyJoiner): - left_start_function = function_cast(joiner_info.left_start_function, *stream_types[:-1]) - left_end_function = function_cast(joiner_info.left_end_function, *stream_types[:-1]) - right_start_function = function_cast(joiner_info.right_start_function, stream_types[-1]) - right_end_function = function_cast(joiner_info.right_end_function, stream_types[-1]) - created_joiner = joiner_info.joiner_creator(left_start_function, left_end_function, - right_start_function, right_end_function) - elif isinstance(joiner_info, FilteringJoiner): - filter_function = predicate_cast(joiner_info.filter_function, *stream_types) - created_joiner = joiner_info.joiner_creator(filter_function) - else: - raise ValueError(f'Invalid Joiner: {joiner_info}. Create Joiners via timefold.solver.constraint.Joiners.') - - output_array[i] = array_type @ created_joiner - - return output_array - - -class Joiners: - # Method parameter type variables - A = TypeVar('A') - B = TypeVar('B') - C = TypeVar('C') - D = TypeVar('D') - E = TypeVar('E') - - # Method return type variables - A_ = TypeVar('A_') - B_ = TypeVar('B_') - C_ = TypeVar('C_') - D_ = TypeVar('D_') - E_ = TypeVar('E_') - - @staticmethod - def _delegate(): - from .._timefold_java_interop import ensure_init - ensure_init() - from ai.timefold.solver.core.api.score.stream import Joiners - return Joiners - - @staticmethod - def _call_comparison_java_joiner(java_joiner, mapping_or_left_mapping, right_mapping): - if mapping_or_left_mapping is None and right_mapping is None: - raise ValueError - elif mapping_or_left_mapping is not None and right_mapping is None: - return SamePropertyUniJoiner(java_joiner, mapping_or_left_mapping) - elif mapping_or_left_mapping is not None and right_mapping is not None: - return PropertyJoiner(java_joiner, mapping_or_left_mapping, right_mapping) - else: - raise ValueError - - @overload # noqa - @staticmethod - def equal() -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def equal(property_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def equal(left_mapping: Callable[[A], A_], right_mapping: Callable[[B], B_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def equal(left_mapping: Callable[[A, B], A_], right_mapping: Callable[[C], B_]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def equal(left_mapping: Callable[[A, B, C], A_], right_mapping: Callable[[D], B_]) -> 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def equal(left_mapping: Callable[[A, B, C, D], A_], right_mapping: Callable[[E], B_]) -> 'PentaJoiner[A,B,C,D,E]': - ... - - @staticmethod - def equal(mapping_or_left_mapping=None, right_mapping=None): - """ - Joins every A and B that share a property. - """ - if mapping_or_left_mapping is None and right_mapping is None: - return SamePropertyUniJoiner(Joiners._delegate().equal, lambda a: a) - return Joiners._call_comparison_java_joiner(Joiners._delegate().equal, mapping_or_left_mapping, right_mapping) - - @overload # noqa - @staticmethod - def filtering(predicate: Callable[[A, B], bool]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def filtering(predicate: Callable[[A, B, C], bool]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def filtering(predicate: Callable[[A, B, C, D], bool]) -> 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def filtering(predicate: Callable[[A, B, C, D, E], bool]) -> 'QuadJoiner[A,B,C,D,E]': - ... - - @staticmethod - def filtering(predicate): - """ - Applies a filter to the joined tuple. - """ - return FilteringJoiner(Joiners._delegate().filtering, predicate) - - @overload # noqa - @staticmethod - def greater_than(property_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def greater_than(left_mapping: Callable[[A], A_], right_mapping: Callable[[B], B_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def greater_than(left_mapping: Callable[[A, B], A_], right_mapping: Callable[[C], B_]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def greater_than(left_mapping: Callable[[A, B, C], A_], right_mapping: Callable[[D], B_]) -> 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def greater_than(left_mapping: Callable[[A, B, C, D], A_], right_mapping: Callable[[E], B_]) -> \ - 'PentaJoiner[A,B,C,D,E]': - ... - - @staticmethod - def greater_than(mapping_or_left_mapping, right_mapping=None): - """ - Joins every A and B where a value of property on A is greater than the value of a property on B. - """ - return Joiners._call_comparison_java_joiner(Joiners._delegate().greaterThan, mapping_or_left_mapping, - right_mapping) - - @overload # noqa - @staticmethod - def greater_than_or_equal(property_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def greater_than_or_equal(left_mapping: Callable[[A], A_], right_mapping: Callable[[B], B_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def greater_than_or_equal(left_mapping: Callable[[A, B], A_], right_mapping: Callable[[C], B_]) -> \ - 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def greater_than_or_equal(left_mapping: Callable[[A, B, C], A_], right_mapping: Callable[[D], B_]) -> \ - 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def greater_than_or_equal(left_mapping: Callable[[A, B, C, D], A_], right_mapping: Callable[[E], B_]) -> \ - 'PentaJoiner[A,B,C,D,E]': - ... - - @staticmethod - def greater_than_or_equal(mapping_or_left_mapping, right_mapping=None): - """ - Joins every A and B where a value of property on A is greater than or equal to the value of a property on B. - """ - return Joiners._call_comparison_java_joiner(Joiners._delegate().greaterThanOrEqual, mapping_or_left_mapping, - right_mapping) - - @overload # noqa - @staticmethod - def less_than(property_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def less_than(left_mapping: Callable[[A], A_], right_mapping: Callable[[B], B_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def less_than(left_mapping: Callable[[A, B], A_], right_mapping: Callable[[C], B_]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def less_than(left_mapping: Callable[[A, B, C], A_], right_mapping: Callable[[D], B_]) -> 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def less_than(left_mapping: Callable[[A, B, C, D], A_], right_mapping: Callable[[E], B_]) -> \ - 'PentaJoiner[A,B,C,D,E]': - ... - - @staticmethod - def less_than(mapping_or_left_mapping, right_mapping=None): - """ - Joins every A and B where a value of property on A is less than the value of a property on B. - """ - return Joiners._call_comparison_java_joiner(Joiners._delegate().lessThan, mapping_or_left_mapping, - right_mapping) - - @overload # noqa - @staticmethod - def less_than_or_equal(property_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def less_than_or_equal(left_mapping: Callable[[A], A_], right_mapping: Callable[[B], B_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def less_than_or_equal(left_mapping: Callable[[A, B], A_], right_mapping: Callable[[C], B_]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def less_than_or_equal(left_mapping: Callable[[A, B, C], A_], right_mapping: Callable[[D], B_]) -> \ - 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def less_than_or_equal(left_mapping: Callable[[A, B, C, D], A_], right_mapping: Callable[[E], B_]) -> \ - 'PentaJoiner[A,B,C,D,E]': - ... - - @staticmethod - def less_than_or_equal(mapping_or_left_mapping, right_mapping=None): - """ - Joins every A and B where a value of property on A is less than or equal to the value of a property on B. - """ - return Joiners._call_comparison_java_joiner(Joiners._delegate().lessThanOrEqual, mapping_or_left_mapping, - right_mapping) - - @overload # noqa - @staticmethod - def overlapping(start_mapping: Callable[[A], A_], end_mapping: Callable[[A], A_]) -> 'BiJoiner[A,A]': - ... - - @overload # noqa - @staticmethod - def overlapping(left_start_mapping: Callable[[A], A_], left_end_mapping: Callable[[A], A_], - right_start_mapping: Callable[[B], A_], right_end_mapping: Callable[[B], A_]) -> 'BiJoiner[A,B]': - ... - - @overload # noqa - @staticmethod - def overlapping(left_start_mapping: Callable[[A, B], A_], left_end_mapping: Callable[[A, B], A_], - right_start_mapping: Callable[[C], A_], right_end_mapping: Callable[[C], A_]) -> 'TriJoiner[A,B,C]': - ... - - @overload # noqa - @staticmethod - def overlapping(left_start_mapping: Callable[[A, B, C], A_], left_end_mapping: Callable[[A, B, C], A_], - right_start_mapping: Callable[[D], A_], right_end_mapping: Callable[[D], A_]) -> \ - 'QuadJoiner[A,B,C,D]': - ... - - @overload # noqa - @staticmethod - def overlapping(left_start_mapping: Callable[[A, B, C, D], A_], left_end_mapping: Callable[[A, B, C, D], A_], - right_start_mapping: Callable[[E], A_], right_end_mapping: Callable[[E], A_]) -> \ - 'PentaJoiner[A,B,C,D]': - ... - - @staticmethod - def overlapping(start_mapping_or_left_start_mapping, end_mapping_or_left_end_mapping, - right_start_mapping=None, right_end_mapping=None): - """ - Joins every A and B that overlap for an interval which is specified by a start and end property on both A and - B. - """ - if start_mapping_or_left_start_mapping is None or end_mapping_or_left_end_mapping is None: - raise ValueError - if right_start_mapping is None and right_end_mapping is None: - return SameOverlappingPropertyUniJoiner(Joiners._delegate().overlapping, - start_mapping_or_left_start_mapping, - end_mapping_or_left_end_mapping) - elif right_start_mapping is not None and right_end_mapping is not None: - return OverlappingPropertyJoiner(Joiners._delegate().overlapping, - start_mapping_or_left_start_mapping, - end_mapping_or_left_end_mapping, - right_start_mapping, - right_end_mapping) - else: - raise ValueError - - -__all__ = ['Joiners', - 'SamePropertyUniJoiner', - 'PropertyJoiner', - 'OverlappingPropertyJoiner', - 'FilteringJoiner', - 'extract_joiners'] diff --git a/timefold-solver-python-core/src/main/python/score/_score.py b/timefold-solver-python-core/src/main/python/score/_score.py deleted file mode 100644 index 982011ce..00000000 --- a/timefold-solver-python-core/src/main/python/score/_score.py +++ /dev/null @@ -1,572 +0,0 @@ -from abc import ABC, abstractmethod -from typing import ClassVar -from dataclasses import dataclass, field -from jpype import JArray, JLong -from decimal import Decimal -from .._timefold_java_interop import _java_score_mapping_dict - - -@dataclass(unsafe_hash=True) -class Score(ABC): - """ - A Score is result of the score function (AKA fitness function) on a single possible solution. - Implementations must be immutable. - - Attributes - ---------- - init_score : int - The init score is the negative of the number of uninitialized genuine planning variables. - If it's 0 (which it usually is), - the `planning_solution` is fully initialized and the score's str does not mention it. - For comparisons, it's even more important than the hard score: - if you don't want this behaviour, read about overconstrained planning in the reference manual. - - is_feasible : bool - A `planning_solution` is feasible if it has no broken hard constraints and `is_solution_initialized` is - true. `SimpleScore` are always feasible, if their `init_score` is 0. - - is_solution_initialized : bool - Checks if the `planning_solution` of this score was fully initialized when it was calculated. - True if `init_score` is 0. - - See Also - -------- - HardSoftScore - """ - init_score: int = field(default=0, kw_only=True, compare=True) - - @property - @abstractmethod - def is_feasible(self) -> bool: - ... - - @abstractmethod - def _to_java_score(self) -> object: - ... - - @property - def is_solution_initialized(self) -> bool: - return self.init_score == 0 - - -@dataclass(unsafe_hash=True, order=True) -class SimpleScore(Score): - """ - This Score is based on one level of `int` constraints. - This class is immutable. - - Attributes - ---------- - score : int - The total of the broken negative constraints and fulfilled positive constraints. - Their weight is included in the total. - The score is usually a negative number because most use cases only have negative constraints. - """ - ZERO: ClassVar['SimpleScore'] - ONE: ClassVar['SimpleScore'] - - score: int = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized - - @staticmethod - def of(score: int) -> 'SimpleScore': - return SimpleScore(score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'SimpleScore': - if 'init' in score_text: - init, score = score_text.split('/') - else: - init = '0init' - score = score_text - - return SimpleScore(int(score), init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['SimpleScore'].ofUninitialized(self.init_score, self.score) - else: - return _java_score_mapping_dict['SimpleScore'].of(self.score) - - def __str__(self): - return (f'{self.score}' if self.is_solution_initialized else - f'{self.init_score}init/{self.score}') - - -SimpleScore.ZERO = SimpleScore.of(0) -SimpleScore.ONE = SimpleScore.of(1) - - -@dataclass(unsafe_hash=True, order=True) -class HardSoftScore(Score): - """ - This Score is based on two levels of int constraints: hard and soft. - Hard constraints have priority over soft constraints. - Hard constraints determine feasibility. - - This class is immutable. - - Attributes - ---------- - hard_score : int - The total of the broken negative hard constraints and fulfilled positive hard constraints. - Their weight is included in the total. - The hard score is usually a negative number because most use cases only have negative constraints. - - soft_score : int - The total of the broken negative soft constraints and fulfilled positive soft constraints. - Their weight is included in the total. - The soft score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, the soft score is irrelevant if the two scores don't have the same hard score. - """ - ZERO: ClassVar['HardSoftScore'] - ONE_HARD: ClassVar['HardSoftScore'] - ONE_SOFT: ClassVar['HardSoftScore'] - - hard_score: int = field(compare=True) - soft_score: int = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and self.hard_score >= 0 - - @staticmethod - def of(hard_score: int, soft_score: int) -> 'HardSoftScore': - return HardSoftScore(hard_score, soft_score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'HardSoftScore': - if 'init' in score_text: - init, hard, soft = score_text.split('/') - else: - init = '0init' - hard, soft = score_text.split('/') - - return HardSoftScore(int(hard.rstrip('hard')), int(soft.rstrip('soft')), - init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['HardSoftScore'].ofUninitialized(self.init_score, self.hard_score, self.soft_score) - else: - return _java_score_mapping_dict['HardSoftScore'].of(self.hard_score, self.soft_score) - - def __str__(self): - return (f'{self.hard_score}hard/{self.soft_score}soft' if self.is_solution_initialized else - f'{self.init_score}init/{self.hard_score}hard/{self.soft_score}soft') - - -HardSoftScore.ZERO = HardSoftScore.of(0, 0) -HardSoftScore.ONE_HARD = HardSoftScore.of(1, 0) -HardSoftScore.ONE_SOFT = HardSoftScore.of(0, 1) - - -@dataclass(unsafe_hash=True, order=True) -class HardMediumSoftScore(Score): - """ - This Score is based on three levels of int constraints: hard, medium and soft. - Hard constraints have priority over medium constraints. - Medium constraints have priority over soft constraints. - Hard constraints determine feasibility. - - This class is immutable. - - Attributes - ---------- - hard_score : int - The total of the broken negative hard constraints and fulfilled positive hard constraints. - Their weight is included in the total. - The hard score is usually a negative number because most use cases only have negative constraints. - - medium_score : int - The total of the broken negative medium constraints and fulfilled positive medium constraints. - Their weight is included in the total. - The medium score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, - the medium score is irrelevant if the two scores don't have the same hard score. - - soft_score : int - The total of the broken negative soft constraints and fulfilled positive soft constraints. - Their weight is included in the total. - The soft score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, - the soft score is irrelevant if the two scores don't have the same hard and medium score. - """ - ZERO: ClassVar['HardMediumSoftScore'] - ONE_HARD: ClassVar['HardMediumSoftScore'] - ONE_MEDIUM: ClassVar['HardMediumSoftScore'] - ONE_SOFT: ClassVar['HardMediumSoftScore'] - - hard_score: int = field(compare=True) - medium_score: int = field(compare=True) - soft_score: int = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and self.hard_score >= 0 - - @staticmethod - def of(hard_score: int, medium_score: int, soft_score: int) -> 'HardMediumSoftScore': - return HardMediumSoftScore(hard_score, medium_score, soft_score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'HardMediumSoftScore': - if 'init' in score_text: - init, hard, medium, soft = score_text.split('/') - else: - init = '0init' - hard, medium, soft = score_text.split('/') - - return HardMediumSoftScore(int(hard.rstrip('hard')), int(medium.rstrip('medium')), - int(soft.rstrip('soft')), init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['HardMediumSoftScore'].ofUninitialized(self.init_score, self.hard_score, - self.medium_score, self.soft_score) - else: - return _java_score_mapping_dict['HardMediumSoftScore'].of(self.hard_score, self.medium_score, self.soft_score) - - def __str__(self): - return (f'{self.hard_score}hard/{self.medium_score}medium/{self.soft_score}soft' - if self.is_solution_initialized else - f'{self.init_score}init/{self.hard_score}hard/{self.medium_score}medium/{self.soft_score}soft') - - -HardMediumSoftScore.ZERO = HardMediumSoftScore.of(0, 0, 0) -HardMediumSoftScore.ONE_HARD = HardMediumSoftScore.of(1, 0, 0) -HardMediumSoftScore.ONE_MEDIUM = HardMediumSoftScore.of(0, 1, 0) -HardMediumSoftScore.ONE_SOFT = HardMediumSoftScore.of(0, 0, 1) - - -@dataclass(unsafe_hash=True, order=True) -class BendableScore(Score): - """ - This Score is based on n levels of int constraints. - The number of levels is bendable at configuration time. - - This class is immutable. - - Attributes - ---------- - hard_scores : tuple[int, ...] - A tuple of hard scores, with earlier hard scores having higher priority than later ones. - - soft_scores : tuple[int, ...] - A tuple of soft scores, with earlier soft scores having higher priority than later ones - """ - hard_scores: tuple[int, ...] = field(compare=True) - soft_scores: tuple[int, ...] = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and all(score >= 0 for score in self.hard_scores) - - @staticmethod - def of(hard_scores: tuple[int, ...], soft_scores: tuple[int, ...]) -> 'BendableScore': - return BendableScore(hard_scores, soft_scores, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'BendableScore': - if 'init' in score_text: - init, hard_score_text, soft_score_text = score_text.split('/[') - else: - hard_score_text, soft_score_text = score_text.split('/[') - # Remove leading [ from hard score text, - # since there is no init score in the text - # (and thus the split will not consume it) - hard_score_text = hard_score_text[1:] - init = '0init' - - hard_scores = tuple([int(score) for score in hard_score_text[:hard_score_text.index(']')].split('/')]) - soft_scores = tuple([int(score) for score in soft_score_text[:soft_score_text.index(']')].split('/')]) - return BendableScore(hard_scores, soft_scores, init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - LongArrayCls = JArray(JLong) - hard_scores = LongArrayCls(self.hard_scores) - soft_scores = LongArrayCls(self.soft_scores) - if self.init_score < 0: - return _java_score_mapping_dict['BendableScore'].ofUninitialized(self.init_score, hard_scores, soft_scores) - else: - return _java_score_mapping_dict['BendableScore'].of(hard_scores, soft_scores) - - def __str__(self): - hard_text = f'{str(list(self.hard_scores)).replace(", ", "/")}hard' - soft_text = f'{str(list(self.soft_scores)).replace(", ", "/")}soft' - return (f'{hard_text}/{soft_text}' if self.is_solution_initialized else - f'{self.init_score}init/{hard_text}/{soft_text}') - - -############################################################## -# Decimal variants -############################################################## -@dataclass(unsafe_hash=True, order=True) -class SimpleDecimalScore(Score): - """ - This Score is based on one level of `Decimal` constraints. - This class is immutable. - - Attributes - ---------- - score : Decimal - The total of the broken negative constraints and fulfilled positive constraints. - Their weight is included in the total. - The score is usually a negative number because most use cases only have negative constraints. - """ - ZERO: ClassVar['SimpleDecimalScore'] - ONE: ClassVar['SimpleDecimalScore'] - - score: Decimal = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized - - @staticmethod - def of(score: Decimal) -> 'SimpleDecimalScore': - return SimpleDecimalScore(score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'SimpleDecimalScore': - if 'init' in score_text: - init, score = score_text.split('/') - else: - init = '0init' - score = score_text - - return SimpleDecimalScore(Decimal(score), init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['SimpleDecimalScore'].ofUninitialized(self.init_score, self.score) - else: - return _java_score_mapping_dict['SimpleDecimalScore'].of(self.score) - - def __str__(self): - return (f'{self.score}' if self.is_solution_initialized else - f'{self.init_score}init/{self.score}') - - -SimpleDecimalScore.ZERO = SimpleDecimalScore.of(Decimal(0)) -SimpleDecimalScore.ONE = SimpleDecimalScore.of(Decimal(1)) - - -@dataclass(unsafe_hash=True, order=True) -class HardSoftDecimalScore(Score): - """ - This Score is based on two levels of int constraints: hard and soft. - Hard constraints have priority over soft constraints. - Hard constraints determine feasibility. - - This class is immutable. - - Attributes - ---------- - hard_score : Decimal - The total of the broken negative hard constraints and fulfilled positive hard constraints. - Their weight is included in the total. - The hard score is usually a negative number because most use cases only have negative constraints. - - soft_score : Decimal - The total of the broken negative soft constraints and fulfilled positive soft constraints. - Their weight is included in the total. - The soft score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, the soft score is irrelevant if the two scores don't have the same hard score. - """ - ZERO: ClassVar['HardSoftDecimalScore'] - ONE_HARD: ClassVar['HardSoftDecimalScore'] - ONE_SOFT: ClassVar['HardSoftDecimalScore'] - - hard_score: Decimal = field(compare=True) - soft_score: Decimal = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and self.hard_score >= 0 - - @staticmethod - def of(hard_score: Decimal, soft_score: Decimal) -> 'HardSoftDecimalScore': - return HardSoftDecimalScore(hard_score, soft_score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'HardSoftDecimalScore': - if 'init' in score_text: - init, hard, soft = score_text.split('/') - else: - init = '0init' - hard, soft = score_text.split('/') - - return HardSoftDecimalScore(Decimal(hard.rstrip('hard')), Decimal(soft.rstrip('soft')), - init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['HardSoftDecimalScore'].ofUninitialized(self.init_score, self.hard_score, self.soft_score) - else: - return _java_score_mapping_dict['HardSoftDecimalScore'].of(self.hard_score, self.soft_score) - - def __str__(self): - return (f'{self.hard_score}hard/{self.soft_score}soft' if self.is_solution_initialized else - f'{self.init_score}init/{self.hard_score}hard/{self.soft_score}soft') - - -HardSoftDecimalScore.ZERO = HardSoftDecimalScore.of(Decimal(0), Decimal(0)) -HardSoftDecimalScore.ONE_HARD = HardSoftDecimalScore.of(Decimal(1), Decimal(0)) -HardSoftDecimalScore.ONE_SOFT = HardSoftDecimalScore.of(Decimal(0), Decimal(1)) - - -@dataclass(unsafe_hash=True, order=True) -class HardMediumSoftDecimalScore(Score): - """ - This Score is based on three levels of int constraints: hard, medium and soft. - Hard constraints have priority over medium constraints. - Medium constraints have priority over soft constraints. - Hard constraints determine feasibility. - - This class is immutable. - - Attributes - ---------- - hard_score : Decimal - The total of the broken negative hard constraints and fulfilled positive hard constraints. - Their weight is included in the total. - The hard score is usually a negative number because most use cases only have negative constraints. - - medium_score : Decimal - The total of the broken negative medium constraints and fulfilled positive medium constraints. - Their weight is included in the total. - The medium score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, - the medium score is irrelevant if the two scores don't have the same hard score. - - soft_score : Decimal - The total of the broken negative soft constraints and fulfilled positive soft constraints. - Their weight is included in the total. - The soft score is usually a negative number because most use cases only have negative constraints. - - In a normal score comparison, - the soft score is irrelevant if the two scores don't have the same hard and medium score. - """ - ZERO: ClassVar['HardMediumSoftDecimalScore'] - ONE_HARD: ClassVar['HardMediumSoftDecimalScore'] - ONE_MEDIUM: ClassVar['HardMediumSoftDecimalScore'] - ONE_SOFT: ClassVar['HardMediumSoftDecimalScore'] - - hard_score: Decimal = field(compare=True) - medium_score: Decimal = field(compare=True) - soft_score: Decimal = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and self.hard_score >= 0 - - @staticmethod - def of(hard_score: Decimal, medium_score: Decimal, soft_score: Decimal) -> 'HardMediumSoftDecimalScore': - return HardMediumSoftDecimalScore(hard_score, medium_score, soft_score, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'HardMediumSoftDecimalScore': - if 'init' in score_text: - init, hard, medium, soft = score_text.split('/') - else: - init = '0init' - hard, medium, soft = score_text.split('/') - - return HardMediumSoftDecimalScore(Decimal(hard.rstrip('hard')), Decimal(medium.rstrip('medium')), - Decimal(soft.rstrip('soft')), init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - if self.init_score < 0: - return _java_score_mapping_dict['HardMediumSoftDecimalScore'].ofUninitialized(self.init_score, self.hard_score, - self.medium_score, self.soft_score) - else: - return _java_score_mapping_dict['HardMediumSoftDecimalScore'].of(self.hard_score, self.medium_score, self.soft_score) - - def __str__(self): - return (f'{self.hard_score}hard/{self.medium_score}medium/{self.soft_score}soft' - if self.is_solution_initialized else - f'{self.init_score}init/{self.hard_score}hard/{self.medium_score}medium/{self.soft_score}soft') - - -HardMediumSoftDecimalScore.ZERO = HardMediumSoftDecimalScore.of(Decimal(0), Decimal(0), Decimal(0)) -HardMediumSoftDecimalScore.ONE_HARD = HardMediumSoftDecimalScore.of(Decimal(1), Decimal(0), Decimal(0)) -HardMediumSoftDecimalScore.ONE_MEDIUM = HardMediumSoftDecimalScore.of(Decimal(0), Decimal(1), Decimal(0)) -HardMediumSoftDecimalScore.ONE_SOFT = HardMediumSoftDecimalScore.of(Decimal(0), Decimal(0), Decimal(1)) - - -@dataclass(unsafe_hash=True, order=True) -class BendableDecimalScore(Score): - """ - This Score is based on n levels of int constraints. - The number of levels is bendable at configuration time. - - This class is immutable. - - Attributes - ---------- - hard_scores : tuple[Decimal, ...] - A tuple of hard scores, with earlier hard scores having higher priority than later ones. - - soft_scores : tuple[Decimal, ...] - A tuple of soft scores, with earlier soft scores having higher priority than later ones - """ - hard_scores: tuple[Decimal, ...] = field(compare=True) - soft_scores: tuple[Decimal, ...] = field(compare=True) - - @property - def is_feasible(self) -> bool: - return self.is_solution_initialized and all(score >= 0 for score in self.hard_scores) - - @staticmethod - def of(hard_scores: tuple[Decimal, ...], soft_scores: tuple[Decimal, ...]) -> 'BendableDecimalScore': - return BendableDecimalScore(hard_scores, soft_scores, init_score=0) - - @staticmethod - def parse(score_text: str) -> 'BendableDecimalScore': - if 'init' in score_text: - init, hard_score_text, soft_score_text = score_text.split('/[') - else: - hard_score_text, soft_score_text = score_text.split('/[') - # Remove leading [ from hard score text, - # since there is no init score in the text - # (and thus the split will not consume it) - hard_score_text = hard_score_text[1:] - init = '0init' - - hard_scores = tuple([Decimal(score) for score in hard_score_text[:hard_score_text.index(']')].split('/')]) - soft_scores = tuple([Decimal(score) for score in soft_score_text[:soft_score_text.index(']')].split('/')]) - return BendableDecimalScore(hard_scores, soft_scores, init_score=int(init.rstrip('init'))) - - def _to_java_score(self): - from java.math import BigDecimal - BigDecimalArrayCls = JArray(BigDecimal) - hard_scores = BigDecimalArrayCls([BigDecimal(str(score)) for score in self.hard_scores]) - soft_scores = BigDecimalArrayCls([BigDecimal(str(score)) for score in self.soft_scores]) - if self.init_score < 0: - return _java_score_mapping_dict['BendableDecimalScore'].ofUninitialized(self.init_score, hard_scores, - soft_scores) - else: - return _java_score_mapping_dict['BendableDecimalScore'].of(hard_scores, soft_scores) - - def __str__(self): - hard_text = f'[{"/".join([str(score) for score in self.hard_scores])}]hard' - soft_text = f'[{"/".join([str(score) for score in self.soft_scores])}]soft' - return (f'{hard_text}/{soft_text}' if self.is_solution_initialized else - f'{self.init_score}init/{hard_text}/{soft_text}') - - -# Import score conversions here to register conversions (circular import) -from ._score_conversions import * - -__all__ = ['Score', - 'SimpleScore', 'HardSoftScore', 'HardMediumSoftScore', 'BendableScore', - 'SimpleDecimalScore', 'HardSoftDecimalScore', 'HardMediumSoftDecimalScore', 'BendableDecimalScore'] diff --git a/timefold-solver-python-core/src/main/python/score/_score_analysis.py b/timefold-solver-python-core/src/main/python/score/_score_analysis.py deleted file mode 100644 index 5288f8f0..00000000 --- a/timefold-solver-python-core/src/main/python/score/_score_analysis.py +++ /dev/null @@ -1,667 +0,0 @@ -from .._timefold_java_interop import get_class -from .._jpype_type_conversions import to_python_score -from .._timefold_java_interop import _java_score_mapping_dict -from _jpyinterpreter import unwrap_python_like_object, add_java_interface -from dataclasses import dataclass - -from typing import overload, TypeVar, Generic, Union, TYPE_CHECKING, Any, cast, Optional, Type - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from ..score import Score - from ai.timefold.solver.core.api.score import ScoreExplanation as _JavaScoreExplanation - from ai.timefold.solver.core.api.score.analysis import ( - ConstraintAnalysis as _JavaConstraintAnalysis, - MatchAnalysis as _JavaMatchAnalysis, - ScoreAnalysis as _JavaScoreAnalysis) - from ai.timefold.solver.core.api.score.constraint import Indictment as _JavaIndictment - from ai.timefold.solver.core.api.score.constraint import (ConstraintRef as _JavaConstraintRef, - ConstraintMatch as _JavaConstraintMatch, - ConstraintMatchTotal as _JavaConstraintMatchTotal) - -Solution_ = TypeVar('Solution_') -ProblemId_ = TypeVar('ProblemId_') -Score_ = TypeVar('Score_', bound='Score') -Justification_ = TypeVar('Justification_', bound='ConstraintJustification') - - -@dataclass(frozen=True, unsafe_hash=True) -class ConstraintRef: - """ - Represents a unique identifier of a constraint. - Users should have no need to create instances of this record. - - Attributes - ---------- - package_name : str - The constraint package is the namespace of the constraint. - When using a `constraint_configuration`, it is equal to the - `ConstraintWeight.constraint_package`. - - constraint_name : str - The constraint name. - It might not be unique, but `constraint_id` is unique. - When using a `constraint_configuration`, it is equal to the `ConstraintWeight.constraint_name`. - - constraint_id : str - Always derived from `packageName` and `constraintName`. - """ - package_name: str - constraint_name: str - - @property - def constraint_id(self) -> str: - return f'{self.package_name}/{self.constraint_name}' - - @staticmethod - def parse_id(constraint_id: str): - slash_index = constraint_id.rfind('/') - if slash_index == -1: - raise ValueError( - f'The constraint_id {constraint_id} is invalid as it does not contain a package separator \'/\'.') - package_name = constraint_id[:slash_index] - constraint_name = constraint_id[slash_index + 1:] - return ConstraintRef(package_name, constraint_name) - - @staticmethod - def compose_constraint_id(solution_type_or_package: Union[type, str], constraint_name: str) -> str: - """ - Returns the constraint id with the given constraint package and the given name - - Parameters - ---------- - solution_type_or_package : type | str - the constraint package, or a class decorated with @planning_solution - (for when the constraint is in the default package) - - constraint_name : str - the name of the constraint - - Returns - ------- - str - the constraint id with the given name in the default package - """ - package = solution_type_or_package - if not isinstance(solution_type_or_package, str): - package = get_class(solution_type_or_package).getPackage().getName() - return ConstraintRef(package_name=package, - constraint_name=constraint_name).constraint_id - - def _to_java(self): - from ai.timefold.solver.core.api.score.constraint import ConstraintRef as JavaConstraintRef - return JavaConstraintRef.of(self.package_name, self.constraint_name) - - -def _safe_hash(obj: Any) -> int: - try: - return hash(obj) - except TypeError: - return id(obj) - - -@dataclass(frozen=True, eq=True) -class ConstraintMatch(Generic[Score_]): - """ - Retrievable from `ConstraintMatchTotal.constraint_match_set` and - `Indictment.constraint_match_set`. - This class is comparable for consistent ordering of constraint matches in visualizations. - The details of this ordering are unspecified and are subject to change. - If possible, prefer using `SolutionManager.analyze` instead. - """ - constraint_ref: ConstraintRef - justification: Any - indicted_objects: tuple[Any, ...] - score: Score_ - - @property - def identification_string(self) -> str: - return self.constraint_ref.constraint_id - - def __hash__(self) -> int: - combined_hash = hash(self.constraint_ref) - combined_hash ^= _safe_hash(self.justification) - for item in self.indicted_objects: - combined_hash ^= _safe_hash(item) - combined_hash ^= self.score.__hash__() - return combined_hash - - -@dataclass(eq=True) -class ConstraintMatchTotal(Generic[Score_]): - """ - Explains the Score of a `planning_solution`, - from the opposite side than `Indictment`. - Retrievable from `ScoreExplanation.constraint_match_total_map`. - If possible, prefer using `SolutionManager.analyze` instead. - """ - constraint_ref: ConstraintRef - constraint_match_count: int - constraint_match_set: set[ConstraintMatch] - constraint_weight: Optional[Score_] - score: Score_ - - def __hash__(self) -> int: - combined_hash = hash(self.constraint_ref) - combined_hash ^= hash(self.constraint_match_count) - for constraint_match in self.constraint_match_set: - combined_hash ^= hash(constraint_match) - - if self.constraint_weight is not None: - combined_hash ^= self.constraint_weight.hashCode() - - combined_hash ^= self.score.__hash__() - return combined_hash - - -@add_java_interface('ai.timefold.solver.core.api.score.stream.ConstraintJustification') -class ConstraintJustification: - """ - Marker interface for constraint justifications. - All classes used as constraint justifications must implement this interface. - Implementing classes ("implementations") - may decide to implement Comparable to preserve order of instances when displayed in user interfaces, - logs etc. This is entirely optional. - - If two instances of this class are equal, they are considered to be the same justification. - This matters in case of `SolutionManager.analyze` score analysis where such justifications are grouped together. - This situation is likely to occur in case a ConstraintStream produces duplicate tuples, - which can be avoided by using `UniConstraintStream.distinct()` or its bi, tri and quad counterparts. - Alternatively, some unique ID (such as `uuid.uuid4()`) can be used to distinguish between instances. - Score analysis does not diff contents of the implementations; - instead it uses equality of the implementations (as defined above) to tell them apart from the outside. - For this reason, it is recommended that: - - - The implementations must not use Score for equal and hash codes, - as that would prevent diffing from working entirely. - - - The implementations should not store any Score instances, - as they would not be diffed, leading to confusion with `MatchAnalysis.score`, which does get diffed. - - If the user wishes to use score analysis, - they are required to ensure that the class(es) - implementing this interface can be serialized into any format - which is supported by the SolutionManager implementation, - typically JSON. - - See Also - -------- - ConstraintMatch.justification - """ - pass - - -@dataclass(frozen=True, eq=True) -class DefaultConstraintJustification(ConstraintJustification): - """ - Default implementation of `ConstraintJustification`, returned by - `ConstraintMatch.justification` unless the user defined a custom justification mapping. - """ - facts: tuple[Any, ...] - impact: Score_ - - def __hash__(self) -> int: - combined_hash = self.impact.__hash__() - for fact in self.facts: - combined_hash ^= _safe_hash(fact) - return combined_hash - - -def _map_constraint_match_set(constraint_match_set: set['_JavaConstraintMatch']) -> set[ConstraintMatch]: - return { - ConstraintMatch(constraint_ref=ConstraintRef(package_name=constraint_match - .getConstraintRef().packageName(), - constraint_name=constraint_match - .getConstraintRef().constraintName()), - justification=_unwrap_justification(constraint_match.getJustification()), - indicted_objects=tuple([unwrap_python_like_object(indicted) - for indicted in cast(list, constraint_match.getIndictedObjectList())]), - score=to_python_score(constraint_match.getScore()) - ) - for constraint_match in constraint_match_set - } - - -def _unwrap_justification(justification: Any) -> ConstraintJustification: - from ai.timefold.solver.core.api.score.stream import ( - DefaultConstraintJustification as _JavaDefaultConstraintJustification) - if isinstance(justification, _JavaDefaultConstraintJustification): - fact_list = justification.getFacts() - return DefaultConstraintJustification(facts=tuple([unwrap_python_like_object(fact) - for fact in cast(list, fact_list)]), - impact=to_python_score(justification.getImpact())) - else: - return unwrap_python_like_object(justification) - - -def _unwrap_justification_list(justification_list: list[Any]) -> list[ConstraintJustification]: - return [_unwrap_justification(justification) for justification in justification_list] - - -class Indictment(Generic[Score_]): - """ - Explains the `Score` of a `planning_solution`, - from the opposite side than `ConstraintMatchTotal`. - Retrievable from `ScoreExplanation.indictment_map`. - - Attributes - ---------- - constraint_match_set: set[ConstraintMatch] - - score: Score_ - Sum of the constraint_match_set's `ConstraintMatch.score`. - - constraint_match_count: int - - indicted_object : Any - The object that was involved in causing the constraints to match. - It is part of `ConstraintMatch.indicted_objects` of every `ConstraintMatch` - in `constraint_match_set`. - - """ - - def __init__(self, delegate: '_JavaIndictment[Score_]'): - self._delegate = delegate - - @property - def score(self) -> Score_: - return to_python_score(self._delegate.getScore()) - - @property - def constraint_match_count(self) -> int: - return self._delegate.getConstraintMatchCount() - - @property - def constraint_match_set(self) -> set[ConstraintMatch[Score_]]: - return _map_constraint_match_set(self._delegate.getConstraintMatchSet()) - - @property - def indicted_object(self) -> Any: - return unwrap_python_like_object(self._delegate.getIndictedObject()) - - def get_justification_list(self, justification_type: Type[Justification_] = None) -> list[Justification_]: - """ - Retrieve ConstraintJustification instances associated with ConstraintMatches in `constraint_match_set`. - This is equivalent to retrieving `constraint_match_set` and collecting all `ConstraintMatch.justification` - objects into a list. - - Parameters - ---------- - justification_type : Type[Justification_], optional - If present, only include justifications of the given type in the returned list. - - Returns - ------- - list[Justification_] - guaranteed to contain unique instances - - """ - if justification_type is None: - justification_list = self._delegate.getJustificationList() - else: - justification_list = self._delegate.getJustificationList(get_class(justification_type)) - - return _unwrap_justification_list(justification_list) - - -class ScoreExplanation(Generic[Solution_]): - """ - Build by `SolutionManager.explain` - to hold `ConstraintMatchTotal`s and `Indictment`s - necessary to explain the quality of a particular `Score`. - - For a simplified, faster and JSON-friendly alternative, see `ScoreAnalysis`. - - Attributes - ---------- - solution : Solution_ - Retrieve the `planning_solution` that the score being explained comes from. - - score : Score - Return the `Score` being explained. - If the specific Score type used by the `planning_solution` - is required, retrieve it from the `solution` attribute. - - summary : str - Returns a diagnostic text - that explains the solution through the `ConstraintMatch` API - to identify which constraints or planning entities cause that score quality. - - In case of an infeasible solution, this can help diagnose the cause of that. - Do not parse the return value, its format may change without warning. - Instead, to provide this information in a UI or a service, - use `constraint_match_total_map` and `indictment_map` and convert those into a domain-specific API. - - constraint_match_total_map : dict[str, ConstraintMatchTotal] - Explains the `Score` of the `score` attribute by splitting it up per `Constraint`. - The sum of `ConstraintMatchTotal.score` equals the `score` attribute. - - indictment_map: dict[Any, Indictment] - Explains the impact of each planning entity or problem fact on the `Score`. - An `Indictment` is basically the inverse of a `ConstraintMatchTotal`: - it is a Score total for any of the indicted objects. - - The sum of `ConstraintMatchTotal.score` accessible from this `dict` - differs from `score` because each `ConstraintMatch.score` is counted for each of the indicted objects. - """ - _delegate: '_JavaScoreExplanation' - - def __init__(self, delegate: '_JavaScoreExplanation'): - self._delegate = delegate - - @property - def constraint_match_total_map(self) -> dict[str, ConstraintMatchTotal]: - return { - e.getKey(): ConstraintMatchTotal( - constraint_ref=ConstraintRef(package_name=e.getValue().getConstraintRef().packageName(), - constraint_name=e.getValue().getConstraintRef().constraintName()), - constraint_match_count=e.getValue().getConstraintMatchCount(), - constraint_match_set=_map_constraint_match_set(e.getValue().getConstraintMatchSet()), - constraint_weight=to_python_score(e.getValue().getConstraintWeight()), - score=to_python_score(e.getValue().getScore()) - ) - for e in cast(set['_JavaMap.Entry[str, _JavaConstraintMatchTotal]'], - self._delegate.getConstraintMatchTotalMap().entrySet()) - } - - @property - def indictment_map(self) -> dict[Any, Indictment]: - return { - unwrap_python_like_object(e.getKey()): Indictment(e.getValue()) - for e in cast(set['_JavaMap.Entry'], self._delegate.getIndictmentMap().entrySet()) - } - - @property - def score(self) -> 'Score': - return to_python_score(self._delegate.getScore()) - - @property - def solution(self) -> Solution_: - from _jpyinterpreter import unwrap_python_like_object - return unwrap_python_like_object(self._delegate.getSolution()) - - @property - def summary(self) -> str: - return self._delegate.getSummary() - - def get_justification_list(self, justification_type: Type[Justification_] = None) -> list[Justification_]: - """ - Explains the `Score` of the `score` attribute for all constraints. - The return value of this method is determined by several factors: - - - With Constraint Streams, - the user has an option to provide a custom justification mapping, implementing `ConstraintJustification`. - If provided, every ConstraintMatch of such constraint will be associated with this custom justification class. - Every constraint - not associated with a custom justification class will be associated with `DefaultConstraintJustification`. - - - With ConstraintMatchAwareIncrementalScoreCalculator, every `ConstraintMatch` - will be associated with the justification class that the user created it with. - - Parameters - ---------- - justification_type : Type[Justification_], optional - If present, only include justifications of the given type in the returned list. - - Returns - ------- - list[Justification_] - all constraint matches, optionally only those of a given class. - """ - if justification_type is None: - justification_list = self._delegate.getJustificationList() - else: - justification_list = self._delegate.getJustificationList(get_class(justification_type)) - - return _unwrap_justification_list(justification_list) - - -class MatchAnalysis(Generic[Score_]): - """ - Users should never create instances of this type directly. - It is available transitively via `SolutionManager.analyze`. - - Attributes - ---------- - constraint_ref : ConstraintRef - score : Score_ - justification : ConstraintJustification - """ - _delegate: '_JavaMatchAnalysis' - - def __init__(self, delegate: '_JavaMatchAnalysis'): - self._delegate = delegate - - @property - def constraint_ref(self) -> ConstraintRef: - return ConstraintRef(package_name=self._delegate.constraintRef().packageName(), - constraint_name=self._delegate.constraintRef().constraintName()) - - @property - def score(self) -> Score_: - return to_python_score(self._delegate.score()) - - @property - def justification(self) -> ConstraintJustification: - return _unwrap_justification(self._delegate.justification()) - - -class ConstraintAnalysis(Generic[Score_]): - """ - Users should never create instances of this type directly. - It is available transitively via `SolutionManager.analyze`. - - Attributes - ---------- - constraint_ref : ConstraintRef - weight : Score_ - score : Score_ - matches : list[MatchAnalysis] - None if analysis not available; - empty if constraint has no matches, - but still non-zero constraint weight; non-empty if constraint has matches. - This is a list to simplify access to individual elements, - but it contains no duplicates just like `set` wouldn't. - summary : str - Returns a diagnostic text - that explains part of the score quality through the ConstraintAnalysis API. - match_count : int - Return the match count of the constraint. - """ - _delegate: '_JavaConstraintAnalysis[Score_]' - - def __init__(self, delegate: '_JavaConstraintAnalysis[Score_]'): - self._delegate = delegate - delegate.constraintRef() - - def __str__(self): - return self.summary - - @property - def constraint_ref(self) -> ConstraintRef: - return ConstraintRef(package_name=self._delegate.constraintRef().packageName(), - constraint_name=self._delegate.constraintRef().constraintName()) - - @property - def constraint_package(self) -> str: - return self._delegate.constraintPackage() - - @property - def constraint_name(self) -> str: - return self._delegate.constraintName() - - @property - def weight(self) -> Optional[Score_]: - return to_python_score(self._delegate.weight()) - - @property - def matches(self) -> list[MatchAnalysis[Score_]]: - return [MatchAnalysis(match_analysis) - for match_analysis in cast(list['_JavaMatchAnalysis[Score_]'], self._delegate.matches())] - - @property - def match_count(self) -> int: - return self._delegate.matchCount() - - @property - def score(self) -> Score_: - return to_python_score(self._delegate.score()) - - @property - def summary(self) -> str: - return self._delegate.summarize() - - -class ScoreAnalysis: - """ - Represents the breakdown of a `Score` into individual `ConstraintAnalysis` instances, - one for each constraint. - Compared to `ScoreExplanation`, this is JSON-friendly and faster to generate. - - In order to be fully serializable to JSON, - MatchAnalysis instances must be serializable to JSON - and that requires any implementations of `ConstraintJustification` to be serializable to JSON. - This is the responsibility of the user. - - For deserialization from JSON, the user needs to provide the deserializer themselves. - This is due to the fact that, once the `ScoreAnalysis` is received over the wire, - we no longer know which Score type or `ConstraintJustification` type was used. - The user has all of that information in their domain model, - and so they are the correct party to provide the deserializer. - - Attributes - ---------- - constraint_map : dict[ConstraintRef, ConstraintAnalysis] - for each constraint identified by its `constraint_ref`, - the `ConstraintAnalysis` that describes the impact of that constraint on the overall score. - Constraints are present even if they have no matches, - unless their weight is zero; zero-weight constraints are not present. - Entries in the map have a stable iteration order; items are ordered first by `ConstraintAnalysis.weight, - then by `ConstraintAnalysis.constraint_ref`. - - constraint_analyses : list[ConstraintAnalysis] - Individual ConstraintAnalysis instances that make up this ScoreAnalysis. - - summary : str - Returns a diagnostic text that explains the solution through the `ConstraintAnalysis` API to identify which - Constraints cause that score quality. - The string is built fresh every time the method is called. - - In case of an infeasible solution, this can help diagnose the cause of that. - - Do not parse the return value, its format may change without warning. - Instead, provide this information in a UI or a service, - use `constraintAnalyses()` - and convert those into a domain-specific API. - - is_solution_initialized : bool - - Notes - ----- - the constructors of this record are off-limits. - We ask users to use exclusively `SolutionManager.analyze` to obtain instances of this record. - """ - _delegate: '_JavaScoreAnalysis' - - def __init__(self, delegate: '_JavaScoreAnalysis'): - self._delegate = delegate - - def __str__(self): - return self.summary - - def __sub__(self, other): - return self.diff(other) - - @property - def score(self) -> 'Score': - return to_python_score(self._delegate.score()) - - @property - def constraint_map(self) -> dict[ConstraintRef, ConstraintAnalysis]: - return { - ConstraintRef(package_name=e.getKey().packageName(), - constraint_name=e.getKey().constraintName()) - : ConstraintAnalysis(e.getValue()) - for e in cast(set['_JavaMap.Entry[_JavaConstraintRef, _JavaConstraintAnalysis]'], - self._delegate.constraintMap().entrySet()) - } - - @property - def constraint_analyses(self) -> list[ConstraintAnalysis]: - return [ - ConstraintAnalysis(analysis) for analysis in cast( - list['_JavaConstraintAnalysis[Score]'], self._delegate.constraintAnalyses()) - ] - - @overload - def constraint_analysis(self, constraint_package: str, constraint_name: str) -> ConstraintAnalysis: - ... - - @overload - def constraint_analysis(self, constraint_ref: 'ConstraintRef') -> ConstraintAnalysis: - ... - - def constraint_analysis(self, *args) -> ConstraintAnalysis: - """ - Performs a lookup on `constraint_map`. - - Parameters - ---------- - *args: *tuple[str, str] | *tuple[ConstraintRef] - Either two strings or a single ConstraintRef can be passed as positional arguments. - If two strings are passed, they are taken to be the constraint package and constraint name, respectively. - If a ConstraintRef is passed, it is used to perform the lookup. - - Returns - ------- - ConstraintAnalysis - None if no constraint matches of such constraint are present - """ - if len(args) == 1: - return ConstraintAnalysis(self._delegate.getConstraintAnalysis(args[0]._to_java())) - else: - return ConstraintAnalysis(self._delegate.getConstraintAnalysis(args[0], args[1])) - - @property - def summary(self) -> str: - return self._delegate.summarize() - - @property - def is_solution_initialized(self) -> bool: - return self._delegate.isSolutionInitialized() - - def diff(self, other: 'ScoreAnalysis') -> 'ScoreAnalysis': - """ - Compare this `ScoreAnalysis to another `ScoreAnalysis` - and retrieve the difference between them. - The comparison is in the direction of `this - other`. - - Example: if `this` has a score of 100 and `other` has a score of 90, - the returned score will be 10. - If this and other were inverted, the score would have been -10. - The same applies to all other properties of `ScoreAnalysis`. - - In order to properly diff `MatchAnalysis` against each other, - we rely on the user implementing `ConstraintJustification` equality correctly. - In other words, the diff will consider two justifications equal if the user says they are equal, - and it expects the hash code to be consistent with equals. - - If one `ScoreAnalysis` provides `MatchAnalysis` and the other doesn't, exception is thrown. - Such `ScoreAnalysis` instances are mutually incompatible. - - Parameters - ---------- - other : ScoreAnalysis - - Returns - ------- - ScoreExplanation - The `ScoreAnalysis` corresponding to the diff. - """ - return ScoreAnalysis(self._delegate.diff(other._delegate)) - - -__all__ = ['ScoreExplanation', - 'ConstraintRef', 'ConstraintMatch', 'ConstraintMatchTotal', - 'ConstraintJustification', 'DefaultConstraintJustification', 'Indictment', - 'ScoreAnalysis', 'ConstraintAnalysis', 'MatchAnalysis'] diff --git a/timefold-solver-python-core/src/main/python/score/_score_conversions.py b/timefold-solver-python-core/src/main/python/score/_score_conversions.py deleted file mode 100644 index 841bcd49..00000000 --- a/timefold-solver-python-core/src/main/python/score/_score_conversions.py +++ /dev/null @@ -1,42 +0,0 @@ -from jpype import JConversion -from ._score import * - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=SimpleScore) -def _convert_simple_score(jcls, score: SimpleScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=HardSoftScore) -def _convert_hard_soft_score(jcls, score: HardSoftScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=HardMediumSoftScore) -def _convert_hard_medium_soft_score(jcls, score: HardMediumSoftScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=BendableScore) -def _convert_bendable_score(jcls, score: BendableScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=SimpleDecimalScore) -def _convert_simple_decimal_score(jcls, score: SimpleDecimalScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=HardSoftDecimalScore) -def _convert_hard_soft_decimal_score(jcls, score: HardSoftDecimalScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=HardMediumSoftDecimalScore) -def _convert_hard_medium_soft_decimal_score(jcls, score: HardMediumSoftDecimalScore): - return score._to_java_score() - - -@JConversion('ai.timefold.solver.core.api.score.Score', exact=BendableDecimalScore) -def _convert_bendable_decimal_score(jcls, score: BendableDecimalScore): - return score._to_java_score() diff --git a/timefold-solver-python-core/src/main/python/score/_score_director.py b/timefold-solver-python-core/src/main/python/score/_score_director.py deleted file mode 100644 index b073030a..00000000 --- a/timefold-solver-python-core/src/main/python/score/_score_director.py +++ /dev/null @@ -1,91 +0,0 @@ -class ScoreDirector: - """ - The `ScoreDirector` holds the working solution and calculates the `Score` for it. - """ - def __init__(self, delegate): - self._delegate = delegate - - def after_entity_added(self, entity) -> None: - self._delegate.afterEntityAdded(entity) - - def after_entity_removed(self, entity) -> None: - self._delegate.afterEntityRemoved(entity) - - def after_list_variable_changed(self, entity, variable_name: str, start: int, end: int) -> None: - self._delegate.afterListVariableChanged(entity, variable_name, start, end) - - def after_list_variable_element_assigned(self, entity, variable_name: str, element) -> None: - self._delegate.afterListVariableElementAssigned(entity, variable_name, element) - - def after_list_variable_element_unassigned(self, entity, variable_name: str, element) -> None: - self._delegate.afterListVariableElementUnassigned(entity, variable_name, element) - - def after_problem_fact_added(self, entity) -> None: - self._delegate.afterProblemFactAdded(entity) - - def after_problem_fact_removed(self, entity) -> None: - self._delegate.afterProblemFactRemoved(entity) - - def after_problem_property_changed(self, entity) -> None: - self._delegate.afterProblemPropertyChanged(entity) - - def after_variable_changed(self, entity, variable_name: str) -> None: - self._delegate.afterVariableChanged(entity, variable_name) - - def before_entity_added(self, entity) -> None: - self._delegate.beforeEntityAdded(entity) - - def before_entity_removed(self, entity) -> None: - self._delegate.beforeEntityRemoved(entity) - - def before_list_variable_changed(self, entity, variable_name: str, start: int, end: int) -> None: - self._delegate.beforeListVariableChanged(entity, variable_name, start, end) - - def before_list_variable_element_assigned(self, entity, variable_name: str, element) -> None: - self._delegate.beforeListVariableElementAssigned(entity, variable_name, element) - - def before_list_variable_element_unassigned(self, entity, variable_name: str, element) -> None: - self._delegate.beforeListVariableElementUnassigned(entity, variable_name, element) - - def before_problem_fact_added(self, entity) -> None: - self._delegate.beforeProblemFactAdded(entity) - - def before_problem_fact_removed(self, entity) -> None: - self._delegate.beforeProblemFactRemoved(entity) - - def before_problem_property_changed(self, entity) -> None: - self._delegate.beforeProblemPropertyChanged(entity) - - def before_variable_changed(self, entity, variable_name: str) -> None: - self._delegate.beforeVariableChanged(entity, variable_name) - - def get_working_solution(self): - """ - The `planning_solution` that is used to calculate the `Score`. - Because a `Score` is best calculated incrementally (by deltas), - the ScoreDirector needs to be notified when its working solution changes. - """ - return self._delegate.getWorkingSolution() - - def look_up_working_object(self, working_object): - """ - Translates an entity or fact instance (often from another Thread) - to this `ScoreDirector`'s internal working instance. - Useful for move rebasing and in a `ProblemChange`. - Matching uses a `PlanningId` by default. - """ - return self._delegate.lookUpWorkingObject(working_object) - - def look_up_working_object_or_return_none(self, working_object): - """ - As defined by `look_up_working_object`, - but doesn't fail fast if no `working_object` was ever added for the `external_object`. - It's recommended to use `look_up_working_object` instead, especially in move rebasing code. - """ - return self._delegate.lookUpWorkingObject(working_object) - - def trigger_variable_listeners(self) -> None: - self._delegate.triggerVariableListeners() - - -__all__ = ['ScoreDirector'] diff --git a/timefold-solver-python-core/src/main/python/score/py.typed b/timefold-solver-python-core/src/main/python/score/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/timefold-solver-python-core/src/main/python/test/__init__.py b/timefold-solver-python-core/src/main/python/test/__init__.py deleted file mode 100644 index e3fd4333..00000000 --- a/timefold-solver-python-core/src/main/python/test/__init__.py +++ /dev/null @@ -1,718 +0,0 @@ -""" -Classes used to test constraints. -See `testing a constraint stream -`_. - -Examples --------- ->>> from timefold.solver.test import ConstraintVerifier ->>> from domain import Lesson, Room, Timeslot, generate_solver_config ->>> from constraint import overlapping_timeslots ->>> ->>> verifier = ConstraintVerifier.create(generate_solver_config()) ->>> timeslot = Timeslot(...) ->>> (verifier.verify_that(overlapping_timeslots) -... .given(Lesson('Amy', Room('A'), timeslot), -... Lesson('Amy', Room('B'), timeslot)) -... .penalizes_by(1)) -""" -from typing import Callable, Generic, List, Type, TypeVar, TYPE_CHECKING, overload, Union - -from .._jpype_type_conversions import PythonBiFunction -from .._timefold_java_interop import get_class -from ..score import ConstraintFactory -from ..config import SolverConfig - -if TYPE_CHECKING: - # These imports require a JVM to be running, so only import if type checking - from ai.timefold.solver.core.api.score.stream import Constraint, ConstraintFactory, ConstraintJustification - from ai.timefold.solver.core.config.solver import SolverConfig - from ai.timefold.solver.core.api.score import Score - - -Solution_ = TypeVar('Solution_') - - -class ConstraintVerifier(Generic[Solution_]): - """ - Entry point for the ConstraintVerifier API, which is used to test constraints defined by - a @constraint_provider function. - """ - def __init__(self, delegate): - self.delegate = delegate - - @staticmethod - def create(solver_config: SolverConfig): - from ai.timefold.solver.test.api.score.stream import ConstraintVerifier as JavaConstraintVerifier # noqa - return ConstraintVerifier(JavaConstraintVerifier.create(solver_config._to_java_solver_config())) - - @staticmethod - def build(constraint_provider: Callable[['ConstraintFactory'], List['Constraint']], - planning_solution_class: Type[Solution_], *entity_classes: Type): - from ai.timefold.solver.test.api.score.stream import ConstraintVerifier as JavaConstraintVerifier # noqa - constraint_provider_instance = get_class(constraint_provider).getConstructor().newInstance() - planning_solution_java_class = get_class(planning_solution_class) - entity_java_classes = list(map(get_class, entity_classes)) - return ConstraintVerifier(JavaConstraintVerifier.build(constraint_provider_instance, - planning_solution_java_class, - entity_java_classes)) - - @overload - def verify_that(self) -> 'MultiConstraintVerification[Solution_]': - """ - Creates a constraint verifier for all constraints of the ConstraintProvider. - """ - ... - - @overload - def verify_that(self, constraint_function: Callable[['ConstraintFactory'], 'Constraint']) -> \ - 'SingleConstraintVerification[Solution_]': - ... - - def verify_that(self, constraint_function: Callable[['ConstraintFactory'], 'Constraint'] = None): - """ - Creates a constraint verifier for a given Constraint of the ConstraintProvider. - - Parameters - ---------- - constraint_function : Callable[['ConstraintFactory'], 'Constraint'], optional - the constraint to verify. - If not provided, all constraints will be tested - """ - if constraint_function is None: - return MultiConstraintVerification(self.delegate.verifyThat()) - else: - return SingleConstraintVerification(self.delegate.verifyThat( - PythonBiFunction(lambda _, constraint_factory: - constraint_function(ConstraintFactory(constraint_factory))))) - - -class SingleConstraintVerification(Generic[Solution_]): - def __init__(self, delegate): - self.delegate = delegate - - def given(self, *facts) -> 'SingleConstraintAssertion': - """ - Set the facts for this assertion - - Parameters - ---------- - facts - never ``None``, at least one - """ - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter # noqa - from ai.timefold.jpyinterpreter.types import CPythonBackedPythonLikeObject # noqa - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference # noqa - from java.util import HashMap - from _jpyinterpreter import convert_to_java_python_like_object - reference_map = HashMap() - wrapped_facts = [] - - for fact in facts: - wrapped_fact = convert_to_java_python_like_object(fact, reference_map) - wrapped_facts.append(wrapped_fact) - - return SingleConstraintAssertion(self.delegate.given(wrapped_facts)) - - def given_solution(self, solution: 'Solution_') -> 'SingleConstraintAssertion': - """ - Set the solution to be used for this assertion - - Parameters - ---------- - solution - never ``None`` - """ - from _jpyinterpreter import convert_to_java_python_like_object - wrapped_solution = convert_to_java_python_like_object(solution) - return SingleConstraintAssertion(self.delegate.givenSolution(wrapped_solution)) - - -class MultiConstraintVerification(Generic[Solution_]): - def __init__(self, delegate): - self.delegate = delegate - - def given(self, *facts) -> 'MultiConstraintAssertion': - """ - Set the facts for this assertion - - Parameters - ---------- - facts - never ``None``, at least one - """ - from ai.timefold.jpyinterpreter import CPythonBackedPythonInterpreter # noqa - from ai.timefold.jpyinterpreter.types import CPythonBackedPythonLikeObject # noqa - from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference # noqa - from _jpyinterpreter import convert_to_java_python_like_object - from java.util import HashMap - reference_map = HashMap() - wrapped_facts = [] - - for fact in facts: - wrapped_fact = convert_to_java_python_like_object(fact, reference_map) - wrapped_facts.append(wrapped_fact) - - return MultiConstraintAssertion(self.delegate.given(wrapped_facts)) - - def given_solution(self, solution: 'Solution_') -> 'MultiConstraintAssertion': - """ - Set the solution to be used for this assertion. - - Parameters - ---------- - solution - never ``None`` - """ - from _jpyinterpreter import convert_to_java_python_like_object - wrapped_solution = convert_to_java_python_like_object(solution) - return MultiConstraintAssertion(self.delegate.givenSolution(wrapped_solution)) - - -class SingleConstraintAssertion: - def __init__(self, delegate): - self.delegate = delegate - - def justifies_with(self, *justifications: 'ConstraintJustification', message: str = None) \ - -> 'SingleConstraintAssertion': - """ - Asserts that the constraint being tested, given a set of facts, results in given justifications. - - Parameters - ---------- - justifications : ConstraintVerifier - zero or more justification to check for - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected justifications are not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - from _jpyinterpreter import convert_to_java_python_like_object - from java.util import HashMap - reference_map = HashMap() - wrapped_justifications = [] - for justification in justifications: - wrapped_justification = convert_to_java_python_like_object(justification, reference_map) - wrapped_justifications.append(wrapped_justification) - try: - if message is None: - return SingleConstraintAssertion(self.delegate.justifiesWith(*wrapped_justifications)) - else: - return SingleConstraintAssertion(self.delegate.justifiesWith(message, *wrapped_justifications)) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def justifies_with_exactly(self, *justifications: 'ConstraintJustification', message: str = None) \ - -> 'SingleConstraintAssertion': - """ - Asserts that the constraint being tested, given a set of facts, results in given justifications an no others. - - Parameters - ---------- - justifications : ConstraintVerifier - zero or more justification to check for - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected justifications are not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - from _jpyinterpreter import convert_to_java_python_like_object - from java.util import HashMap - reference_map = HashMap() - wrapped_justifications = [] - for justification in justifications: - wrapped_justification = convert_to_java_python_like_object(justification, reference_map) - wrapped_justifications.append(wrapped_justification) - try: - if message is None: - return SingleConstraintAssertion(self.delegate.justifiesWithExactly(*wrapped_justifications)) - else: - return SingleConstraintAssertion(self.delegate.justifiesWithExactly(message, *wrapped_justifications)) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def indicts_with(self, *indictments, message: str = None) -> 'SingleConstraintAssertion': - """ - Asserts that the constraint being tested, given a set of facts, results in given indictments. - - Parameters - ---------- - indictments : ConstraintVerifier - zero or more indictments to check for - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected indictments are not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - from _jpyinterpreter import convert_to_java_python_like_object - from java.util import HashMap - reference_map = HashMap() - wrapped_indictments = [] - for indictment in indictments: - wrapped_indictment = convert_to_java_python_like_object(indictment, reference_map) - wrapped_indictments.append(wrapped_indictment) - try: - if message is None: - return SingleConstraintAssertion(self.delegate.indictsWith(*wrapped_indictments)) - else: - return SingleConstraintAssertion(self.delegate.indictsWith(message, *wrapped_indictments)) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def indicts_with_exactly(self, *indictments, message: str = None) -> 'SingleConstraintAssertion': - """ - Asserts that the constraint being tested, given a set of facts, results in given indictments an no others. - - Parameters - ---------- - indictments : ConstraintVerifier - zero or more justification to check for - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected indictments are not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - from _jpyinterpreter import convert_to_java_python_like_object - from java.util import HashMap - reference_map = HashMap() - wrapped_indictments = [] - for indictment in indictments: - wrapped_indictment = convert_to_java_python_like_object(indictment, reference_map) - wrapped_indictments.append(wrapped_indictment) - try: - if message is None: - return SingleConstraintAssertion(self.delegate.indictsWithExactly(*wrapped_indictments)) - else: - return SingleConstraintAssertion(self.delegate.indictsWithExactly(message, *wrapped_indictments)) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes(self, times: int = None, message: str = None) -> None: - """ - Asserts that the Constraint being tested, given a set of facts, results in a given number of penalties. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int, optional - the expected number of penalties. - If not provided, it raises an AssertionError when there are no penalties - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed if `times` is provided, or - when there are no penalties if `times` is not provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is None and message is None: - self.delegate.penalizes() - elif times is not None and message is None: - self.delegate.penalizes(times) - elif times is None and message is not None: - self.delegate.penalizes(message) - else: - self.delegate.penalizes(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes_less_than(self, times: int, message: str = None) -> None: - """ - Asserts that the Constraint being tested, given a set of facts, - results in less than a given number of penalties. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int - the expected number of penalties. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed if `times` is provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is not None and message is None: - self.delegate.penalizesLessThan(times) - else: - self.delegate.penalizesLessThan(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes_more_than(self, times: int, message: str = None) -> None: - """ - Asserts that the Constraint being tested, given a set of facts, - results in more than a given number of penalties. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int - the expected number of penalties. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed if `times` is provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is not None and message is None: - self.delegate.penalizesMoreThan(times) - else: - self.delegate.penalizesMoreThan(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes_by(self, match_weight_total: int, message: str = None): - """ - Asserts that the `Constraint` being tested, given a set of facts, results in a specific penalty. - - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of -2hard reduces the - score by -20hard. In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - the expected penalty - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.penalizesBy(match_weight_total) - else: - self.delegate.penalizesBy(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes_by_less_than(self, match_weight_total: int, message: str = None): - """ - Asserts that the `Constraint` being tested, given a set of facts, results in less than a specific penalty. - - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of -2hard reduces the - score by -20hard. - In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - the expected penalty - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.penalizesByLessThan(match_weight_total) - else: - self.delegate.penalizesByLessThan(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def penalizes_by_more_than(self, match_weight_total: int, message: str = None): - """ - Asserts that the `Constraint` being tested, given a set of facts, results in more than a specific penalty. - - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of -2hard reduces the - score by -20hard. - In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - the expected penalty - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected penalty is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.penalizesByMoreThan(match_weight_total) - else: - self.delegate.penalizesByMoreThan(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards(self, times: int = None, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, results in a given number of rewards. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int, optional - the expected number of rewards. - If not provided, it raises an AssertionError when there are no rewards - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed if times is provided, or - when there are no rewards if times is not provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is None and message is None: - self.delegate.rewards() - elif times is not None and message is None: - self.delegate.rewards(times) - elif times is None and message is not None: - self.delegate.rewards(message) - else: - self.delegate.rewards(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards_less_than(self, times: int, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, - results in a less than a given number of rewards. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int - the expected number of rewards. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed if times is provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is not None and message is None: - self.delegate.rewardsLessThan(times) - else: - self.delegate.rewardsLessThan(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards_more_than(self, times: int, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, - results in more than a given number of rewards. - - Ignores the constraint and match weights: it only asserts the number of matches - For example: if there are two matches with weight of 10 each, this assertion will check for 2 matches. - - Parameters - ---------- - times : int - the expected number of rewards. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed if times is provided - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if times is not None and message is None: - self.delegate.rewardsMoreThan(times) - else: - self.delegate.rewardsMoreThan(times, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards_with(self, match_weight_total: int, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, results in a specific reward. - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of - -2hard reduces the score by -20hard. - In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - at least 0, expected sum of match weights of matches of the constraint. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.rewardsWith(match_weight_total) - else: - self.delegate.rewardsWith(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards_with_less_than(self, match_weight_total: int, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, results in less than a specific reward. - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of - -2hard reduces the score by -20hard. - In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - at least 0, expected sum of match weights of matches of the constraint. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.rewardsWithLessThan(match_weight_total) - else: - self.delegate.rewardsWithLessThan(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - def rewards_with_more_than(self, match_weight_total: int, message: str = None): - """ - Asserts that the Constraint being tested, given a set of facts, results in more than a specific reward. - Ignores the constraint weight: it only asserts the match weights. - For example: a match with a match weight of 10 on a constraint with a constraint weight of - -2hard reduces the score by -20hard. - In that case, this assertion checks for 10. - - Parameters - ---------- - match_weight_total : int - at least 0, expected sum of match weights of matches of the constraint. - - message : str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected reward is not observed - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.rewardsWithMoreThan(match_weight_total) - else: - self.delegate.rewardsWithMoreThan(match_weight_total, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - -class MultiConstraintAssertion: - def __init__(self, delegate): - self.delegate = delegate - - def scores(self, score: 'Score', message: str = None): - """ - Asserts that the `constraint_provider` under test, given a set of facts, results in a specific `Score`. - - Parameters - ---------- - score : Score - total score calculated for the given set of facts - - message: str, optional - description of the scenario being asserted - - Raises - ------ - AssertionError - when the expected score does not match the calculated score - """ - from java.lang import AssertionError as JavaAssertionError # noqa - try: - if message is None: - self.delegate.scores(score) - else: - self.delegate.scores(score, message) - except JavaAssertionError as e: - raise AssertionError(e.getMessage()) - - -__all__ = [ - 'ConstraintVerifier', - 'SingleConstraintVerification', 'SingleConstraintAssertion', - 'MultiConstraintVerification', 'MultiConstraintAssertion' -] diff --git a/timefold-solver-python-core/src/main/python/test/py.typed b/timefold-solver-python-core/src/main/python/test/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/timefold-solver-python-core/src/main/resources/logback.xml b/timefold-solver-python-core/src/main/resources/logback.xml deleted file mode 100644 index 286ce3e8..00000000 --- a/timefold-solver-python-core/src/main/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/PythonValueRangeFactoryTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/PythonValueRangeFactoryTest.java deleted file mode 100644 index 55b48645..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/PythonValueRangeFactoryTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package ai.timefold.solver.python; - -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; -import ai.timefold.jpyinterpreter.types.numeric.PythonFloat; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange; - -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; - -class PythonValueRangeFactoryTest { - - @Test - void createIntValueRange() { - assertThat(PythonValueRangeFactory.createIntValueRange(BigInteger.valueOf(10), BigInteger.valueOf(15))) - .extracting(CountableValueRange::createOriginalIterator, - as(InstanceOfAssertFactories.iterator(PythonInteger.class))) - .toIterable() - .containsExactly( - PythonInteger.valueOf(10), - PythonInteger.valueOf(11), - PythonInteger.valueOf(12), - PythonInteger.valueOf(13), - PythonInteger.valueOf(14)); - } - - @Test - void createIntValueRangeWithStep() { - assertThat(PythonValueRangeFactory.createIntValueRange(BigInteger.valueOf(10), BigInteger.valueOf(20), - BigInteger.valueOf(2))) - .extracting(CountableValueRange::createOriginalIterator, - as(InstanceOfAssertFactories.iterator(PythonInteger.class))) - .toIterable() - .containsExactly( - PythonInteger.valueOf(10), - PythonInteger.valueOf(12), - PythonInteger.valueOf(14), - PythonInteger.valueOf(16), - PythonInteger.valueOf(18)); - } - - @Test - void createFloatValueRange() { - assertThat(PythonValueRangeFactory.createFloatValueRange(BigDecimal.valueOf(10), BigDecimal.valueOf(15))) - .extracting(CountableValueRange::createOriginalIterator, - as(InstanceOfAssertFactories.iterator(PythonFloat.class))) - .toIterable() - .containsExactly( - PythonFloat.valueOf(10), - PythonFloat.valueOf(11), - PythonFloat.valueOf(12), - PythonFloat.valueOf(13), - PythonFloat.valueOf(14)); - } - - @Test - void createFloatValueRangeWithStep() { - assertThat(PythonValueRangeFactory.createFloatValueRange(BigDecimal.valueOf(10), BigDecimal.valueOf(20), - BigDecimal.valueOf(2))) - .extracting(CountableValueRange::createOriginalIterator, - as(InstanceOfAssertFactories.iterator(PythonFloat.class))) - .toIterable() - .containsExactly( - PythonFloat.valueOf(10), - PythonFloat.valueOf(12), - PythonFloat.valueOf(14), - PythonFloat.valueOf(16), - PythonFloat.valueOf(18)); - } - - @Test - void createBooleanValueRange() { - assertThat(PythonValueRangeFactory.createBooleanValueRange()) - .extracting(CountableValueRange::createOriginalIterator, - as(InstanceOfAssertFactories.iterator(PythonBoolean.class))) - .toIterable() - .containsExactly( - PythonBoolean.FALSE, - PythonBoolean.TRUE); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMappingTest.java deleted file mode 100644 index cf5f0656..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableDecimalScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class BendableDecimalScorePythonJavaTypeMappingTest { - BendableDecimalScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new BendableDecimalScorePythonJavaTypeMapping(PythonBendableDecimalScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonBendableDecimalScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(BendableBigDecimalScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = BendableBigDecimalScore.of( - new BigDecimal[] { BigDecimal.valueOf(10), BigDecimal.valueOf(20), BigDecimal.valueOf(30) }, - new BigDecimal[] { BigDecimal.valueOf(4), BigDecimal.valueOf(5) }); - - var initializedPythonScore = (PythonBendableDecimalScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - - assertThat(initializedPythonScore.hard_scores.size()).isEqualTo(3); - assertThat(initializedPythonScore.hard_scores.get(0)).isEqualTo(PythonDecimal.valueOf("10")); - assertThat(initializedPythonScore.hard_scores.get(1)).isEqualTo(PythonDecimal.valueOf("20")); - assertThat(initializedPythonScore.hard_scores.get(2)).isEqualTo(PythonDecimal.valueOf("30")); - - assertThat(initializedPythonScore.soft_scores.size()).isEqualTo(2); - assertThat(initializedPythonScore.soft_scores.get(0)).isEqualTo(PythonDecimal.valueOf("4")); - assertThat(initializedPythonScore.soft_scores.get(1)).isEqualTo(PythonDecimal.valueOf("5")); - - var uninitializedScore = BendableBigDecimalScore.ofUninitialized(-300, - new BigDecimal[] { BigDecimal.valueOf(10), BigDecimal.valueOf(20), BigDecimal.valueOf(30) }, - new BigDecimal[] { BigDecimal.valueOf(4), BigDecimal.valueOf(5) }); - var uninitializedPythonScore = (PythonBendableDecimalScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); - - assertThat(uninitializedPythonScore.hard_scores.size()).isEqualTo(3); - assertThat(uninitializedPythonScore.hard_scores.get(0)).isEqualTo(PythonDecimal.valueOf("10")); - assertThat(uninitializedPythonScore.hard_scores.get(1)).isEqualTo(PythonDecimal.valueOf("20")); - assertThat(uninitializedPythonScore.hard_scores.get(2)).isEqualTo(PythonDecimal.valueOf("30")); - - assertThat(uninitializedPythonScore.soft_scores.size()).isEqualTo(2); - assertThat(uninitializedPythonScore.soft_scores.get(0)).isEqualTo(PythonDecimal.valueOf("4")); - assertThat(uninitializedPythonScore.soft_scores.get(1)).isEqualTo(PythonDecimal.valueOf("5")); - } - - @Test - void toJavaObject() { - var initializedScore = PythonBendableDecimalScore.of(new int[] { 10, 20, 30 }, new int[] { 4, 5 }); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScores()).containsExactly( - BigDecimal.valueOf(10), - BigDecimal.valueOf(20), - BigDecimal.valueOf(30)); - assertThat(initializedJavaScore.softScores()).containsExactly( - BigDecimal.valueOf(4), - BigDecimal.valueOf(5)); - - var uninitializedScore = PythonBendableDecimalScore.ofUninitialized(-300, new int[] { 10, 20, 30 }, new int[] { 4, 5 }); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-300); - assertThat(uninitializedJavaScore.hardScores()).containsExactly( - BigDecimal.valueOf(10), - BigDecimal.valueOf(20), - BigDecimal.valueOf(30)); - assertThat(uninitializedJavaScore.softScores()).containsExactly( - BigDecimal.valueOf(4), - BigDecimal.valueOf(5)); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java deleted file mode 100644 index 7b890fa4..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class BendableScorePythonJavaTypeMappingTest { - BendableScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new BendableScorePythonJavaTypeMapping(PythonBendableScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonBendableScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(BendableLongScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = BendableLongScore.of(new long[] { 10, 20, 30 }, new long[] { 4, 5 }); - - var initializedPythonScore = (PythonBendableScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - - assertThat(initializedPythonScore.hard_scores.size()).isEqualTo(3); - assertThat(initializedPythonScore.hard_scores.get(0)).isEqualTo(PythonInteger.valueOf(10)); - assertThat(initializedPythonScore.hard_scores.get(1)).isEqualTo(PythonInteger.valueOf(20)); - assertThat(initializedPythonScore.hard_scores.get(2)).isEqualTo(PythonInteger.valueOf(30)); - - assertThat(initializedPythonScore.soft_scores.size()).isEqualTo(2); - assertThat(initializedPythonScore.soft_scores.get(0)).isEqualTo(PythonInteger.valueOf(4)); - assertThat(initializedPythonScore.soft_scores.get(1)).isEqualTo(PythonInteger.valueOf(5)); - - var uninitializedScore = BendableLongScore.ofUninitialized(-300, new long[] { 10, 20, 30 }, new long[] { 4, 5 }); - var uninitializedPythonScore = (PythonBendableScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); - - assertThat(uninitializedPythonScore.hard_scores.size()).isEqualTo(3); - assertThat(uninitializedPythonScore.hard_scores.get(0)).isEqualTo(PythonInteger.valueOf(10)); - assertThat(uninitializedPythonScore.hard_scores.get(1)).isEqualTo(PythonInteger.valueOf(20)); - assertThat(uninitializedPythonScore.hard_scores.get(2)).isEqualTo(PythonInteger.valueOf(30)); - - assertThat(uninitializedPythonScore.soft_scores.size()).isEqualTo(2); - assertThat(uninitializedPythonScore.soft_scores.get(0)).isEqualTo(PythonInteger.valueOf(4)); - assertThat(uninitializedPythonScore.soft_scores.get(1)).isEqualTo(PythonInteger.valueOf(5)); - } - - @Test - void toJavaObject() { - var initializedScore = PythonBendableScore.of(new int[] { 10, 20, 30 }, new int[] { 4, 5 }); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScores()).containsExactly(10, 20, 30); - assertThat(initializedJavaScore.softScores()).containsExactly(4, 5); - - var uninitializedScore = PythonBendableScore.ofUninitialized(-300, new int[] { 10, 20, 30 }, new int[] { 4, 5 }); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-300); - assertThat(uninitializedJavaScore.hardScores()).containsExactly(10, 20, 30); - assertThat(uninitializedJavaScore.softScores()).containsExactly(4, 5); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMappingTest.java deleted file mode 100644 index a84fe3a7..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftDecimalScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal.HardMediumSoftBigDecimalScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class HardMediumSoftDecimalScorePythonJavaTypeMappingTest { - HardMediumSoftDecimalScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new HardMediumSoftDecimalScorePythonJavaTypeMapping(PythonHardMediumSoftDecimalScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonHardMediumSoftDecimalScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardMediumSoftBigDecimalScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = HardMediumSoftBigDecimalScore.of(BigDecimal.valueOf(300), - BigDecimal.valueOf(20), - BigDecimal.valueOf(1)); - - var initializedPythonScore = (PythonHardMediumSoftDecimalScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.hard_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(300))); - assertThat(initializedPythonScore.medium_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(20))); - assertThat(initializedPythonScore.soft_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(1))); - - var uninitializedScore = HardMediumSoftBigDecimalScore.ofUninitialized(-4000, BigDecimal.valueOf(300), - BigDecimal.valueOf(20), - BigDecimal.valueOf(1)); - var uninitializedPythonScore = (PythonHardMediumSoftDecimalScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-4000)); - assertThat(initializedPythonScore.hard_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(300))); - assertThat(initializedPythonScore.medium_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(20))); - assertThat(initializedPythonScore.soft_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(1))); - } - - @Test - void toJavaObject() { - var initializedScore = PythonHardMediumSoftDecimalScore.of(300, 20, 1); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScore()).isEqualTo(BigDecimal.valueOf(300)); - assertThat(initializedJavaScore.mediumScore()).isEqualTo(BigDecimal.valueOf(20)); - assertThat(initializedJavaScore.softScore()).isEqualTo(BigDecimal.valueOf(1)); - - var uninitializedScore = PythonHardMediumSoftDecimalScore.ofUninitialized(-4000, 300, 20, 1); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-4000); - assertThat(initializedJavaScore.hardScore()).isEqualTo(BigDecimal.valueOf(300)); - assertThat(initializedJavaScore.mediumScore()).isEqualTo(BigDecimal.valueOf(20)); - assertThat(initializedJavaScore.softScore()).isEqualTo(BigDecimal.valueOf(1)); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java deleted file mode 100644 index b86374d8..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class HardMediumSoftScorePythonJavaTypeMappingTest { - HardMediumSoftScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new HardMediumSoftScorePythonJavaTypeMapping(PythonHardMediumSoftScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonHardMediumSoftScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardMediumSoftLongScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = HardMediumSoftLongScore.of(300, 20, 1); - - var initializedPythonScore = (PythonHardMediumSoftScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.hard_score).isEqualTo(PythonInteger.valueOf(300)); - assertThat(initializedPythonScore.medium_score).isEqualTo(PythonInteger.valueOf(20)); - assertThat(initializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(1)); - - var uninitializedScore = HardMediumSoftLongScore.ofUninitialized(-4000, 300, 20, 1); - var uninitializedPythonScore = (PythonHardMediumSoftScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-4000)); - assertThat(uninitializedPythonScore.hard_score).isEqualTo(PythonInteger.valueOf(300)); - assertThat(uninitializedPythonScore.medium_score).isEqualTo(PythonInteger.valueOf(20)); - assertThat(uninitializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(1)); - } - - @Test - void toJavaObject() { - var initializedScore = PythonHardMediumSoftScore.of(300, 20, 1); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScore()).isEqualTo(300); - assertThat(initializedJavaScore.mediumScore()).isEqualTo(20); - assertThat(initializedJavaScore.softScore()).isEqualTo(1); - - var uninitializedScore = PythonHardMediumSoftScore.ofUninitialized(-4000, 300, 20, 1); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-4000); - assertThat(uninitializedJavaScore.hardScore()).isEqualTo(300); - assertThat(uninitializedJavaScore.mediumScore()).isEqualTo(20); - assertThat(uninitializedJavaScore.softScore()).isEqualTo(1); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMappingTest.java deleted file mode 100644 index e2e934e2..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftDecimalScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class HardSoftDecimalScorePythonJavaTypeMappingTest { - HardSoftDecimalScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new HardSoftDecimalScorePythonJavaTypeMapping(PythonHardSoftDecimalScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonHardSoftDecimalScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardSoftBigDecimalScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = HardSoftBigDecimalScore.of(BigDecimal.valueOf(10), BigDecimal.valueOf(2)); - - var initializedPythonScore = (PythonHardSoftDecimalScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.hard_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(10))); - assertThat(initializedPythonScore.soft_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(2))); - - var uninitializedScore = HardSoftBigDecimalScore.ofUninitialized(-300, BigDecimal.valueOf(20), BigDecimal.valueOf(1)); - var uninitializedPythonScore = (PythonHardSoftDecimalScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); - assertThat(uninitializedPythonScore.hard_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(20))); - assertThat(uninitializedPythonScore.soft_score).isEqualTo(new PythonDecimal(BigDecimal.valueOf(1))); - } - - @Test - void toJavaObject() { - var initializedScore = PythonHardSoftDecimalScore.of(10, 2); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScore()).isEqualTo(BigDecimal.valueOf(10)); - assertThat(initializedJavaScore.softScore()).isEqualTo(BigDecimal.valueOf(2)); - - var uninitializedScore = PythonHardSoftDecimalScore.ofUninitialized(-300, 20, 1); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-300); - assertThat(uninitializedJavaScore.hardScore()).isEqualTo(BigDecimal.valueOf(20)); - assertThat(uninitializedJavaScore.softScore()).isEqualTo(BigDecimal.valueOf(1)); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java deleted file mode 100644 index 6926c3bd..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class HardSoftScorePythonJavaTypeMappingTest { - HardSoftScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new HardSoftScorePythonJavaTypeMapping(PythonHardSoftScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonHardSoftScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardSoftLongScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = HardSoftLongScore.of(10, 2); - - var initializedPythonScore = (PythonHardSoftScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.hard_score).isEqualTo(PythonInteger.valueOf(10)); - assertThat(initializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(2)); - - var uninitializedScore = HardSoftLongScore.ofUninitialized(-300, 20, 1); - var uninitializedPythonScore = (PythonHardSoftScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); - assertThat(uninitializedPythonScore.hard_score).isEqualTo(PythonInteger.valueOf(20)); - assertThat(uninitializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(1)); - } - - @Test - void toJavaObject() { - var initializedScore = PythonHardSoftScore.of(10, 2); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.hardScore()).isEqualTo(10); - assertThat(initializedJavaScore.softScore()).isEqualTo(2); - - var uninitializedScore = PythonHardSoftScore.ofUninitialized(-300, 20, 1); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-300); - assertThat(uninitializedJavaScore.hardScore()).isEqualTo(20); - assertThat(uninitializedJavaScore.softScore()).isEqualTo(1); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableDecimalScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableDecimalScore.java deleted file mode 100644 index 797de616..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableDecimalScore.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.math.BigDecimal; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonBendableDecimalScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("BendableDecimalScore", PythonBendableDecimalScore.class); - public PythonInteger init_score; - public PythonLikeTuple hard_scores; - public PythonLikeTuple soft_scores; - - public PythonBendableDecimalScore() { - super(TYPE); - } - - public static PythonBendableDecimalScore of(int[] hardScores, int[] softScores) { - var out = new PythonBendableDecimalScore(); - out.init_score = PythonInteger.ZERO; - out.hard_scores = IntStream.of(hardScores) - .mapToObj(i -> new PythonDecimal(BigDecimal.valueOf(i))) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - out.soft_scores = IntStream.of(softScores) - .mapToObj(i -> new PythonDecimal(BigDecimal.valueOf(i))) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return out; - } - - public static PythonBendableDecimalScore ofUninitialized(int initScore, int[] hardScores, int[] softScores) { - var out = new PythonBendableDecimalScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_scores = IntStream.of(hardScores) - .mapToObj(i -> new PythonDecimal(BigDecimal.valueOf(i))) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - out.soft_scores = IntStream.of(softScores) - .mapToObj(i -> new PythonDecimal(BigDecimal.valueOf(i))) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableScore.java deleted file mode 100644 index 7bc264e6..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonBendableScore.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonBendableScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("BendableScore", PythonBendableScore.class); - public PythonInteger init_score; - public PythonLikeTuple hard_scores; - public PythonLikeTuple soft_scores; - - public PythonBendableScore() { - super(TYPE); - } - - public static PythonBendableScore of(int[] hardScores, int[] softScores) { - var out = new PythonBendableScore(); - out.init_score = PythonInteger.ZERO; - out.hard_scores = IntStream.of(hardScores) - .mapToObj(PythonInteger::valueOf) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - out.soft_scores = IntStream.of(softScores) - .mapToObj(PythonInteger::valueOf) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return out; - } - - public static PythonBendableScore ofUninitialized(int initScore, int[] hardScores, int[] softScores) { - var out = new PythonBendableScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_scores = IntStream.of(hardScores) - .mapToObj(PythonInteger::valueOf) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - out.soft_scores = IntStream.of(softScores) - .mapToObj(PythonInteger::valueOf) - .collect(Collectors.toCollection(PythonLikeTuple::new)); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftDecimalScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftDecimalScore.java deleted file mode 100644 index 940d31e8..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftDecimalScore.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonHardMediumSoftDecimalScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = - new PythonLikeType("HardMediumSoftDecimalScore", PythonHardMediumSoftDecimalScore.class); - public PythonInteger init_score; - public PythonDecimal hard_score; - public PythonDecimal medium_score; - public PythonDecimal soft_score; - - public PythonHardMediumSoftDecimalScore() { - super(TYPE); - } - - public static PythonHardMediumSoftDecimalScore of(int hardScore, int mediumScore, int softScore) { - var out = new PythonHardMediumSoftDecimalScore(); - out.init_score = PythonInteger.ZERO; - out.hard_score = new PythonDecimal(BigDecimal.valueOf(hardScore)); - out.medium_score = new PythonDecimal(BigDecimal.valueOf(mediumScore)); - out.soft_score = new PythonDecimal(BigDecimal.valueOf(softScore)); - return out; - } - - public static PythonHardMediumSoftDecimalScore ofUninitialized(int initScore, int hardScore, int mediumScore, - int softScore) { - var out = new PythonHardMediumSoftDecimalScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_score = new PythonDecimal(BigDecimal.valueOf(hardScore)); - out.medium_score = new PythonDecimal(BigDecimal.valueOf(mediumScore)); - out.soft_score = new PythonDecimal(BigDecimal.valueOf(softScore)); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftScore.java deleted file mode 100644 index 94960149..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardMediumSoftScore.java +++ /dev/null @@ -1,35 +0,0 @@ -package ai.timefold.solver.python.score; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonHardMediumSoftScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("HardMediumSoftScore", PythonHardMediumSoftScore.class); - public PythonInteger init_score; - public PythonInteger hard_score; - public PythonInteger medium_score; - public PythonInteger soft_score; - - public PythonHardMediumSoftScore() { - super(TYPE); - } - - public static PythonHardMediumSoftScore of(int hardScore, int mediumScore, int softScore) { - var out = new PythonHardMediumSoftScore(); - out.init_score = PythonInteger.ZERO; - out.hard_score = PythonInteger.valueOf(hardScore); - out.medium_score = PythonInteger.valueOf(mediumScore); - out.soft_score = PythonInteger.valueOf(softScore); - return out; - } - - public static PythonHardMediumSoftScore ofUninitialized(int initScore, int hardScore, int mediumScore, int softScore) { - var out = new PythonHardMediumSoftScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_score = PythonInteger.valueOf(hardScore); - out.medium_score = PythonInteger.valueOf(mediumScore); - out.soft_score = PythonInteger.valueOf(softScore); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftDecimalScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftDecimalScore.java deleted file mode 100644 index a1595a63..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftDecimalScore.java +++ /dev/null @@ -1,35 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonHardSoftDecimalScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("HardSoftDecimalScore", PythonHardSoftDecimalScore.class); - public PythonInteger init_score; - public PythonDecimal hard_score; - public PythonDecimal soft_score; - - public PythonHardSoftDecimalScore() { - super(TYPE); - } - - public static PythonHardSoftDecimalScore of(int hardScore, int softScore) { - var out = new PythonHardSoftDecimalScore(); - out.init_score = PythonInteger.ZERO; - out.hard_score = new PythonDecimal(BigDecimal.valueOf(hardScore)); - out.soft_score = new PythonDecimal(BigDecimal.valueOf(softScore)); - return out; - } - - public static PythonHardSoftDecimalScore ofUninitialized(int initScore, int hardScore, int softScore) { - var out = new PythonHardSoftDecimalScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_score = new PythonDecimal(BigDecimal.valueOf(hardScore)); - out.soft_score = new PythonDecimal(BigDecimal.valueOf(softScore)); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftScore.java deleted file mode 100644 index 29623da7..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonHardSoftScore.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.python.score; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonHardSoftScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("HardSoftScore", PythonHardSoftScore.class); - public PythonInteger init_score; - public PythonInteger hard_score; - public PythonInteger soft_score; - - public PythonHardSoftScore() { - super(TYPE); - } - - public static PythonHardSoftScore of(int hardScore, int softScore) { - var out = new PythonHardSoftScore(); - out.init_score = PythonInteger.ZERO; - out.hard_score = PythonInteger.valueOf(hardScore); - out.soft_score = PythonInteger.valueOf(softScore); - return out; - } - - public static PythonHardSoftScore ofUninitialized(int initScore, int hardScore, int softScore) { - var out = new PythonHardSoftScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.hard_score = PythonInteger.valueOf(hardScore); - out.soft_score = PythonInteger.valueOf(softScore); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleDecimalScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleDecimalScore.java deleted file mode 100644 index 4568a5e1..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleDecimalScore.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.python.score; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonSimpleDecimalScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("SimpleDecimalScore", PythonSimpleDecimalScore.class); - public PythonInteger init_score; - public PythonDecimal score; - - public PythonSimpleDecimalScore() { - super(TYPE); - } - - public static PythonSimpleDecimalScore of(int score) { - var out = new PythonSimpleDecimalScore(); - out.init_score = PythonInteger.ZERO; - out.score = new PythonDecimal(BigDecimal.valueOf(score)); - return out; - } - - public static PythonSimpleDecimalScore ofUninitialized(int initScore, int score) { - var out = new PythonSimpleDecimalScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.score = new PythonDecimal(BigDecimal.valueOf(score)); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleScore.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleScore.java deleted file mode 100644 index dd245f93..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/PythonSimpleScore.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.solver.python.score; - -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; - -public class PythonSimpleScore extends AbstractPythonLikeObject { - public static final PythonLikeType TYPE = new PythonLikeType("SimpleScore", PythonSimpleScore.class); - public PythonInteger init_score; - public PythonInteger score; - - public PythonSimpleScore() { - super(TYPE); - } - - public static PythonSimpleScore of(int score) { - var out = new PythonSimpleScore(); - out.init_score = PythonInteger.ZERO; - out.score = PythonInteger.valueOf(score); - return out; - } - - public static PythonSimpleScore ofUninitialized(int initScore, int score) { - var out = new PythonSimpleScore(); - out.init_score = PythonInteger.valueOf(initScore); - out.score = PythonInteger.valueOf(score); - return out; - } -} diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMappingTest.java deleted file mode 100644 index 5dc2574c..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleDecimalScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.math.BigDecimal; - -import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal; -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class SimpleDecimalScorePythonJavaTypeMappingTest { - SimpleDecimalScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new SimpleDecimalScorePythonJavaTypeMapping(PythonSimpleDecimalScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonSimpleDecimalScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(SimpleBigDecimalScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = SimpleBigDecimalScore.of(BigDecimal.valueOf(10)); - - var initializedPythonScore = (PythonSimpleDecimalScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.score).isEqualTo(PythonDecimal.valueOf("10")); - - var uninitializedScore = SimpleBigDecimalScore.ofUninitialized(-5, BigDecimal.valueOf(20)); - var uninitializedPythonScore = (PythonSimpleDecimalScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-5)); - assertThat(uninitializedPythonScore.score).isEqualTo(PythonDecimal.valueOf("20")); - } - - @Test - void toJavaObject() { - var initializedScore = PythonSimpleDecimalScore.of(10); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.score()).isEqualTo(BigDecimal.valueOf(10)); - - var uninitializedScore = PythonSimpleDecimalScore.ofUninitialized(-5, 20); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-5); - assertThat(uninitializedJavaScore.score()).isEqualTo(BigDecimal.valueOf(20)); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java deleted file mode 100644 index d0536fc0..00000000 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package ai.timefold.solver.python.score; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class SimpleScorePythonJavaTypeMappingTest { - SimpleScorePythonJavaTypeMapping typeMapping; - - @BeforeEach - void setUp() throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException { - this.typeMapping = new SimpleScorePythonJavaTypeMapping(PythonSimpleScore.TYPE); - } - - @Test - void getPythonType() { - assertThat(typeMapping.getPythonType()).isEqualTo(PythonSimpleScore.TYPE); - } - - @Test - void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(SimpleLongScore.class); - } - - @Test - void toPythonObject() { - var initializedScore = SimpleLongScore.of(10); - - var initializedPythonScore = (PythonSimpleScore) typeMapping.toPythonObject(initializedScore); - - assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); - assertThat(initializedPythonScore.score).isEqualTo(PythonInteger.valueOf(10)); - - var uninitializedScore = SimpleLongScore.ofUninitialized(-5, 20); - var uninitializedPythonScore = (PythonSimpleScore) typeMapping.toPythonObject(uninitializedScore); - - assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-5)); - assertThat(uninitializedPythonScore.score).isEqualTo(PythonInteger.valueOf(20)); - } - - @Test - void toJavaObject() { - var initializedScore = PythonSimpleScore.of(10); - - var initializedJavaScore = typeMapping.toJavaObject(initializedScore); - - assertThat(initializedJavaScore.initScore()).isEqualTo(0); - assertThat(initializedJavaScore.score()).isEqualTo(10); - - var uninitializedScore = PythonSimpleScore.ofUninitialized(-5, 20); - var uninitializedJavaScore = typeMapping.toJavaObject(uninitializedScore); - - assertThat(uninitializedJavaScore.initScore()).isEqualTo(-5); - assertThat(uninitializedJavaScore.score()).isEqualTo(20); - } -} \ No newline at end of file diff --git a/timefold-solver-python-core/tests/.gitkeep b/timefold-solver-python-core/tests/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 007b69b6..00000000 --- a/tox.ini +++ /dev/null @@ -1,22 +0,0 @@ -# tox (https://tox.readthedocs.io/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. - -[tox] -env_list = py310,py311,p312 - -[testenv] -pass_env = * # needed by tox4, to pass JAVA_HOME -deps = - pytest>=8.1.1 - pytest-cov>=4.1.0 - coverage>=7.4.3 - JPype1>=1.5.0 -commands = - pytest --import-mode=importlib {posargs} tests - -[coverage:paths] -source = - timefold-solver-python-core/src/main/python - **/timefold/solver