mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
411 lines
14 KiB
Plaintext
411 lines
14 KiB
Plaintext
// define exceptions
|
|
function InterruptedException(m) {
|
|
this.message = m;
|
|
this.stack = (new Error()).stack;
|
|
};
|
|
InterruptedException.prototype = Object.create(Error.prototype);
|
|
InterruptedException.prototype.name = 'InterruptedException';
|
|
InterruptedException.prototype.constructor = InterruptedException;
|
|
function IllegalAccessException(m) {
|
|
this.message = m;
|
|
this.stack = (new Error()).stack;
|
|
};
|
|
IllegalAccessException.prototype = Object.create(Error.prototype);
|
|
IllegalAccessException.prototype.name = 'IllegalAccessException';
|
|
IllegalAccessException.prototype.constructor = IllegalAccessException;
|
|
class SIG {
|
|
constructor(name, number) {
|
|
this.name = "SIG" + name;
|
|
this.number = number|0;
|
|
}
|
|
}
|
|
const SIGTERM = new SIG("TERM",15);
|
|
const SIGSEGV = new SIG("SEGV",11);
|
|
|
|
function generateRandomHashStr(len) {
|
|
let cs = 'qwfpgarstdzxcvbjluyhneiokmQWFPGARSTDZXCVBJLUYHNEIOKM';
|
|
let s = '';
|
|
for (let i = 0; i < len; i++) {
|
|
s += cs[(Math.random()*cs.length)|0];
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// define TVDOS
|
|
const _TVDOS = {};
|
|
_TVDOS.VERSION = "1.0";
|
|
_TVDOS.DRIVES = {}; // Object where key-value pair is <drive-letter> : [serial-port, drive-number]
|
|
// actually figure out the drive letter association
|
|
// Drive A is always the device we're currently on
|
|
_TVDOS.DRIVES["A"] = _BIOS.FIRST_BOOTABLE_PORT;
|
|
//TODO
|
|
|
|
|
|
_TVDOS.getPath = function() {
|
|
return [''].concat(_TVDOS.variables.PATH.split(';'));
|
|
};
|
|
// initial values
|
|
_TVDOS.variables = {
|
|
DOSDIR: "\\tvdos",
|
|
LANG: "EN",
|
|
KEYBOARD: "us_qwerty",
|
|
PATH: "\\tvdos\\bin;\\tbas;\\home",
|
|
PATHEXT: ".com;.bat;.js",
|
|
HELPPATH: "\\tvdos\\help",
|
|
OS_NAME: "Terrarum Virtual DOS",
|
|
OS_VERSION: _TVDOS.VERSION
|
|
};
|
|
Object.freeze(_TVDOS);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const filesystem = {};
|
|
filesystem._toPorts = (driveLetter) => {
|
|
if (driveLetter.toUpperCase === undefined) {
|
|
throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter");
|
|
}
|
|
var port = _TVDOS.DRIVES[driveLetter.toUpperCase()];
|
|
if (port === undefined) {
|
|
throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist");
|
|
}
|
|
return port
|
|
};
|
|
filesystem._close = (portNo) => {
|
|
com.sendMessage(portNo, "CLOSE");
|
|
};
|
|
filesystem._flush = (portNo) => {
|
|
com.sendMessage(portNo, "FLUSH");
|
|
};
|
|
// @return true if operation committed successfully, false if:
|
|
// - opening file with R-mode and target file does not exists
|
|
// throws if:
|
|
// - java.lang.NullPointerException if path is null
|
|
// - Error if operation mode is not "R", "W" nor "A"
|
|
filesystem.open = (driveLetter, path, operationMode) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
|
|
filesystem._flush(port[0]); filesystem._close(port[0]);
|
|
|
|
var mode = operationMode.toUpperCase();
|
|
if (mode != "R" && mode != "W" && mode != "A") {
|
|
throw Error("Unknown file opening mode: " + mode);
|
|
}
|
|
|
|
com.sendMessage(port[0], "OPEN"+mode+'"'+path+'",'+port[1]);
|
|
return com.getStatusCode(port[0]);
|
|
};
|
|
filesystem.getFileLen = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "GETLEN");
|
|
var response = com.getStatusCode(port[0]);
|
|
if (135 == response) {
|
|
throw Error("File not opened");
|
|
}
|
|
if (response < 0 || response >= 128) {
|
|
throw Error("Reading a file failed with "+response);
|
|
}
|
|
return Number(com.pullMessage(port[0]));
|
|
};
|
|
// @return the entire contents of the file in String
|
|
filesystem.readAll = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "READ");
|
|
var response = com.getStatusCode(port[0]);
|
|
if (135 == response) {
|
|
throw Error("File not opened");
|
|
}
|
|
if (response < 0 || response >= 128) {
|
|
throw Error("Reading a file failed with "+response);
|
|
}
|
|
return com.pullMessage(port[0]);
|
|
};
|
|
filesystem.readAllBytes = (driveLetter) => {
|
|
var str = filesystem.readAll(driveLetter);
|
|
var bytes = new Uint8Array(str.length);
|
|
for (let i = 0; i < str.length; i++) {
|
|
bytes[i] = str.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
};
|
|
filesystem.write = (driveLetter, string) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "WRITE"+string.length);
|
|
var response = com.getStatusCode(port[0]);
|
|
if (135 == response) {
|
|
throw Error("File not opened");
|
|
}
|
|
if (response < 0 || response >= 128) {
|
|
throw Error("Writing a file failed with "+response);
|
|
}
|
|
com.sendMessage(port[0], string);
|
|
filesystem._flush(port[0]); filesystem._close(port[0]);
|
|
};
|
|
filesystem.writeBytes = (driveLetter, bytes) => {
|
|
var string = String.fromCharCode(...bytes);
|
|
filesystem.write(driveLetter, string);
|
|
};
|
|
filesystem.isDirectory = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "LISTFILES");
|
|
var response = com.getStatusCode(port[0]);
|
|
return (response === 0);
|
|
};
|
|
filesystem.mkDir = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "MKDIR");
|
|
var response = com.getStatusCode(port[0]);
|
|
|
|
if (response < 0 || response >= 128) {
|
|
var status = com.getDeviceStatus(port[0]);
|
|
throw Error("Creating a directory failed with ("+response+"): "+status.message+"\n");
|
|
}
|
|
return (response === 0); // possible status codes: 0 (success), 1 (fail)
|
|
};
|
|
filesystem.touch = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "TOUCH");
|
|
var response = com.getStatusCode(port[0]);
|
|
return (response === 0);
|
|
};
|
|
filesystem.mkFile = (driveLetter) => {
|
|
var port = filesystem._toPorts(driveLetter);
|
|
com.sendMessage(port[0], "MKFILE");
|
|
var response = com.getStatusCode(port[0]);
|
|
return (response === 0);
|
|
};
|
|
Object.freeze(filesystem);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const input = {};
|
|
const inputwork = {};
|
|
|
|
inputwork.keymap = [];
|
|
|
|
input.changeKeyLayout = function(name) {
|
|
let res0 = filesystem.open("A",`tvdos/${name.toLowerCase()}.key`,"R");
|
|
if (res0 != 0 && inputwork.keymap.length == 0) throw new Error(`I/O Error ${res0} - A:\\tvdos\\${name.toLowerCase()}.key`);
|
|
try {
|
|
inputwork.keymap = eval(filesystem.readAll("A"));
|
|
}
|
|
catch (e) {
|
|
printerrln(e);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// load initial key layout
|
|
input.changeKeyLayout(_TVDOS.variables.KEYBOARD || "us_qwerty");
|
|
|
|
// states to run the keyboard input
|
|
inputwork.stroboTime = 4294967296*0;
|
|
inputwork.stroboDelays = [0,250000000,0,25000000,0]; // 250ms, 25ms
|
|
inputwork.stroboStatus = 0; // 0: first key, 1: waiting for initial delay, 2: repeating key, 3: waiting for repeat delay
|
|
inputwork.oldKeys = [];
|
|
inputwork.oldMouse = [];
|
|
inputwork.repeatCount = 0;
|
|
//inputwork.keyChanged = false;
|
|
|
|
/**
|
|
* callback: takes one argument of object which
|
|
* [ <eventname>, args ]
|
|
* where:
|
|
* "key_down", <key symbol string>, <repeat count>, keycode0, keycode1 .. keycode7
|
|
* "key_change", <key symbol string (what went up)>, 0, keycode0, keycode1 .. keycode7 (remaining keys that are held down)
|
|
* "mouse_down", pos-x, pos-y, 1 // yes there's only one mouse button :p
|
|
* "mouse_up", pos-x, pos-y, 0
|
|
* "mouse_move", pos-x, pos-y, <button down?>, oldpos-x, oldpos-y
|
|
*/
|
|
input.withEvent = function(callback) {
|
|
|
|
// TODO mouse event
|
|
function arrayEq(a,b) {
|
|
for (let i = 0; i < a.length; ++i) {
|
|
if (a[i] !== b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function arrayDiff(a,b) {
|
|
return a.filter(x => !b.includes(x));
|
|
}
|
|
|
|
function keysToStr(keys) {
|
|
//let shiftin = keys.includes(59) || keys.includes(60);
|
|
let headkey = keys.head();
|
|
//return (inputwork.keymap[headkey] == undefined) ? undefined : (shiftin) ? (inputwork.keymap[headkey][1] || inputwork.keymap[headkey][0]) : inputwork.keymap[headkey][0];
|
|
return (inputwork.keymap[headkey] == undefined) ? undefined : inputwork.keymap[headkey];
|
|
}
|
|
|
|
sys.poke(-40, 255);
|
|
let keys = [sys.peek(-41),sys.peek(-42),sys.peek(-43),sys.peek(-44),sys.peek(-45),sys.peek(-46),sys.peek(-47),sys.peek(-48)];
|
|
let mouse = [sys.peek(-33) | (sys.peek(-34) << 8), sys.peek(-35) | (sys.peek(-36) << 8), sys.peek(-37)];
|
|
let keyChanged = !arrayEq(keys, inputwork.oldKeys)
|
|
let keyDiff = arrayDiff(keys, inputwork.oldKeys)
|
|
|
|
// FIXME does not work with QMK's "LT(1,KC_ENT)" or something similar
|
|
//if (keys.sum() > 0) serial.println(`! captured=${keys.join()}`)
|
|
|
|
if (inputwork.stroboStatus % 2 == 0 && keys[0] != 0) {
|
|
inputwork.stroboStatus += 1;
|
|
inputwork.stroboTime = sys.nanoTime();
|
|
inputwork.repeatCount += 1;
|
|
|
|
let shiftin = keys.includes(59) || keys.includes(60);
|
|
let keysym0 = keysToStr(keys);
|
|
let newKeysym0 = keysToStr(keyDiff);
|
|
let keysym = (keysym0 === undefined) ? undefined : (shiftin && keysym0[1]) ? keysym0[1] : keysym0[0];
|
|
let newKeysym = (newKeysym0 === undefined) ? undefined : (shiftin && newKeysym0[1]) ? newKeysym0[1] : newKeysym0[0];
|
|
|
|
|
|
//serial.println(`keys=${keys.join()}; oldkeys=${inputwork.oldKeys}; keyDiff=${keyDiff}`)
|
|
//serial.println(`keysym=${keysym}; newkeysym=${newKeysym}`)
|
|
|
|
if (!keyChanged) {
|
|
callback(["key_down", keysym, inputwork.repeatCount].concat(keys));
|
|
}
|
|
else if (newKeysym !== undefined) {
|
|
callback(["key_down", newKeysym, inputwork.repeatCount].concat(keys));
|
|
}
|
|
|
|
|
|
inputwork.oldKeys = keys; // don't put this outside of if-cascade
|
|
}
|
|
else if (keyChanged || keys[0] == 0) {
|
|
inputwork.stroboStatus = 0;
|
|
inputwork.repeatCount = 0;
|
|
|
|
if (keys[0] == 0)
|
|
inputwork.keyChanged = false;
|
|
|
|
if (keyChanged) {
|
|
//callback(["key_change", keysToStr(arrayDiff(keys, inputwork.oldKeys)), inputwork.repeatCount].concat(keys));
|
|
|
|
//serial.println(`$ oldkeys=${inputwork.oldKeys}; newkeys=${keys}`)
|
|
//serial.println(`$ keydiff=${arrayDiff(keys, inputwork.oldKeys)}; newkeysym=${keysToStr(arrayDiff(keys, inputwork.oldKeys))}`)
|
|
|
|
//inputwork.keyChanged = keysToStr(arrayDiff(keys, inputwork.oldKeys));
|
|
}
|
|
}
|
|
else if (inputwork.stroboStatus % 2 == 1 && sys.nanoTime() - inputwork.stroboTime < inputwork.stroboDelays[inputwork.stroboStatus]) {
|
|
sys.spin();
|
|
}
|
|
else {
|
|
inputwork.stroboStatus += 1;
|
|
if (inputwork.stroboStatus >= 4) {
|
|
inputwork.stroboStatus = 2;
|
|
}
|
|
}
|
|
|
|
inputwork.oldMouse = mouse;
|
|
|
|
};
|
|
|
|
Object.freeze(input);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const unicode = {};
|
|
unicode.utf8toCodepoints = function(utf8text) {
|
|
let UTF8_ACCEPT = 0
|
|
let UTF8D = [
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
|
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
|
|
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
|
|
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
|
|
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
|
|
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
|
|
12,36,12,12,12,12,12,12,12,12,12,12,
|
|
]
|
|
|
|
let state = UTF8_ACCEPT
|
|
let codep = 0
|
|
let codepoints = []
|
|
|
|
for (let i=0; i < utf8text.length; i++) {
|
|
let byte = utf8text.charCodeAt(i)
|
|
let type = UTF8D[byte]
|
|
codep = (state != UTF8_ACCEPT) ?
|
|
(byte & 0x3f) | (codep << 6) : (0xff >> type) & (byte)
|
|
state = UTF8D[256 + state + type]
|
|
if (state == UTF8_ACCEPT)
|
|
codepoints.push(codep)
|
|
}
|
|
return codepoints
|
|
}
|
|
|
|
// array of array: [predicate_for_coderange: (Codepoint) -> Boolean , printing_function: (Codepoint) -> Unit_that_does_screen_drawing]
|
|
// Usage: unicode.uniprint.push[(c) => 0xAC00 <= c && c <= 0xD7A3, printHalfRowHangul]
|
|
unicode.uniprint = [];
|
|
unicode.uniprint.push([c => (0 <= c && c <= 255), c => { sys.print(String.fromCodePoint(c)) }]);
|
|
// @return [predicate, printing function]
|
|
unicode.getUniprint = (c) => {
|
|
for (let k = 0; k < unicode.uniprint.length; k++) {
|
|
if (unicode.uniprint[k][0](c))
|
|
return unicode.uniprint[k]
|
|
}}
|
|
|
|
print = function(str) {
|
|
if ((typeof str === 'string' || str instanceof String) && str.length > 0) {
|
|
let cp = unicode.utf8toCodepoints(str)
|
|
cp.forEach(c => {
|
|
let q = unicode.getUniprint(c)
|
|
|
|
if (q == undefined || !q[0](c)) {
|
|
con.addch(4)
|
|
con.curs_right()
|
|
}
|
|
else {
|
|
q[1](c)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
Object.freeze(unicode);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// install other stuffs
|
|
filesystem.open("A", "tvdos/gl.js", "R");
|
|
const GL = eval(filesystem.readAll("A"));
|
|
|
|
let checkTerm = `if (sys.peek(-49)&1) throw new InterruptedException();`;
|
|
let injectIntChk = (s, n) => {
|
|
// primitive way of injecting a code; will replace a JS string that matches the regex...
|
|
let k = s
|
|
.replace(/while *\([^\n]+\) *{/, "$& "+n+"();")
|
|
.replace(/for *\([^\n]+\) *{/, "$& "+n+"();")
|
|
.replace(/do *{/, "$& "+n+"();");
|
|
//serial.println(k);
|
|
return k;
|
|
}
|
|
|
|
// @param cmdsrc JS source code
|
|
// @param args arguments for the program, must be Array, and args[0] is always the name of the program, e.g.
|
|
// for command line 'echo foo bar', args[0] must be 'echo'
|
|
// @return status returned by the program
|
|
var execApp = (cmdsrc, args) => {
|
|
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
|
|
var execAppPrg = eval(
|
|
`var ${intchkFunName} = function(){ ${checkTerm} };` +
|
|
`var _appStub=function(exec_args){${injectIntChk(cmdsrc, intchkFunName)}\n};` +
|
|
`_appStub`); // making 'exec_args' a app-level global
|
|
|
|
return execAppPrg(args);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Boot script
|
|
serial.println("TVDOS.SYS initialised, running boot script...");
|
|
var _G = {};
|
|
filesystem.open("A", "tvdos/bin/command.js", "R");
|
|
execApp(filesystem.readAll("A"), ["", "/c", "\\AUTOEXEC.BAT"]);
|