jopy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jopy-0.1.0/LICENSE +21 -0
- jopy-0.1.0/PKG-INFO +275 -0
- jopy-0.1.0/README.md +250 -0
- jopy-0.1.0/jopy/__init__.py +167 -0
- jopy-0.1.0/jopy/__main__.py +10 -0
- jopy-0.1.0/jopy/api.py +570 -0
- jopy-0.1.0/jopy/ast_nodes.py +542 -0
- jopy-0.1.0/jopy/cli.py +555 -0
- jopy-0.1.0/jopy/codegen/__init__.py +74 -0
- jopy-0.1.0/jopy/codegen/classes.py +846 -0
- jopy-0.1.0/jopy/codegen/collections.py +338 -0
- jopy-0.1.0/jopy/codegen/core.py +571 -0
- jopy-0.1.0/jopy/codegen/expressions.py +1447 -0
- jopy-0.1.0/jopy/codegen/functions.py +839 -0
- jopy-0.1.0/jopy/codegen/module.py +478 -0
- jopy-0.1.0/jopy/codegen/patterns.py +277 -0
- jopy-0.1.0/jopy/codegen/statements.py +1192 -0
- jopy-0.1.0/jopy/codegen/values.py +268 -0
- jopy-0.1.0/jopy/errors.py +270 -0
- jopy-0.1.0/jopy/features.py +201 -0
- jopy-0.1.0/jopy/inference.py +799 -0
- jopy-0.1.0/jopy/javatypes.py +451 -0
- jopy-0.1.0/jopy/jdk.py +902 -0
- jopy-0.1.0/jopy/lexer.py +768 -0
- jopy-0.1.0/jopy/mappings.py +708 -0
- jopy-0.1.0/jopy/parser.py +1566 -0
- jopy-0.1.0/jopy/progress.py +411 -0
- jopy-0.1.0/jopy/py.typed +0 -0
- jopy-0.1.0/jopy/runtime/Coroutine.java +535 -0
- jopy-0.1.0/jopy/runtime/JoPy.java +2800 -0
- jopy-0.1.0/jopy/runtime/Json.java +90 -0
- jopy-0.1.0/jopy/runtime/PyCallable.java +94 -0
- jopy-0.1.0/jopy/runtime/PyOperand.java +69 -0
- jopy-0.1.0/jopy/runtime/Reflection.java +64 -0
- jopy-0.1.0/jopy/runtime/Task.java +280 -0
- jopy-0.1.0/jopy/runtime/__init__.py +24 -0
- jopy-0.1.0/jopy/tokens.py +154 -0
- jopy-0.1.0/jopy.egg-info/PKG-INFO +275 -0
- jopy-0.1.0/jopy.egg-info/SOURCES.txt +59 -0
- jopy-0.1.0/jopy.egg-info/dependency_links.txt +1 -0
- jopy-0.1.0/jopy.egg-info/entry_points.txt +2 -0
- jopy-0.1.0/jopy.egg-info/requires.txt +4 -0
- jopy-0.1.0/jopy.egg-info/top_level.txt +1 -0
- jopy-0.1.0/pyproject.toml +51 -0
- jopy-0.1.0/setup.cfg +4 -0
- jopy-0.1.0/tests/test_api.py +428 -0
- jopy-0.1.0/tests/test_cli.py +308 -0
- jopy-0.1.0/tests/test_codegen.py +620 -0
- jopy-0.1.0/tests/test_end_to_end.py +242 -0
- jopy-0.1.0/tests/test_errors.py +282 -0
- jopy-0.1.0/tests/test_features.py +194 -0
- jopy-0.1.0/tests/test_jdk.py +1000 -0
- jopy-0.1.0/tests/test_lexer.py +360 -0
- jopy-0.1.0/tests/test_multiple_inheritance.py +330 -0
- jopy-0.1.0/tests/test_operator_regressions.py +85 -0
- jopy-0.1.0/tests/test_packaging.py +125 -0
- jopy-0.1.0/tests/test_parser.py +599 -0
- jopy-0.1.0/tests/test_progress.py +394 -0
- jopy-0.1.0/tests/test_runtime_contract.py +764 -0
- jopy-0.1.0/tests/test_runtime_self_test.py +47 -0
- jopy-0.1.0/tests/test_version_matrix.py +532 -0
jopy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jopy contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jopy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jopy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python to Java transpiler: translate Python source into readable Java 9-21 source code.
|
|
5
|
+
Author: jopy contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: python,java,transpiler,compiler,code-generation,source-to-source
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Java
|
|
16
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# jopy
|
|
27
|
+
|
|
28
|
+
**jopy** is a Python-to-Java transpiler written in pure Python. It reads Python
|
|
29
|
+
source with a hand written lexer and recursive descent parser, infers Java
|
|
30
|
+
types, and emits readable Java source that targets every release from **Java 9
|
|
31
|
+
to Java 21**. It never executes the Python it reads: it only parses, analyses
|
|
32
|
+
and re-emits it.
|
|
33
|
+
|
|
34
|
+
Generated code can call a small Java runtime (`jopy.runtime.JoPy`) that mirrors
|
|
35
|
+
Python semantics where Java differs: floor division, modulo sign, negative
|
|
36
|
+
indexing, truthiness, slices, integer overflow checks.
|
|
37
|
+
|
|
38
|
+
- Version: 0.1.0
|
|
39
|
+
- Language: Python 3.9+ (the translator), Java 9-21 (the output)
|
|
40
|
+
- Dependencies: standard library only
|
|
41
|
+
- Entry points: `import jopy` and `python -m jopy <file.py>`
|
|
42
|
+
|
|
43
|
+
## Two usage modes
|
|
44
|
+
|
|
45
|
+
**Library.** Import the package and translate text, files or whole trees:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import jopy
|
|
49
|
+
|
|
50
|
+
result = jopy.translate("def add(a: int, b: int) -> int:\n return a + b\n",
|
|
51
|
+
filename="math_utils.py")
|
|
52
|
+
print(result.java)
|
|
53
|
+
print(result.stats["java_lines"], "Java lines")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Command line.** Translate from a shell, optionally compiling the result with
|
|
57
|
+
`javac`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python -m jopy math_utils.py --java-version 17 --compile -o build/java
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 1. work from a checkout of this repository
|
|
67
|
+
cd /path/to/jopy
|
|
68
|
+
|
|
69
|
+
# 2. translate one file and print the Java on stdout
|
|
70
|
+
python -m jopy examples/greeting.py
|
|
71
|
+
|
|
72
|
+
# 3. write files, copy the runtime and verify with javac
|
|
73
|
+
python -m jopy examples/greeting.py -o build --java-version 17 --compile
|
|
74
|
+
|
|
75
|
+
# 4. run the compiled program
|
|
76
|
+
java -cp build/classes Greeting
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Step 3 and 4 are verified for `examples/greeting.py`: the translation exits 0,
|
|
80
|
+
javac accepts the output and the program prints the same lines as
|
|
81
|
+
`python examples/greeting.py`. The `examples/` directory holds runnable
|
|
82
|
+
programs that show one feature each; they are checked by hand rather than by
|
|
83
|
+
the suite (the automated fixtures live in `tests/fixtures/`), and
|
|
84
|
+
`examples/shapes_and_matching.py` currently fails `--compile` because of the
|
|
85
|
+
`match` guard defect in [docs/08-limitations.md](docs/08-limitations.md#known-defects-in-010).
|
|
86
|
+
See [examples/README.md](examples/README.md).
|
|
87
|
+
|
|
88
|
+
Machine readable progress is written to stderr as JSON lines whenever stderr is
|
|
89
|
+
not a terminal, so a pipeline stays clean:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python -m jopy src/ -o build --progress json 2> progress.jsonl
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Before and after
|
|
96
|
+
|
|
97
|
+
Input, `greet.py`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
def greet(name: str) -> str:
|
|
101
|
+
return "Hello, " + name + "!"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main() -> None:
|
|
105
|
+
for i in range(3):
|
|
106
|
+
print(greet("world"), i)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Output (`python -m jopy greet.py --java-version 17`):
|
|
114
|
+
|
|
115
|
+
```java
|
|
116
|
+
import jopy.runtime.JoPy;
|
|
117
|
+
|
|
118
|
+
public class Greet {
|
|
119
|
+
public static void main(String[] args) {
|
|
120
|
+
main();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public static String greet(String name) {
|
|
124
|
+
return (("Hello, " + name) + "!");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public static void main() {
|
|
128
|
+
for (int i = 0; i < 3; i++) {
|
|
129
|
+
System.out.println(JoPy.joinStrings(" ", greet("world"), i));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`print(a, b)` becomes one call because Python prints space separated values,
|
|
136
|
+
and `range(3)` becomes a counting loop because jopy can prove the bounds are
|
|
137
|
+
integral. Compiling and running that Java prints exactly what the Python
|
|
138
|
+
prints.
|
|
139
|
+
|
|
140
|
+
## Feature matrix
|
|
141
|
+
|
|
142
|
+
| Area | Support | Notes |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| Lexer, parser, AST | Full Python 3.8-3.12 grammar subset | INDENT/DEDENT tokenising, f-strings, match statements, walrus, PEP 604 unions |
|
|
145
|
+
| Type inference | Heuristic, never fatal | Annotations plus a usage driven narrowing pass; what stays unprovable becomes `Object`, dispatches through `JoPy` helpers and is reported with a manual-fix note |
|
|
146
|
+
| Functions | Parameters, defaults, `*args`, `**kwargs`, decorators | Defaults expand into overloads, unknown decorators become comments plus a manual note |
|
|
147
|
+
| Classes | Inheritance, static/class methods, properties, dunder methods | Single inheritance; extra bases become manual work |
|
|
148
|
+
| Modules | 30+ stdlib modules mapped | Unknown modules produce a manual note and a commented import |
|
|
149
|
+
| Containers | list / dict / set / tuple | Tuples are `List`, dicts are `Map`, sets are `Set` |
|
|
150
|
+
| Exceptions | try / except / else / finally, multi-catch | Checked exceptions are caught as `Exception` and rethrown with `JoPy.sneakyThrow` |
|
|
151
|
+
| Comprehensions | list / set / dict / generator expressions | Emitted as `Stream` pipelines |
|
|
152
|
+
| Generators | `yield` / `yield from` inside a function | Lazy `Coroutine` on one daemon thread; `next`, `send`, `throw` and `close` work, unbounded generators are fine |
|
|
153
|
+
| Nested functions | `def` inside a `def` | `PyCallable` closures that capture the enclosing locals |
|
|
154
|
+
| 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 |
|
|
155
|
+
| `async` / `await` | `async def`, `await`, `asyncio.run` | `Task` on a daemon thread; `asyncio.gather`/`sleep` map to `JoPy` helpers |
|
|
156
|
+
| Context managers | `with open(...) as f:` | Becomes try-with-resources when the manager is `AutoCloseable` |
|
|
157
|
+
| Match statements | Sequence, mapping, class, value, singleton, or, as, wildcard | Emitted as an if/else chain on every target release |
|
|
158
|
+
| Comments and docstrings | Preserved by default | `--no-comments` / `--no-javadoc` drop them |
|
|
159
|
+
| Progress reporting | Terminal bar or JSON lines | Same events, two renderers, plus a `progress_callback` hook |
|
|
160
|
+
| Manual-fix reporting | Structured diagnostics | `--report report.json` writes every diagnostic as JSON |
|
|
161
|
+
| 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 |
|
|
162
|
+
|
|
163
|
+
Constructs jopy cannot express faithfully are **not** silently dropped: they are
|
|
164
|
+
reported as `manual-fix` diagnostics, embedded as `// MANUAL: ...` comments in
|
|
165
|
+
the generated Java and listed by `--explain`. See
|
|
166
|
+
[docs/07-manual-work.md](docs/07-manual-work.md).
|
|
167
|
+
|
|
168
|
+
## Architecture
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
Python source (.py)
|
|
172
|
+
|
|
|
173
|
+
v
|
|
174
|
+
+------------------+ tokens + comments + INDENT/DEDENT
|
|
175
|
+
| lexer.py |-------------------------------------+
|
|
176
|
+
+------------------+ |
|
|
177
|
+
| |
|
|
178
|
+
v v
|
|
179
|
+
+------------------+ syntax tree (ast_nodes) +-----------+
|
|
180
|
+
| parser.py |-----------------------------> | tokens.py |
|
|
181
|
+
+------------------+ +-----------+
|
|
182
|
+
|
|
|
183
|
+
v
|
|
184
|
+
+------------------+ Java types (javatypes) +-------------+
|
|
185
|
+
| inference.py |-----------------------------> | mappings.py |
|
|
186
|
+
+------------------+ builtins, modules, ... +-------------+
|
|
187
|
+
|
|
|
188
|
+
v
|
|
189
|
+
+------------------+ Java source + diagnostics + line map
|
|
190
|
+
| codegen/ |-------------------------------------+
|
|
191
|
+
+------------------+ |
|
|
192
|
+
| v
|
|
193
|
+
v +-------------------------+
|
|
194
|
+
+------------------+ TranslationResult | runtime/JoPy.java |
|
|
195
|
+
| api.py |------------------------> | runtime/PyCallable.java |
|
|
196
|
+
+------------------+ progress.py events | runtime/PyOperand.java |
|
|
197
|
+
| | runtime/Coroutine.java |
|
|
198
|
+
| | runtime/Task.java |
|
|
199
|
+
| +-------------------------+
|
|
200
|
+
v
|
|
201
|
+
+------------------+ exit codes 0/1/2/3
|
|
202
|
+
| cli.py |
|
|
203
|
+
+------------------+
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
More detail, including how comments and source lines survive the round trip,
|
|
207
|
+
lives in [docs/06-architecture.md](docs/06-architecture.md).
|
|
208
|
+
|
|
209
|
+
## Requirements
|
|
210
|
+
|
|
211
|
+
- **Python 3.9 or newer** to run jopy itself. Only the standard library is used.
|
|
212
|
+
- **A JDK is optional.** It is needed only for `--compile`, `javac_release_supported()`
|
|
213
|
+
and the `--javac-output` workflow. The JDK must be at least as new as the
|
|
214
|
+
`--java-version` you target (`javac --release N` cannot target a newer release).
|
|
215
|
+
When none is installed, `jopy --install-jdk` fetches one into the managed root
|
|
216
|
+
(`~/.jopy/jdks`, or `$JOPY_HOME/jdks`) and prints its path; a manually downloaded
|
|
217
|
+
archive installs offline with `--jdk-archive FILE`, and `--list-jdks` shows what
|
|
218
|
+
is already there. See [docs/02-cli-reference.md](docs/02-cli-reference.md#jdk-management-options).
|
|
219
|
+
- Verified during the writing of this documentation with Python 3.11.9 and
|
|
220
|
+
`javac 17.0.19`.
|
|
221
|
+
|
|
222
|
+
## Installation
|
|
223
|
+
|
|
224
|
+
The checkout is directly runnable, because the package lives in the `jopy/`
|
|
225
|
+
directory at the repository root:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cd /path/to/jopy
|
|
229
|
+
python -m jopy --version # jopy 0.1.0
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
From another working directory, put the checkout on the import path:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
export PYTHONPATH=/path/to/jopy # Windows: set PYTHONPATH=D:\path\to\jopy
|
|
236
|
+
python -m jopy my_script.py
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Editable install:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
cd /path/to/jopy
|
|
243
|
+
pip install -e .
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This installs the `jopy` console script (entry point `jopy.cli:main`), which is
|
|
247
|
+
equivalent to `python -m jopy`, and ships `jopy/runtime/*.java` as package data,
|
|
248
|
+
so the runtime sources stay available to `--compile` and to
|
|
249
|
+
`jopy.write_runtime()`.
|
|
250
|
+
|
|
251
|
+
**Known defect:** the `packages` list in `pyproject.toml` is
|
|
252
|
+
`["jopy", "jopy.runtime"]` and therefore omits `jopy.codegen`, so an installed
|
|
253
|
+
wheel cannot import at all (`ModuleNotFoundError: No module named
|
|
254
|
+
'jopy.codegen'`). Run from the checkout, or add `jopy.codegen` to that list;
|
|
255
|
+
see [docs/08-limitations.md](docs/08-limitations.md#known-defects-in-010).
|
|
256
|
+
|
|
257
|
+
## Documentation
|
|
258
|
+
|
|
259
|
+
| Document | Contents |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| [docs/01-getting-started.md](docs/01-getting-started.md) | Install, first translation, output layout, javac, running, troubleshooting |
|
|
262
|
+
| [docs/02-cli-reference.md](docs/02-cli-reference.md) | Every flag, default and example, exit codes, stream conventions |
|
|
263
|
+
| [docs/03-python-api.md](docs/03-python-api.md) | `translate`, `Translator`, `TranslationResult`, runtime and compile helpers, errors |
|
|
264
|
+
| [docs/04-progress-reporting.md](docs/04-progress-reporting.md) | Bar vs JSON renderers, event schema, stage weights, callbacks |
|
|
265
|
+
| [docs/05-language-mapping.md](docs/05-language-mapping.md) | The construct-by-construct Python to Java reference |
|
|
266
|
+
| [docs/06-architecture.md](docs/06-architecture.md) | Pipeline, module responsibilities, codegen mechanisms |
|
|
267
|
+
| [docs/07-manual-work.md](docs/07-manual-work.md) | Every diagnostic that asks a human to finish the job |
|
|
268
|
+
| [docs/08-limitations.md](docs/08-limitations.md) | What jopy will not translate, unsupported modules, performance |
|
|
269
|
+
| [docs/09-testing.md](docs/09-testing.md) | Verification scripts, end-to-end javac testing, adding fixtures |
|
|
270
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, style, adding a mapping, review checklist |
|
|
271
|
+
| [CHANGELOG.md](CHANGELOG.md) | Release history |
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT. See [LICENSE](LICENSE).
|
jopy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# jopy
|
|
2
|
+
|
|
3
|
+
**jopy** is a Python-to-Java transpiler written in pure Python. It reads Python
|
|
4
|
+
source with a hand written lexer and recursive descent parser, infers Java
|
|
5
|
+
types, and emits readable Java source that targets every release from **Java 9
|
|
6
|
+
to Java 21**. It never executes the Python it reads: it only parses, analyses
|
|
7
|
+
and re-emits it.
|
|
8
|
+
|
|
9
|
+
Generated code can call a small Java runtime (`jopy.runtime.JoPy`) that mirrors
|
|
10
|
+
Python semantics where Java differs: floor division, modulo sign, negative
|
|
11
|
+
indexing, truthiness, slices, integer overflow checks.
|
|
12
|
+
|
|
13
|
+
- Version: 0.1.0
|
|
14
|
+
- Language: Python 3.9+ (the translator), Java 9-21 (the output)
|
|
15
|
+
- Dependencies: standard library only
|
|
16
|
+
- Entry points: `import jopy` and `python -m jopy <file.py>`
|
|
17
|
+
|
|
18
|
+
## Two usage modes
|
|
19
|
+
|
|
20
|
+
**Library.** Import the package and translate text, files or whole trees:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import jopy
|
|
24
|
+
|
|
25
|
+
result = jopy.translate("def add(a: int, b: int) -> int:\n return a + b\n",
|
|
26
|
+
filename="math_utils.py")
|
|
27
|
+
print(result.java)
|
|
28
|
+
print(result.stats["java_lines"], "Java lines")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Command line.** Translate from a shell, optionally compiling the result with
|
|
32
|
+
`javac`:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m jopy math_utils.py --java-version 17 --compile -o build/java
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. work from a checkout of this repository
|
|
42
|
+
cd /path/to/jopy
|
|
43
|
+
|
|
44
|
+
# 2. translate one file and print the Java on stdout
|
|
45
|
+
python -m jopy examples/greeting.py
|
|
46
|
+
|
|
47
|
+
# 3. write files, copy the runtime and verify with javac
|
|
48
|
+
python -m jopy examples/greeting.py -o build --java-version 17 --compile
|
|
49
|
+
|
|
50
|
+
# 4. run the compiled program
|
|
51
|
+
java -cp build/classes Greeting
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Step 3 and 4 are verified for `examples/greeting.py`: the translation exits 0,
|
|
55
|
+
javac accepts the output and the program prints the same lines as
|
|
56
|
+
`python examples/greeting.py`. The `examples/` directory holds runnable
|
|
57
|
+
programs that show one feature each; they are checked by hand rather than by
|
|
58
|
+
the suite (the automated fixtures live in `tests/fixtures/`), and
|
|
59
|
+
`examples/shapes_and_matching.py` currently fails `--compile` because of the
|
|
60
|
+
`match` guard defect in [docs/08-limitations.md](docs/08-limitations.md#known-defects-in-010).
|
|
61
|
+
See [examples/README.md](examples/README.md).
|
|
62
|
+
|
|
63
|
+
Machine readable progress is written to stderr as JSON lines whenever stderr is
|
|
64
|
+
not a terminal, so a pipeline stays clean:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m jopy src/ -o build --progress json 2> progress.jsonl
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Before and after
|
|
71
|
+
|
|
72
|
+
Input, `greet.py`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
def greet(name: str) -> str:
|
|
76
|
+
return "Hello, " + name + "!"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> None:
|
|
80
|
+
for i in range(3):
|
|
81
|
+
print(greet("world"), i)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Output (`python -m jopy greet.py --java-version 17`):
|
|
89
|
+
|
|
90
|
+
```java
|
|
91
|
+
import jopy.runtime.JoPy;
|
|
92
|
+
|
|
93
|
+
public class Greet {
|
|
94
|
+
public static void main(String[] args) {
|
|
95
|
+
main();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public static String greet(String name) {
|
|
99
|
+
return (("Hello, " + name) + "!");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static void main() {
|
|
103
|
+
for (int i = 0; i < 3; i++) {
|
|
104
|
+
System.out.println(JoPy.joinStrings(" ", greet("world"), i));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`print(a, b)` becomes one call because Python prints space separated values,
|
|
111
|
+
and `range(3)` becomes a counting loop because jopy can prove the bounds are
|
|
112
|
+
integral. Compiling and running that Java prints exactly what the Python
|
|
113
|
+
prints.
|
|
114
|
+
|
|
115
|
+
## Feature matrix
|
|
116
|
+
|
|
117
|
+
| Area | Support | Notes |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| Lexer, parser, AST | Full Python 3.8-3.12 grammar subset | INDENT/DEDENT tokenising, f-strings, match statements, walrus, PEP 604 unions |
|
|
120
|
+
| Type inference | Heuristic, never fatal | Annotations plus a usage driven narrowing pass; what stays unprovable becomes `Object`, dispatches through `JoPy` helpers and is reported with a manual-fix note |
|
|
121
|
+
| Functions | Parameters, defaults, `*args`, `**kwargs`, decorators | Defaults expand into overloads, unknown decorators become comments plus a manual note |
|
|
122
|
+
| Classes | Inheritance, static/class methods, properties, dunder methods | Single inheritance; extra bases become manual work |
|
|
123
|
+
| Modules | 30+ stdlib modules mapped | Unknown modules produce a manual note and a commented import |
|
|
124
|
+
| Containers | list / dict / set / tuple | Tuples are `List`, dicts are `Map`, sets are `Set` |
|
|
125
|
+
| Exceptions | try / except / else / finally, multi-catch | Checked exceptions are caught as `Exception` and rethrown with `JoPy.sneakyThrow` |
|
|
126
|
+
| Comprehensions | list / set / dict / generator expressions | Emitted as `Stream` pipelines |
|
|
127
|
+
| Generators | `yield` / `yield from` inside a function | Lazy `Coroutine` on one daemon thread; `next`, `send`, `throw` and `close` work, unbounded generators are fine |
|
|
128
|
+
| Nested functions | `def` inside a `def` | `PyCallable` closures that capture the enclosing locals |
|
|
129
|
+
| 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 |
|
|
130
|
+
| `async` / `await` | `async def`, `await`, `asyncio.run` | `Task` on a daemon thread; `asyncio.gather`/`sleep` map to `JoPy` helpers |
|
|
131
|
+
| Context managers | `with open(...) as f:` | Becomes try-with-resources when the manager is `AutoCloseable` |
|
|
132
|
+
| Match statements | Sequence, mapping, class, value, singleton, or, as, wildcard | Emitted as an if/else chain on every target release |
|
|
133
|
+
| Comments and docstrings | Preserved by default | `--no-comments` / `--no-javadoc` drop them |
|
|
134
|
+
| Progress reporting | Terminal bar or JSON lines | Same events, two renderers, plus a `progress_callback` hook |
|
|
135
|
+
| Manual-fix reporting | Structured diagnostics | `--report report.json` writes every diagnostic as JSON |
|
|
136
|
+
| 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 |
|
|
137
|
+
|
|
138
|
+
Constructs jopy cannot express faithfully are **not** silently dropped: they are
|
|
139
|
+
reported as `manual-fix` diagnostics, embedded as `// MANUAL: ...` comments in
|
|
140
|
+
the generated Java and listed by `--explain`. See
|
|
141
|
+
[docs/07-manual-work.md](docs/07-manual-work.md).
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
Python source (.py)
|
|
147
|
+
|
|
|
148
|
+
v
|
|
149
|
+
+------------------+ tokens + comments + INDENT/DEDENT
|
|
150
|
+
| lexer.py |-------------------------------------+
|
|
151
|
+
+------------------+ |
|
|
152
|
+
| |
|
|
153
|
+
v v
|
|
154
|
+
+------------------+ syntax tree (ast_nodes) +-----------+
|
|
155
|
+
| parser.py |-----------------------------> | tokens.py |
|
|
156
|
+
+------------------+ +-----------+
|
|
157
|
+
|
|
|
158
|
+
v
|
|
159
|
+
+------------------+ Java types (javatypes) +-------------+
|
|
160
|
+
| inference.py |-----------------------------> | mappings.py |
|
|
161
|
+
+------------------+ builtins, modules, ... +-------------+
|
|
162
|
+
|
|
|
163
|
+
v
|
|
164
|
+
+------------------+ Java source + diagnostics + line map
|
|
165
|
+
| codegen/ |-------------------------------------+
|
|
166
|
+
+------------------+ |
|
|
167
|
+
| v
|
|
168
|
+
v +-------------------------+
|
|
169
|
+
+------------------+ TranslationResult | runtime/JoPy.java |
|
|
170
|
+
| api.py |------------------------> | runtime/PyCallable.java |
|
|
171
|
+
+------------------+ progress.py events | runtime/PyOperand.java |
|
|
172
|
+
| | runtime/Coroutine.java |
|
|
173
|
+
| | runtime/Task.java |
|
|
174
|
+
| +-------------------------+
|
|
175
|
+
v
|
|
176
|
+
+------------------+ exit codes 0/1/2/3
|
|
177
|
+
| cli.py |
|
|
178
|
+
+------------------+
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
More detail, including how comments and source lines survive the round trip,
|
|
182
|
+
lives in [docs/06-architecture.md](docs/06-architecture.md).
|
|
183
|
+
|
|
184
|
+
## Requirements
|
|
185
|
+
|
|
186
|
+
- **Python 3.9 or newer** to run jopy itself. Only the standard library is used.
|
|
187
|
+
- **A JDK is optional.** It is needed only for `--compile`, `javac_release_supported()`
|
|
188
|
+
and the `--javac-output` workflow. The JDK must be at least as new as the
|
|
189
|
+
`--java-version` you target (`javac --release N` cannot target a newer release).
|
|
190
|
+
When none is installed, `jopy --install-jdk` fetches one into the managed root
|
|
191
|
+
(`~/.jopy/jdks`, or `$JOPY_HOME/jdks`) and prints its path; a manually downloaded
|
|
192
|
+
archive installs offline with `--jdk-archive FILE`, and `--list-jdks` shows what
|
|
193
|
+
is already there. See [docs/02-cli-reference.md](docs/02-cli-reference.md#jdk-management-options).
|
|
194
|
+
- Verified during the writing of this documentation with Python 3.11.9 and
|
|
195
|
+
`javac 17.0.19`.
|
|
196
|
+
|
|
197
|
+
## Installation
|
|
198
|
+
|
|
199
|
+
The checkout is directly runnable, because the package lives in the `jopy/`
|
|
200
|
+
directory at the repository root:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
cd /path/to/jopy
|
|
204
|
+
python -m jopy --version # jopy 0.1.0
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
From another working directory, put the checkout on the import path:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
export PYTHONPATH=/path/to/jopy # Windows: set PYTHONPATH=D:\path\to\jopy
|
|
211
|
+
python -m jopy my_script.py
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Editable install:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
cd /path/to/jopy
|
|
218
|
+
pip install -e .
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This installs the `jopy` console script (entry point `jopy.cli:main`), which is
|
|
222
|
+
equivalent to `python -m jopy`, and ships `jopy/runtime/*.java` as package data,
|
|
223
|
+
so the runtime sources stay available to `--compile` and to
|
|
224
|
+
`jopy.write_runtime()`.
|
|
225
|
+
|
|
226
|
+
**Known defect:** the `packages` list in `pyproject.toml` is
|
|
227
|
+
`["jopy", "jopy.runtime"]` and therefore omits `jopy.codegen`, so an installed
|
|
228
|
+
wheel cannot import at all (`ModuleNotFoundError: No module named
|
|
229
|
+
'jopy.codegen'`). Run from the checkout, or add `jopy.codegen` to that list;
|
|
230
|
+
see [docs/08-limitations.md](docs/08-limitations.md#known-defects-in-010).
|
|
231
|
+
|
|
232
|
+
## Documentation
|
|
233
|
+
|
|
234
|
+
| Document | Contents |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| [docs/01-getting-started.md](docs/01-getting-started.md) | Install, first translation, output layout, javac, running, troubleshooting |
|
|
237
|
+
| [docs/02-cli-reference.md](docs/02-cli-reference.md) | Every flag, default and example, exit codes, stream conventions |
|
|
238
|
+
| [docs/03-python-api.md](docs/03-python-api.md) | `translate`, `Translator`, `TranslationResult`, runtime and compile helpers, errors |
|
|
239
|
+
| [docs/04-progress-reporting.md](docs/04-progress-reporting.md) | Bar vs JSON renderers, event schema, stage weights, callbacks |
|
|
240
|
+
| [docs/05-language-mapping.md](docs/05-language-mapping.md) | The construct-by-construct Python to Java reference |
|
|
241
|
+
| [docs/06-architecture.md](docs/06-architecture.md) | Pipeline, module responsibilities, codegen mechanisms |
|
|
242
|
+
| [docs/07-manual-work.md](docs/07-manual-work.md) | Every diagnostic that asks a human to finish the job |
|
|
243
|
+
| [docs/08-limitations.md](docs/08-limitations.md) | What jopy will not translate, unsupported modules, performance |
|
|
244
|
+
| [docs/09-testing.md](docs/09-testing.md) | Verification scripts, end-to-end javac testing, adding fixtures |
|
|
245
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, style, adding a mapping, review checklist |
|
|
246
|
+
| [CHANGELOG.md](CHANGELOG.md) | Release history |
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT. See [LICENSE](LICENSE).
|