1
0
mirror of https://github.com/quinton-ashley/java2js synced 2024-12-29 10:11:54 +01:00
This commit is contained in:
Quinton Ashley 2022-02-20 13:52:18 -05:00
parent 8de0c299bb
commit 7e855616c8
6 changed files with 100 additions and 77 deletions

View File

@ -2,11 +2,9 @@
`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 JS JDK
## Java classes included in the java2js JDK
| java.io | | |
| ------- | ----------- | ----------- |
@ -37,32 +35,10 @@ I created this project so intro level computer science students could write Java
| LinkedList | Random | Scanner |
| 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
The java2js transpiler supports:
- imports
- static classes
- static methods
@ -72,6 +48,26 @@ Runs the main method with optional JVM arguments.
- lambda arrow functions
- 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
- 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
## 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
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!

View File

@ -17,20 +17,35 @@
}
#javaFile {
width: 47vw;
height: 90vh;
width: 48vw;
height: 97vh;
}
#javaConsole {
width: 47vw;
height: 90vh;
width: 48vw;
height: 97vh;
float: right;
}
</style>
</head>
<body>
<textarea id="javaFile" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
<textarea id="javaConsole" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
<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="ide.js"></script>

3
ide.js
View File

@ -5,9 +5,8 @@
let file0 = document.getElementById('javaFile');
file0.onchange = async () => {
jdk.log.value = '';
let translation = await jdk.translate(file0.value);
let translation = await jdk.transpile(file0.value);
console.log(translation);
jdk.load(translation);
jdk.run();
};
})();

79
jdk.js
View File

@ -48,7 +48,7 @@
constructor() {}
async init(root) {
this.root = root || './';
this.root = root || '.';
this.java = {};
let pkgs = ['com', 'io', 'lang', 'org', 'security', 'time', 'util'];
for (let pkg of pkgs) {
@ -94,8 +94,10 @@
System.in.reset();
System.out.reset();
// stub main for now
this.main = () => {};
// stub main
this.main = () => {
console.error('No main method found in loaded classes!');
};
}
run(jvmArgs) {
@ -152,7 +154,7 @@
return classes;
}
async translate(file) {
async transpile(file) {
if (!this.java) {
console.error(
'java2js error: JDK not initialized! You must call `await jdk.init()` before using `jdk.load()`'
@ -216,12 +218,11 @@
return 'new Runnable("' + in0 + '")';
});
// convert string .length() method
file = file.replace(/\.length\(\)/gm, '.length');
// cast to int, truncates the number (just removes decimal value)
file = file.replace(/\(int\)\s*/gm, 'Math.floor');
let packageName = (file.match(/package\s+([^;]+)/gm) || [])[1] || 'default';
let trans = await java_to_javascript(file);
// log(trans);
@ -233,7 +234,7 @@
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
for (let i = 0; i < imports.length; i++) {
@ -252,17 +253,22 @@
let suffix = '\n';
suffix += `window.${className} = ${className};\n`;
suffix += '})();';
suffix += '};';
trans = prefix + trans + suffix;
return trans;
}
load(trans) {
const script = document.createElement('script');
script.innerHTML = trans;
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();
@ -19073,8 +19079,11 @@
const joinStatements = (stats) => `${stats.join(';')}${stats.length ? ';' : ''}`;
let variableTypes = {};
const varToString = ({ name, value, type, final }, noLet) => {
if (value === undefined) value = literalInitializers[type] || 'null';
variableTypes[name] = type;
return `${noLet !== true ? (final ? 'const ' : 'let ') : ''}${name} = ${value}`;
};
@ -19108,27 +19117,16 @@
};
const classVarsMap = {};
let asyncMethods = [];
let asyncMethods = {
Scanner: ['next', 'nextLine', 'nextInt', 'nextShort', 'nextLong', 'nextFloat', 'nextDouble']
};
if (typeof QuintOS != 'undefined') {
// asyncMethods = {
// Sprite: ['move']
// };
asyncMethods = [
'alert',
'delay',
'erase',
'eraseRect',
'frame',
'move',
'play',
'print',
'println',
'printf',
'prompt',
'text',
'textRect'
];
Object.assign(asyncMethods, {
Sprite: ['move'],
window: ['alert', 'delay', 'erase', 'eraseRect', 'frame', 'play', 'prompt', 'text', 'textRect']
});
}
if (!asyncMethods.window) asyncMethods.window = [];
const assignParent = (name, isInConstructor) => {
if (name in classVarsMap) {
@ -19195,12 +19193,19 @@
return `${parseExpr(expr.leftOperand)} ${op} ${parseExpr(expr.rightOperand)}`;
case 'MethodInvocation':
let str = '';
if (asyncMethods.includes(expr.name.identifier) && !expr.isInConstructor) {
str += 'await ';
}
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}`;
if (expr.name.identifier == 'length') return str;
} else {
if (asyncMethods.window.includes(expr.name.identifier) && !expr.isInConstructor) {
str += 'await ';
}
str += `${assignParent(expr.name.identifier)}`;
}
return str + `(${expr.arguments.map(parseExpr)})`;
@ -19259,9 +19264,9 @@
data
)
);
variableTypes[frag.name.identifier] = data.type;
} else unhandledNode(frag);
}
return vars;
};
@ -19344,7 +19349,7 @@
const arr = Array.isArray(str) ? str : [str];
statements.push(...arr.map(semicolon));
if (method && !method.isAsync && !method.constructor && statements.join('').includes('await')) {
asyncMethods.push(method.name.identifier);
asyncMethods.window.push(method.name.identifier);
method.isAsync = true;
}
}

View File

@ -3,9 +3,9 @@ jdk.imports['java.io.InputStream'].load = async () => {
constructor() {
this.reset();
let _this = this;
if (window?.ide) {
ide.log.onkeyup = () => {
_this.stream = ide.log.value;
if (jdk.log) {
jdk.log.onkeyup = () => {
_this.stream = jdk.log.value;
};
}
}

View File

@ -1,6 +1,6 @@
{
"name": "java2js",
"version": "1.1.9",
"version": "1.1.10",
"description": "Converts Java to JavaScript with support for p5.js and QuintOS.",
"main": "jdk.js",
"scripts": {