Metadata-Version: 2.4
Name: jopy
Version: 0.1.1
Summary: Python to Java transpiler: translate Python source into readable Java 9-21 source code.
Author: jopy contributors
License-Expression: MIT
Keywords: python,java,transpiler,compiler,code-generation,source-to-source
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Java
Classifier: Topic :: Software Development :: Compilers
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# jopy

**jopy** is a Python-to-Java transpiler written in pure Python. It reads Python
source with a hand written lexer and recursive descent parser, infers Java
types, and emits readable Java source that targets every release from **Java 9
to Java 21**. It never executes the Python it reads: it only parses, analyses
and re-emits it.

Generated code can call a small Java runtime (`jopy.runtime.JoPy`) that mirrors
Python semantics where Java differs: floor division, modulo sign, negative
indexing, truthiness, slices, integer overflow checks.

- Version: 0.1.1
- Language: Python 3.9+ (the translator), Java 9-21 (the output)
- Dependencies: standard library only
- Entry points: `import jopy` and `python -m jopy <file.py>`

## Two usage modes

**Library.** Import the package and translate text, files or whole trees:

```python
import jopy

result = jopy.translate("def add(a: int, b: int) -> int:\n    return a + b\n",
                        filename="math_utils.py")
print(result.java)
print(result.stats["java_lines"], "Java lines")
```

**Command line.** Translate from a shell, optionally compiling the result with
`javac`:

```bash
python -m jopy math_utils.py --java-version 17 --compile -o build/java
```

## Quick start

```bash
# 1. work from a checkout of this repository
cd /path/to/jopy

# 2. translate one file and print the Java on stdout
python -m jopy examples/greeting.py

# 3. write files, copy the runtime and verify with javac
python -m jopy examples/greeting.py -o build --java-version 17 --compile

# 4. run the compiled program
java -cp build/classes Greeting
```

Step 3 and 4 are verified for `examples/greeting.py`: the translation exits 0,
javac accepts the output and the program prints the same lines as
`python examples/greeting.py`. The `examples/` directory holds runnable
programs that show one feature each; they are checked by hand rather than by
the suite (the automated fixtures live in `tests/fixtures/`), so treat a
non-zero exit of `python -m jopy examples -o build --compile --explain` as
"read the file it names", not as a documentation bug.

One exception to keep in mind: `examples/api_usage.py` is a **host script**
that drives the Python jopy API - it calls `jopy.translate`, reads
`result.diagnostics`/`result.stats` and installs a progress callback. It is not
a Java translation target: translating it emits calls to a local module class
that only exists if the jopy package itself is translated too, so javac reports
unresolved symbols for it. Run that one with plain Python
(`python examples/api_usage.py`) and use the other examples for `--compile`.
See [examples/README.md](examples/README.md).

Machine readable progress is written to stderr as JSON lines whenever stderr is
not a terminal, so a pipeline stays clean:

```bash
python -m jopy src/ -o build --progress json 2> progress.jsonl
```

## Before and after

Input, `greet.py`:

```python
def greet(name: str) -> str:
    return "Hello, " + name + "!"


def main() -> None:
    for i in range(3):
        print(greet("world"), i)


if __name__ == "__main__":
    main()
```

Output (`python -m jopy greet.py --java-version 17`):

```java
import jopy.runtime.JoPy;

public class Greet {
    public static void main(String[] args) {
        main();
    }

    public static String greet(String name) {
        return (("Hello, " + name) + "!");
    }

    public static void main() {
        for (int i = 0; i < 3; i++) {
            System.out.println(JoPy.joinStrings(" ", greet("world"), i));
        }
    }
}
```

`print(a, b)` becomes one call because Python prints space separated values,
and `range(3)` becomes a counting loop because jopy can prove the bounds are
integral. Compiling and running that Java prints exactly what the Python
prints.

## Feature matrix

| Area | Support | Notes |
| --- | --- | --- |
| Lexer, parser, AST | Full Python 3.8-3.12 grammar subset | INDENT/DEDENT tokenising, f-strings, match statements, walrus, PEP 604 unions |
| Type inference | Heuristic, never fatal | Annotations, a usage driven narrowing pass and duck typing (a value used only through members that exactly one known type provides gets that type); a class of another translated module is a real Java type, and `isinstance` against a name no Java class declares goes through `JoPy.isInstance`. What stays unprovable becomes `Object`, dispatches through `JoPy` helpers and is reported with a manual-fix note |
| Functions | Parameters, defaults, `*args`, `**kwargs`, decorators | Defaults expand into overloads, unknown decorators become comments plus a manual note |
| Classes | Inheritance, static/class methods, properties, dunder methods | The first base becomes `extends`; each additional *local* base contributes its fields and methods (mixins are inlined, with a manual note per copied member) |
| Modules | 30+ stdlib modules mapped | Unknown modules produce a manual note and a commented import. `decimal` maps onto the bundled `jopy.runtime.PyDecimal` (see [docs/05-language-mapping.md](docs/05-language-mapping.md#decimal)) |
| Containers | list / dict / set / tuple | Tuples are `List`, dicts are `Map`, sets are `Set` |
| Exceptions | try / except / else / finally, multi-catch | Checked exceptions are caught as `Exception` and rethrown with `JoPy.sneakyThrow` |
| Comprehensions | list / set / dict / generator expressions | Emitted as `Stream` pipelines |
| Generators | `yield` / `yield from` inside a function | Lazy `Coroutine` on one daemon thread; `next`, `send`, `throw` and `close` work, unbounded generators are fine |
| Nested functions | `def` inside a `def` | `PyCallable` closures that capture the enclosing locals |
| Operator overloading | `__add__`, `__lt__`, `__len__`, `__getitem__`, ... | The class implements `PyOperand`, so `a + b`, `a < b`, `len(a)` and `a[i]` keep working at the call site |
| `async` / `await` | `async def`, `await`, `asyncio.run` | `Task` on a daemon thread; `asyncio.gather`/`sleep` map to `JoPy` helpers |
| Context managers | `with open(...) as f:` | Becomes try-with-resources when the manager is `AutoCloseable` |
| Match statements | Sequence, mapping, class, value, singleton, or, as, wildcard | Emitted as an if/else chain on every target release |
| Comments and docstrings | Preserved by default | `--no-comments` / `--no-javadoc` drop them |
| Progress reporting | Terminal bar or JSON lines | Same events, two renderers, plus a `progress_callback` hook |
| Manual-fix reporting | Structured diagnostics | `--report report.json` writes every diagnostic as JSON |
| Compiling the result | `--compile` drives `javac` | `--javac-output` picks the class directory, `--classpath` adds class path entries and `--no-deps` compiles only the files named on the command line instead of rebuilding every local module the output directory holds. `--no-runtime` keeps the jopy runtime out of the copy *and* out of the javac command, so `--compile --no-deps --no-runtime --classpath <classes>` compiles one file and produces one class file |
| JDK provisioning | Automatic download or local archive | `--install-jdk` fetches Temurin into `~/.jopy/jdks` (or `$JOPY_HOME/jdks`), `--jdk-archive FILE` installs a downloaded archive without a network, `--list-jdks` shows what is managed, and `find_javac()` finds it |

Constructs jopy cannot express faithfully are **not** silently dropped: they are
reported as `manual-fix` diagnostics, embedded as `// MANUAL: ...` comments in
the generated Java and listed by `--explain`. See
[docs/07-manual-work.md](docs/07-manual-work.md).

## Architecture

```text
    Python source (.py)
            |
            v
   +------------------+     tokens + comments + INDENT/DEDENT
   |  lexer.py        |-------------------------------------+
   +------------------+                                     |
            |                                               |
            v                                               v
   +------------------+     syntax tree (ast_nodes)   +-----------+
   |  parser.py       |-----------------------------> | tokens.py |
   +------------------+                               +-----------+
            |
            v
   +------------------+     Java types (javatypes)    +-------------+
   |  inference.py    |-----------------------------> | mappings.py |
   +------------------+     builtins, modules, ...    +-------------+
            |
            v
   +------------------+     Java source + diagnostics + line map
   |  codegen/        |-------------------------------------+
   +------------------+                                     |
            |                                               v
            v                                    +---------------------------+
   +------------------+   TranslationResult      | runtime/JoPy.java         |
   |  api.py          |------------------------> | runtime/PyCallable.java   |
   +------------------+   progress.py events     | runtime/PyOperand.java    |
            |                                    | runtime/Coroutine.java    |
            |                                    | runtime/Task.java         |
            |                                    | runtime/PyDecimal.java    |
            |                                    | runtime/Json.java         |
            |                                    | runtime/Reflection.java   |
            |                                    +---------------------------+
            v
   +------------------+   exit codes 0/1/2/3
   |  cli.py          |
   +------------------+
```

More detail, including how comments and source lines survive the round trip,
lives in [docs/06-architecture.md](docs/06-architecture.md).

## Requirements

- **Python 3.9 or newer** to run jopy itself. Only the standard library is used.
- **A JDK is optional.** It is needed only for `--compile`, `javac_release_supported()`
  and the `--javac-output` workflow. The JDK must be at least as new as the
  `--java-version` you target (`javac --release N` cannot target a newer release).
  When none is installed, `jopy --install-jdk` fetches one into the managed root
  (`~/.jopy/jdks`, or `$JOPY_HOME/jdks`) and prints its path; a manually downloaded
  archive installs offline with `--jdk-archive FILE`, and `--list-jdks` shows what
  is already there. See [docs/02-cli-reference.md](docs/02-cli-reference.md#jdk-management-options).
- Verified during the writing of this documentation with Python 3.11.9 and
  JDK 21 (`javac 21.0.12`); the suite targets Java 9-21 and skips the release
  columns a local JDK cannot target (see
  [docs/09-testing.md](docs/09-testing.md#the-java-version-the-suite-needs)).

## Installation

The checkout is directly runnable, because the package lives in the `jopy/`
directory at the repository root:

```bash
cd /path/to/jopy
python -m jopy --version      # jopy 0.1.1
```

From another working directory, put the checkout on the import path:

```bash
export PYTHONPATH=/path/to/jopy       # Windows: set PYTHONPATH=D:\path\to\jopy
python -m jopy my_script.py
```

Editable install:

```bash
cd /path/to/jopy
pip install -e .
```

This installs the `jopy` console script (entry point `jopy.cli:main`), which is
equivalent to `python -m jopy`, and ships `jopy/runtime/*.java` as package data,
so the runtime sources stay available to `--compile` and to
`jopy.write_runtime()`.

The `packages` list in `pyproject.toml` is explicit on purpose -
`["jopy", "jopy.codegen", "jopy.runtime"]` - and `tests/test_packaging.py`
fails as soon as a package on disk is not declared, so an installed wheel can
always import.

## Documentation

| Document | Contents |
| --- | --- |
| [docs/01-getting-started.md](docs/01-getting-started.md) | Install, first translation, output layout, javac, running, troubleshooting |
| [docs/02-cli-reference.md](docs/02-cli-reference.md) | Every flag, default and example, exit codes, stream conventions |
| [docs/03-python-api.md](docs/03-python-api.md) | `translate`, `Translator`, `TranslationResult`, runtime and compile helpers, errors |
| [docs/04-progress-reporting.md](docs/04-progress-reporting.md) | Bar vs JSON renderers, event schema, stage weights, callbacks |
| [docs/05-language-mapping.md](docs/05-language-mapping.md) | The construct-by-construct Python to Java reference |
| [docs/06-architecture.md](docs/06-architecture.md) | Pipeline, module responsibilities, codegen mechanisms |
| [docs/07-manual-work.md](docs/07-manual-work.md) | Every diagnostic that asks a human to finish the job |
| [docs/08-limitations.md](docs/08-limitations.md) | What jopy will not translate, unsupported modules, performance |
| [docs/09-testing.md](docs/09-testing.md) | Verification scripts, end-to-end javac testing, adding fixtures |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, style, adding a mapping, review checklist |
| [CHANGELOG.md](CHANGELOG.md) | Release history |

## License

MIT. See [LICENSE](LICENSE).
