mirror of
https://github.com/quinton-ashley/java2js
synced 2024-12-29 10:11:54 +01:00
1.1.10
This commit is contained in:
parent
8de0c299bb
commit
7e855616c8
60
README.md
60
README.md
@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
`java2js` can translate simple Java programs to JavaScript and runs them using a JavaScript based JDK.
|
`java2js` can translate simple Java programs to JavaScript and runs them using a JavaScript based JDK.
|
||||||
|
|
||||||
I made significant improvements to the [java-to-javascript](https://github.com/wyattades/java-to-javascript) transpiler by @wyattades. I also did a modern JS implementation of the fundamental classes in the JDK (Java Development Kit), inspired by [java2javascript](https://github.com/java2script/java2script) by @BobHanson, @zhourenjian, @abego, and others.
|
Try out the `demo.html` and `ide.js`, a barebones implementation.
|
||||||
|
|
||||||
I created this project so intro level computer science students could write Java code but still use my [QuintOS](https://github.com/quinton-ashley/quintos) retro game engine library, which is web based.
|
## Java classes included in the java2js JDK
|
||||||
|
|
||||||
## Java Classes Included in the JS JDK
|
|
||||||
|
|
||||||
| java.io | | |
|
| java.io | | |
|
||||||
| ------- | ----------- | ----------- |
|
| ------- | ----------- | ----------- |
|
||||||
@ -37,32 +35,10 @@ I created this project so intro level computer science students could write Java
|
|||||||
| LinkedList | Random | Scanner |
|
| LinkedList | Random | Scanner |
|
||||||
| Set | Stack | |
|
| Set | Stack | |
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### await jdk.init(root)
|
|
||||||
|
|
||||||
- root (optional) path to the parent folder of the JS `jdk` folder, by default it is `./` you could also link to this package online 'https://unpkg.com/java2js'
|
|
||||||
|
|
||||||
This function imports much of the standard Java lang classes into the global scope. You must use it before translating or running files.
|
|
||||||
|
|
||||||
### await jdk.translate(javaFile)
|
|
||||||
|
|
||||||
- javaFile is a string with contents of a Java file
|
|
||||||
|
|
||||||
returns the Java file translated into JavaScript
|
|
||||||
|
|
||||||
### jdk.load(translatedJSFile)
|
|
||||||
|
|
||||||
- translatedJSFile is the translated JS class to run
|
|
||||||
|
|
||||||
Loads the JS class file but doesn't run the main method.
|
|
||||||
|
|
||||||
### jdk.run(jvmArgs)
|
|
||||||
|
|
||||||
Runs the main method with optional JVM arguments.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
The java2js transpiler supports:
|
||||||
|
|
||||||
- imports
|
- imports
|
||||||
- static classes
|
- static classes
|
||||||
- static methods
|
- static methods
|
||||||
@ -72,6 +48,26 @@ Runs the main method with optional JVM arguments.
|
|||||||
- lambda arrow functions
|
- lambda arrow functions
|
||||||
- triple quotes
|
- triple quotes
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### await jdk.init(root)
|
||||||
|
|
||||||
|
This function imports the standard java.lang classes into the global scope. You must use it before translating or running files.
|
||||||
|
|
||||||
|
- root (optional) path to the parent folder of the JS `jdk` folder, by default it is `.` (the current directory) you could also link to this package online 'https://unpkg.com/java2js'
|
||||||
|
|
||||||
|
### await jdk.transpile(javaFile)
|
||||||
|
|
||||||
|
Translates a Java class and loads it.
|
||||||
|
|
||||||
|
- javaFile is a String with a Java class in it
|
||||||
|
|
||||||
|
returns a String with the JavaScript transpilation of the Java class
|
||||||
|
|
||||||
|
### jdk.run(jvmArgs)
|
||||||
|
|
||||||
|
Runs the main method with optional JVM arguments.
|
||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
|
|
||||||
- not very good error reporting
|
- not very good error reporting
|
||||||
@ -88,6 +84,14 @@ Runs the main method with optional JVM arguments.
|
|||||||
|
|
||||||
- no method can be named `.length()` because it will be replaced with `.length` for getting the length of Strings in JS
|
- no method can be named `.length()` because it will be replaced with `.length` for getting the length of Strings in JS
|
||||||
|
|
||||||
|
## Why did you make this?
|
||||||
|
|
||||||
|
I created this package so intro level computer science students could write Java code but still use my [QuintOS](https://github.com/quinton-ashley/quintos) retro game engine library, which is web based.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This project builds upon the [java-to-javascript](https://github.com/wyattades/java-to-javascript) transpiler by @wyattades. I also did a modern JS implementation of the fundamental classes in the JDK (Java Development Kit), which I got the inspiration to do from [java2javascript](https://github.com/java2script/java2script) by @BobHanson, @zhourenjian, @abego, and others.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
I've only done a partial implementation of the Java 17 JDK in JavaScript, so if you're interested in adding more please go for it and submit a pull request!
|
I've only done a partial implementation of the Java 17 JDK in JavaScript, so if you're interested in adding more please go for it and submit a pull request!
|
||||||
|
27
demo.html
27
demo.html
@ -17,20 +17,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#javaFile {
|
#javaFile {
|
||||||
width: 47vw;
|
width: 48vw;
|
||||||
height: 90vh;
|
height: 97vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#javaConsole {
|
#javaConsole {
|
||||||
width: 47vw;
|
width: 48vw;
|
||||||
height: 90vh;
|
height: 97vh;
|
||||||
|
float: right;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<textarea id="javaFile" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
|
<textarea
|
||||||
<textarea id="javaConsole" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
|
id="javaFile"
|
||||||
|
spellcheck="false"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
placeholder="Create a Java class with a main method here..."
|
||||||
|
></textarea>
|
||||||
|
<textarea
|
||||||
|
id="javaConsole"
|
||||||
|
spellcheck="false"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
placeholder="Java console, click here to run!"
|
||||||
|
></textarea>
|
||||||
|
|
||||||
<script type="text/javascript" src="jdk.js"></script>
|
<script type="text/javascript" src="jdk.js"></script>
|
||||||
<script type="text/javascript" src="ide.js"></script>
|
<script type="text/javascript" src="ide.js"></script>
|
||||||
|
3
ide.js
3
ide.js
@ -5,9 +5,8 @@
|
|||||||
let file0 = document.getElementById('javaFile');
|
let file0 = document.getElementById('javaFile');
|
||||||
file0.onchange = async () => {
|
file0.onchange = async () => {
|
||||||
jdk.log.value = '';
|
jdk.log.value = '';
|
||||||
let translation = await jdk.translate(file0.value);
|
let translation = await jdk.transpile(file0.value);
|
||||||
console.log(translation);
|
console.log(translation);
|
||||||
jdk.load(translation);
|
|
||||||
jdk.run();
|
jdk.run();
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
79
jdk.js
79
jdk.js
@ -48,7 +48,7 @@
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
async init(root) {
|
async init(root) {
|
||||||
this.root = root || './';
|
this.root = root || '.';
|
||||||
this.java = {};
|
this.java = {};
|
||||||
let pkgs = ['com', 'io', 'lang', 'org', 'security', 'time', 'util'];
|
let pkgs = ['com', 'io', 'lang', 'org', 'security', 'time', 'util'];
|
||||||
for (let pkg of pkgs) {
|
for (let pkg of pkgs) {
|
||||||
@ -94,8 +94,10 @@
|
|||||||
System.in.reset();
|
System.in.reset();
|
||||||
System.out.reset();
|
System.out.reset();
|
||||||
|
|
||||||
// stub main for now
|
// stub main
|
||||||
this.main = () => {};
|
this.main = () => {
|
||||||
|
console.error('No main method found in loaded classes!');
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
run(jvmArgs) {
|
run(jvmArgs) {
|
||||||
@ -152,7 +154,7 @@
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async translate(file) {
|
async transpile(file) {
|
||||||
if (!this.java) {
|
if (!this.java) {
|
||||||
console.error(
|
console.error(
|
||||||
'java2js error: JDK not initialized! You must call `await jdk.init()` before using `jdk.load()`'
|
'java2js error: JDK not initialized! You must call `await jdk.init()` before using `jdk.load()`'
|
||||||
@ -216,12 +218,11 @@
|
|||||||
return 'new Runnable("' + in0 + '")';
|
return 'new Runnable("' + in0 + '")';
|
||||||
});
|
});
|
||||||
|
|
||||||
// convert string .length() method
|
|
||||||
file = file.replace(/\.length\(\)/gm, '.length');
|
|
||||||
|
|
||||||
// cast to int, truncates the number (just removes decimal value)
|
// cast to int, truncates the number (just removes decimal value)
|
||||||
file = file.replace(/\(int\)\s*/gm, 'Math.floor');
|
file = file.replace(/\(int\)\s*/gm, 'Math.floor');
|
||||||
|
|
||||||
|
let packageName = (file.match(/package\s+([^;]+)/gm) || [])[1] || 'default';
|
||||||
|
|
||||||
let trans = await java_to_javascript(file);
|
let trans = await java_to_javascript(file);
|
||||||
|
|
||||||
// log(trans);
|
// log(trans);
|
||||||
@ -233,7 +234,7 @@
|
|||||||
|
|
||||||
trans = trans.replace(/(\([^\(\)]*\) =>)/gm, 'async $1');
|
trans = trans.replace(/(\([^\(\)]*\) =>)/gm, 'async $1');
|
||||||
|
|
||||||
let prefix = `((jdk.imports['games_java.${className}'] = {}).load = async () => {\n\n`;
|
let prefix = `(jdk.imports['${packageName}.${className}'] = {}).load = async () => {\n\n`;
|
||||||
|
|
||||||
// handle Java class imports
|
// handle Java class imports
|
||||||
for (let i = 0; i < imports.length; i++) {
|
for (let i = 0; i < imports.length; i++) {
|
||||||
@ -252,17 +253,22 @@
|
|||||||
|
|
||||||
let suffix = '\n';
|
let suffix = '\n';
|
||||||
suffix += `window.${className} = ${className};\n`;
|
suffix += `window.${className} = ${className};\n`;
|
||||||
suffix += '})();';
|
suffix += '};';
|
||||||
|
|
||||||
trans = prefix + trans + suffix;
|
trans = prefix + trans + suffix;
|
||||||
|
|
||||||
return trans;
|
|
||||||
}
|
|
||||||
|
|
||||||
load(trans) {
|
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.innerHTML = trans;
|
script.innerHTML = trans;
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jdk.imports[`${packageName}.${className}`].load();
|
||||||
|
} catch (ror) {
|
||||||
|
console.error('Failed to load class!\n' + trans);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trans;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.jdk = new JDK();
|
window.jdk = new JDK();
|
||||||
@ -19073,8 +19079,11 @@
|
|||||||
|
|
||||||
const joinStatements = (stats) => `${stats.join(';')}${stats.length ? ';' : ''}`;
|
const joinStatements = (stats) => `${stats.join(';')}${stats.length ? ';' : ''}`;
|
||||||
|
|
||||||
|
let variableTypes = {};
|
||||||
|
|
||||||
const varToString = ({ name, value, type, final }, noLet) => {
|
const varToString = ({ name, value, type, final }, noLet) => {
|
||||||
if (value === undefined) value = literalInitializers[type] || 'null';
|
if (value === undefined) value = literalInitializers[type] || 'null';
|
||||||
|
variableTypes[name] = type;
|
||||||
return `${noLet !== true ? (final ? 'const ' : 'let ') : ''}${name} = ${value}`;
|
return `${noLet !== true ? (final ? 'const ' : 'let ') : ''}${name} = ${value}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19108,27 +19117,16 @@
|
|||||||
};
|
};
|
||||||
const classVarsMap = {};
|
const classVarsMap = {};
|
||||||
|
|
||||||
let asyncMethods = [];
|
let asyncMethods = {
|
||||||
|
Scanner: ['next', 'nextLine', 'nextInt', 'nextShort', 'nextLong', 'nextFloat', 'nextDouble']
|
||||||
|
};
|
||||||
if (typeof QuintOS != 'undefined') {
|
if (typeof QuintOS != 'undefined') {
|
||||||
// asyncMethods = {
|
Object.assign(asyncMethods, {
|
||||||
// Sprite: ['move']
|
Sprite: ['move'],
|
||||||
// };
|
window: ['alert', 'delay', 'erase', 'eraseRect', 'frame', 'play', 'prompt', 'text', 'textRect']
|
||||||
asyncMethods = [
|
});
|
||||||
'alert',
|
|
||||||
'delay',
|
|
||||||
'erase',
|
|
||||||
'eraseRect',
|
|
||||||
'frame',
|
|
||||||
'move',
|
|
||||||
'play',
|
|
||||||
'print',
|
|
||||||
'println',
|
|
||||||
'printf',
|
|
||||||
'prompt',
|
|
||||||
'text',
|
|
||||||
'textRect'
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
if (!asyncMethods.window) asyncMethods.window = [];
|
||||||
|
|
||||||
const assignParent = (name, isInConstructor) => {
|
const assignParent = (name, isInConstructor) => {
|
||||||
if (name in classVarsMap) {
|
if (name in classVarsMap) {
|
||||||
@ -19195,12 +19193,19 @@
|
|||||||
return `${parseExpr(expr.leftOperand)} ${op} ${parseExpr(expr.rightOperand)}`;
|
return `${parseExpr(expr.leftOperand)} ${op} ${parseExpr(expr.rightOperand)}`;
|
||||||
case 'MethodInvocation':
|
case 'MethodInvocation':
|
||||||
let str = '';
|
let str = '';
|
||||||
if (asyncMethods.includes(expr.name.identifier) && !expr.isInConstructor) {
|
|
||||||
str += 'await ';
|
|
||||||
}
|
|
||||||
if (expr.expression) {
|
if (expr.expression) {
|
||||||
|
let exp = expr.expression;
|
||||||
|
if (exp.expression) exp = exp.expression;
|
||||||
|
let arr = asyncMethods[variableTypes[exp.identifier]];
|
||||||
|
if (arr && arr.length && arr.includes(expr.name.identifier) && !expr.isInConstructor) {
|
||||||
|
str += 'await ';
|
||||||
|
}
|
||||||
str += `${parseExpr(expr.expression)}.${expr.name.identifier}`;
|
str += `${parseExpr(expr.expression)}.${expr.name.identifier}`;
|
||||||
|
if (expr.name.identifier == 'length') return str;
|
||||||
} else {
|
} else {
|
||||||
|
if (asyncMethods.window.includes(expr.name.identifier) && !expr.isInConstructor) {
|
||||||
|
str += 'await ';
|
||||||
|
}
|
||||||
str += `${assignParent(expr.name.identifier)}`;
|
str += `${assignParent(expr.name.identifier)}`;
|
||||||
}
|
}
|
||||||
return str + `(${expr.arguments.map(parseExpr)})`;
|
return str + `(${expr.arguments.map(parseExpr)})`;
|
||||||
@ -19259,9 +19264,9 @@
|
|||||||
data
|
data
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
variableTypes[frag.name.identifier] = data.type;
|
||||||
} else unhandledNode(frag);
|
} else unhandledNode(frag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return vars;
|
return vars;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19344,7 +19349,7 @@
|
|||||||
const arr = Array.isArray(str) ? str : [str];
|
const arr = Array.isArray(str) ? str : [str];
|
||||||
statements.push(...arr.map(semicolon));
|
statements.push(...arr.map(semicolon));
|
||||||
if (method && !method.isAsync && !method.constructor && statements.join('').includes('await')) {
|
if (method && !method.isAsync && !method.constructor && statements.join('').includes('await')) {
|
||||||
asyncMethods.push(method.name.identifier);
|
asyncMethods.window.push(method.name.identifier);
|
||||||
method.isAsync = true;
|
method.isAsync = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ jdk.imports['java.io.InputStream'].load = async () => {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.reset();
|
this.reset();
|
||||||
let _this = this;
|
let _this = this;
|
||||||
if (window?.ide) {
|
if (jdk.log) {
|
||||||
ide.log.onkeyup = () => {
|
jdk.log.onkeyup = () => {
|
||||||
_this.stream = ide.log.value;
|
_this.stream = jdk.log.value;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "java2js",
|
"name": "java2js",
|
||||||
"version": "1.1.9",
|
"version": "1.1.10",
|
||||||
"description": "Converts Java to JavaScript with support for p5.js and QuintOS.",
|
"description": "Converts Java to JavaScript with support for p5.js and QuintOS.",
|
||||||
"main": "jdk.js",
|
"main": "jdk.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user