diff --git a/README.md b/README.md index 821f980..4bd7900 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/demo.html b/demo.html index 3c43d81..157d1f8 100755 --- a/demo.html +++ b/demo.html @@ -17,20 +17,35 @@ } #javaFile { - width: 47vw; - height: 90vh; + width: 48vw; + height: 97vh; } #javaConsole { - width: 47vw; - height: 90vh; + width: 48vw; + height: 97vh; + float: right; }
- - + + diff --git a/ide.js b/ide.js index f987293..f19d7b3 100644 --- a/ide.js +++ b/ide.js @@ -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(); }; })(); diff --git a/jdk.js b/jdk.js index 56a2f2a..1b2a3e3 100755 --- a/jdk.js +++ b/jdk.js @@ -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; } } diff --git a/jdk/java/io/InputStream.js b/jdk/java/io/InputStream.js index 143e7a9..9069c65 100755 --- a/jdk/java/io/InputStream.js +++ b/jdk/java/io/InputStream.js @@ -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; }; } } diff --git a/package.json b/package.json index 712c103..4504296 100644 --- a/package.json +++ b/package.json @@ -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": {