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-01 21:32:29 -05:00
parent ae0e57da70
commit 213f552d52
13 changed files with 308 additions and 243 deletions

View File

@ -1,14 +1,45 @@
# Java to Javascript for QuintOS
# Java to Javascript
I've built on top of the [java-to-javascript](https://github.com/wyattades/java-to-javascript) transpiler by @wyattades and got inspiration from the JDK (Java Development Kit) implementation in [java2javascript](https://github.com/java2script/java2script) by @BobHanson, @zhourenjian, @abego, and others.
I've built on top of the [java-to-javascript](https://github.com/wyattades/java-to-javascript) transpiler by @wyattades and got inspiration from the barebones JDK (Java Development Kit) implementation in [java2javascript](https://github.com/java2script/java2script) by @BobHanson, @zhourenjian, @abego, and others.
My purpose in creating this project is to allow intro level CS students to write Java code but still use my [QuintOS](https://github.com/quinton-ashley/quintos) retro game engine library which is web based. Yes, I went to all this trouble just so some teenagers don't have to run their programs in a boring Java console! I made a barebones JDK implementation in modern Javascript to acheive this.
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. Yes, I went to all this trouble just so my students don't have to run their programs in a boring Java console! To achieve this I've been working on a JDK implementation in modern Javascript.
## Java Classes Included in the JS JDK
| java.io | | |
| ------- | ----------- | ----------- |
| File | InputStream | PrintStream |
| java.lang | | |
| --------- | ------------- | --------- |
| Boolean | Byte | Character |
| Double | Exception | Float |
| Integer | Long | Short |
| String | StringBuilder | System |
| Thread | Throwable | |
| java.security | | |
| ------------- | --- | --- |
| MessageDigest | | |
| java.time | | |
| --------- | --- | --- |
| Instant | | |
| java.util | | |
| ------------------ | ---------------------- | ----------- |
| AbstractCollection | AbstractList | AbstractMap |
| AbstractSet | ArrayList | Arrays |
| Collections | Formatter | HashMap |
| HashSet | IllegalFormatException | Itr |
| LinkedList | Random | Scanner |
| Set | Stack | |
## API
### jdk.init(root)
- root is the local path or url to the parent folder of the jdk, by default it links to 'https://unpkg.com/java2js' (this package) so you don't need to change it
- root (optional) can be a local path or url to the parent folder of the JS `jdk` folder, by default it links to 'https://unpkg.com/java2js' (this package) so you don't need to change it
This function imports much of the standard Java lang classes into the global scope. You must use it before translating or running files.
@ -26,16 +57,22 @@ Loads the JS class file but doesn't run the main method.
### jdk.run(jvmArgs)
Runs the main method with the given JVM arguments.
Runs the main method with optional JVM arguments.
## Known limitations
- casting to int truncation workaround requires parenthesis around the number being cast
- casting to int truncation workaround requires parenthesis around the number being cast `int x = (int) (Math.random() * 100);`
- no support for method overloading, though a workaround might be possible by making a function with the og name route to each of the variations of the overloaded function
- no support for private/public methods yet, though this could be done since they are included in modern JavaScript classes
- no three dimensional arrays
- no third level static classes
- not much error checking
## Contribute
I've only done a barebones implementation of Java 17 JDK, a lot is missing, so if you are 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!

89
jdk.js
View File

@ -80,7 +80,10 @@
lang.push('java.lang.' + name);
}
await this.import(lang);
} catch (e) {}
} catch (ror) {
console.error(ror);
return;
}
// make java.lang classes global
for (let name in this.java.lang) {
@ -130,40 +133,32 @@
for (let className of classNames) {
// log('importing ' + className);
let imp = this.imports[className];
if (imp) {
// class is ready for use
if (imp.ready) ready++;
// class file loaded but not ready yet
continue;
if (!imp) {
imp = this.imports[className] = {};
imp.load = null;
imp.classPath = className.split('.');
let src = this.root + '/jdk';
for (let part of imp.classPath) {
src += '/' + part;
}
src += '.js';
const loadJS = () => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
document.body.appendChild(script);
script.onload = resolve;
script.onerror = reject;
script.async = true;
script.src = src;
});
};
await loadJS();
}
imp = this.imports[className] = {};
imp.load = null;
imp.classPath = className.split('.');
let src = this.root + '/jdk';
for (let part of imp.classPath) {
src += '/' + part;
}
src += '.js';
const loadJS = () => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
document.body.appendChild(script);
script.onload = resolve;
script.onerror = reject;
script.async = true;
script.src = src;
});
};
await loadJS();
await imp.load();
if (!imp.ready) await imp.load();
classes.push(this.getClass(imp.classPath));
}
if (classNames.length == 1) classes = classes[0];
return classes;
}
@ -186,13 +181,17 @@
return '(' + p1.replace(/( |\t){0,3}(.*)(\r*\n|$)/gm, '"$2\\n"+').slice(0, -1) + ')';
});
let arrToString = '';
// hacky support for Array literals
file = file.replace(/new\s*\w*\s*\[\s*\]\s*\{([^}]*)}/gm, 'Array.of($1)');
// 2D arrays
file = file.replace(/new\s*\w*(\s*\[\s*\])*\s*\{([^}]*)\}/gm, (match, p1, p2) => {
return 'Array.of(' + p2.replace(/\{([^\}]*)\}/gm, 'Array.of($1)') + ')';
});
// Array with defined length
file = file.replace(/new\s*\w*\s*\[\s*(\d)+\s*\]\s*/gm, 'new Array($1)');
// workaround hack for converting lambda expressions to Runnables
@ -19210,13 +19209,21 @@
case 'ArrayCreation':
return `new Array()`;
case 'ArrayAccess':
let arr = expr.array;
// TODO support for three dimensional arrays
if (expr.array.array) {
return `${assignParent(expr.array.array.identifier)}[${parseExpr(expr.array.index)}][${parseExpr(
expr.index
)}]`;
if (arr.array) {
return `${assignParent(arr.array.identifier)}[${parseExpr(arr.index)}][${parseExpr(expr.index)}]`;
}
return `${assignParent(expr.array.identifier)}[${parseExpr(expr.index)}]`;
if (arr.identifier) {
return `${assignParent(arr.identifier)}[${parseExpr(expr.index)}]`;
}
// TODO support for third level subclasses
if (arr.qualifier.qualifier) {
return `${assignParent(arr.qualifier.qualifier.identifier)}.${arr.qualifier.name.identifier}.${
arr.name.identifier
}[${parseExpr(expr.index)}]`;
}
return `${assignParent(arr.qualifier.identifier)}.${arr.name.identifier}[${parseExpr(expr.index)}]`;
case 'ParenthesizedExpression':
return `(${parseExpr(expr.expression)})`;
default:
@ -19372,13 +19379,17 @@
return classData;
};
const classToJs = ({ name: className, vars, superclass, methods, abstract }) => {
const classToJs = ({ name: className, vars, superclass, methods, abstract, classes }) => {
if (abstract) return '';
const initVars = [];
const classProps = [];
const staticVars = [];
for (const cl of classes) {
staticVars.push(`${classToJs(cl)}\n${className}.${cl.name}=${cl.name};`);
}
for (const var_ of vars) {
if (var_.value === undefined) var_.value = literalInitializers[var_.type] || 'null';
if (var_.static) {
@ -19404,7 +19415,7 @@
? initVars.join('') + (block ? opts.separator : '')
: '';
let method = `${name}(${parameters}){${preblock}${block}}`;
if (typeof QuintOS != 'undefined' && /(System|alert|prompt|eraseRect)/gm.test(block))
if (typeof QuintOS != 'undefined' && /(alert|prompt|erase|eraseRect|text|textRect|frame)/gm.test(block))
method = 'async ' + method;
classProps.push(method);
}

View File

@ -21,7 +21,7 @@ jdk.imports['java.io.PrintStream'].load = async () => {
);
let md = MessageDigest.getInstance();
let str = '[Ljava.lang.';
let hash = md.digest(arr.toString()).slice(0, 8);
let hash = md.digest(arr.join(',')).slice(0, 8);
if (arr.length == 0) {
return str + 'Array;@' + hash;
}
@ -36,6 +36,8 @@ jdk.imports['java.io.PrintStream'].load = async () => {
let str;
if (Array.isArray(arg)) {
str = this._printArray(arg);
} else if (typeof arg == 'undefined' || arg == null) {
str = 'null';
} else {
str = arg.toString();
}
@ -57,7 +59,7 @@ jdk.imports['java.io.PrintStream'].load = async () => {
}
printf(format, ...args) {
let str = String.format(format, args);
let str = String.format(format, ...args);
return this._print(str);
}
}

View File

@ -0,0 +1,71 @@
jdk.imports['java.util.AbstractCollection'].load = async () => {
const Itr = await jdk.import('java.util.Itr');
class AbstractCollection {
constructor(co) {
this.arr = co || [];
}
add(idx, elem) {
if (typeof elem == 'undefined') {
return this.arr.push(idx);
} else {
return this.arr.splice(idx, 0, elem);
}
}
addAll(idx, vals) {
this.arr.splice(idx, 0, ...vals);
}
clear() {
this.arr = [];
}
contains(val) {
return this.arr.indexOf(val) != -1;
}
// containsAll(elems) {}
isEmpty() {
return this.arr.length == 0;
}
iterator() {
return new Itr(this);
}
// parallelStream() {}
remove(idxOrElem) {
let index = idxOrElem;
if (typeof idxOrElem != 'number') {
index = this.arr.indexOf(idxOrElem);
}
return this.arr.splice(index, 1);
}
removeAll() {
this.arr = [];
return true;
}
// removeIf() {}
// retainAll(collection) {}
size() {
return this.arr.length;
}
toArray() {
return this.arr;
}
toString() {
return '[' + this.arr.join(', ') + ']';
}
}
jdk.java.util.AbstractCollection = AbstractCollection;
};

View File

@ -1,87 +1,32 @@
jdk.imports['java.util.AbstractList'].load = async () => {
const Itr = await jdk.import('java.util.Itr');
let AbstractCollection = await jdk.import('java.util.AbstractCollection');
class AbstractList {
constructor(arr) {
// TODO
this.arr = arr || [];
class AbstractList extends AbstractCollection {
constructor(...args) {
super(...args);
}
addAll(index, vals) {
this.arr.splice(index, 0, ...vals);
// equals(o) {}
get(idx) {
return this.arr[idx];
}
clear() {
this.arr = [];
indexOf(elem) {
return this.arr.indexOf(elem);
}
lastIndexOf(elem) {
return this.arr.lastIndexOf(elem);
}
poll() {
return this.arr.shift();
}
remove(indexOrElem) {
let index = indexOrElem;
if (typeof indexOrElem != 'number') {
index = this.arr.indexOf(indexOrElem);
}
return this.arr.splice(index, 1);
}
removeAll() {
this.arr = [];
return true;
}
toArray() {
return this.arr;
}
toString() {
return '[' + this.arr.toString() + ']';
}
size() {
return this.arr.length;
}
add(index, elem) {
if (typeof elem == 'undefined') {
return this.arr.push(index);
} else {
return this.arr.splice(index, 0, elem);
}
}
get(index) {
return this.arr[index];
}
contains(val) {
return this.arr.indexOf(val) != -1;
}
// containsAll(elems) {
// }
isEmpty() {
return this.arr.length == 0;
}
set(index, element) {
this.arr[index] = element;
return element;
}
indexOf(element) {
return this.arr.indexOf(element);
}
lastIndexOf(element) {
return this.arr.lastIndexOf(element);
}
iterator() {
return new Itr(this);
set(idx, elem) {
this.arr[idx] = elem;
return elem;
}
}
jdk.java.util.AbstractList = AbstractList;

View File

@ -0,0 +1,80 @@
jdk.imports['java.util.AbstractMap'].load = async () => {
let AbstractCollection = await jdk.import('java.util.AbstractCollection');
let Set = await jdk.import('java.util.Set');
class AbstractMap {
constructor(co) {
this.map = co || {};
class SimpleEntry {
constructor(key, val) {
this.key = key;
this.val = val;
}
getKey() {
return this.key;
}
getValue() {
return this.val;
}
toString() {
return this.key + '=' + this.val;
}
}
this.SimpleEntry = SimpleEntry;
}
clear() {
this.map = {};
}
containsKey(key) {
return this.map.hasOwnProperty(key);
}
containsValue(val) {
return Object.values(this.map).find((x) => x == val) ? true : false;
}
get(key) {
return this.map[key] || null;
}
isEmpty() {
return Object.keys(this.map).length == 0;
}
entrySet() {
let set = new Set();
for (let key in this.map) {
set.add(new this.SimpleEntry(key, this.map[key]));
}
return set;
}
put(key, value) {
const previous_val = this.map[key];
this.map[key] = value;
return previous_val || null;
}
remove(key) {
if (!this.map[key]) return null;
const val = this.map[key];
delete this.map[key];
return val;
}
values() {
const co = new AbstractCollection(Object.values(this.map));
return co;
}
size() {
return Object.keys(this.map).length;
}
}
jdk.java.util.AbstractMap = AbstractMap;
};

View File

@ -0,0 +1,17 @@
jdk.imports['java.util.AbstractSet'].load = async () => {
let AbstractCollection = await jdk.import('java.util.AbstractCollection');
class AbstractSet extends AbstractCollection {
constructor(...args) {
super(...args);
}
add(elem) {
if (this.arr.includes(elem)) return;
super.add(elem);
}
// equals(o) {}
}
jdk.java.util.AbstractSet = AbstractSet;
};

View File

@ -3,7 +3,7 @@ jdk.imports['java.util.ArrayList'].load = async () => {
class ArrayList extends AbstractList {
constructor(...args) {
super(args);
super(...args);
}
}
jdk.java.util.ArrayList = ArrayList;

View File

@ -1,59 +1,9 @@
jdk.imports['java.util.HashMap'].load = async () => {
class HashMap {
constructor() {
this.content = {};
}
let AbstractMap = await jdk.import('java.util.AbstractMap');
get(key) {
return this.content[key];
}
put(key, value) {
const previous_val = this.content[key];
this.content[key] = value;
return previous_val;
}
containsKey(key) {
return this.content.hasOwnProperty(key);
}
remove(key) {
const tmp = this.content[key];
delete this.content[key];
return tmp;
}
keySet() {
const result = new HashSet();
for (const p in this.content) {
if (this.content.hasOwnProperty(p)) {
result.add(p);
}
}
return result;
}
isEmpty() {
return Object.keys(this.content).length == 0;
}
values() {
const result = new HashSet();
for (const p in this.content) {
if (this.content.hasOwnProperty(p)) {
result.add(this.content[p]);
}
}
return result;
}
clear() {
this.content = {};
}
size() {
return Object.keys(this.content).length;
class HashMap extends AbstractMap {
constructor(...args) {
super(...args);
}
}
jdk.java.util.HashMap = HashMap;

View File

@ -1,72 +1,12 @@
jdk.imports['java.util.HashSet'].load = async () => {
class HashSet {
constructor() {
this.content = {};
let AbstractSet = await jdk.import('java.util.AbstractSet');
class HashSet extends AbstractSet {
constructor(...args) {
super(...args);
}
add(val) {
this.content[val] = val;
}
clear() {
this.content = {};
}
contains(val) {
return this.content.hasOwnProperty(val);
}
containsAll(elems) {
return false;
}
addAll(vals) {
const tempArray = vals.toArray(null);
for (let i = 0; i < tempArray.length; i++) {
this.content[tempArray[i]] = tempArray[i];
}
return true;
}
remove(val) {
let b = false;
if (this.content[val]) {
b = true;
}
delete this.content[val];
return b;
}
removeAll() {
return false;
}
size() {
return Object.keys(this.content).length;
}
isEmpty() {
return this.size() == 0;
}
toArray(a) {
const _this = this;
return Object.keys(this.content).map((key) => _this.content[key]);
}
iterator() {
return new java.util.Itr(this);
}
forEach(f) {
for (const p in this.content) {
f(this.content[p]);
}
}
get(index) {
return this.content[index];
}
// equals(o) {}
}
jdk.java.util.HashSet = HashSet;
};

View File

@ -3,7 +3,7 @@ jdk.imports['java.util.LinkedList'].load = async () => {
class LinkedList extends AbstractList {
constructor(...args) {
super(args);
super(...args);
}
}
jdk.java.util.LinkedList = LinkedList;

12
jdk/java/util/Set.js Normal file
View File

@ -0,0 +1,12 @@
jdk.imports['java.util.Set'].load = async () => {
let AbstractSet = await jdk.import('java.util.AbstractSet');
class Set extends AbstractSet {
constructor(...args) {
super(...args);
}
// equals(o) {}
}
jdk.java.util.Set = Set;
};

View File

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