diff --git a/assets/FontROM7x14.png b/assets/FontROM7x14.png index 112394b..2a16cd9 100644 Binary files a/assets/FontROM7x14.png and b/assets/FontROM7x14.png differ diff --git a/assets/FontROM7x14_HANGUL.kra b/assets/FontROM7x14_HANGUL.kra index dd5e1c2..f69b11e 100644 --- a/assets/FontROM7x14_HANGUL.kra +++ b/assets/FontROM7x14_HANGUL.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:039933a407e399a6f05f5ecb5b25dced31afc6e9f29cc27835f260fb74a60392 -size 230026 +oid sha256:d582665c62dde195d01a5976576291db8e7ce61d7bf4cfd86fc320c1ff2a2744 +size 229930 diff --git a/assets/disk0/home/hangasm.js b/assets/disk0/home/hangasm.js index 30c3eb3..38f6039 100644 --- a/assets/disk0/home/hangasm.js +++ b/assets/disk0/home/hangasm.js @@ -202,8 +202,10 @@ if (unicode.uniprint) { ]) println("조합한글 커널모듈이 로드되었습니다.") + return 0 } else { println("Failed to load Assembly Hangul kernel module: incompatible DOS version") + return 1 } diff --git a/assets/disk0/tvdos/bin/color.js b/assets/disk0/tvdos/bin/color.js index 54d4488..766da6c 100644 --- a/assets/disk0/tvdos/bin/color.js +++ b/assets/disk0/tvdos/bin/color.js @@ -1,4 +1,29 @@ -let backs = {0:[0,0,0],1:[3,2,15],2:[3,9,4],3:[6,13,15],4:[15,4,4],5:[15,6,15],6:[15,13,0],7:[14,14,14]} +let backs = { +0:[0,0,0], +1:[2,1,10], +2:[2,6,3], +3:[4,9,12], +4:[12,3,3], +5:[12,4,12], +6:[12,10,0], +7:[10,10,10], + +8:[5,5,5], +9:[5,3,15], +'A':[5,12,3], +'B':[8,15,15], +'C':[15,6,6], +'D':[15,9,15], +'E':[15,15,0], +'F':[15,15,15], + +'G':[2,3,4], +'H':[2,4,3], +'I':[3,2,4], +'J':[3,4,2], +'K':[4,2,3], +'L':[4,3,2] +} let fores = {0:240,1:49,2:61,3:114,4:211,5:219,6:230,7:254} if (exec_args[1]) { diff --git a/assets/disk0/tvdos/bin/commandko.js b/assets/disk0/tvdos/bin/commandko.js new file mode 100644 index 0000000..dbfeab9 --- /dev/null +++ b/assets/disk0/tvdos/bin/commandko.js @@ -0,0 +1,567 @@ +let PROMPT_TEXT = ">"; +let CURRENT_DRIVE = "A"; +let shell_pwd = [""]; + +let goInteractive = false; +let goFancy = false; + +let DEBUG_PRINT = true; + +let errorlevel = 0; + +const termWidth = con.getmaxyx()[1]; +const termHeight = con.getmaxyx()[0]; +const welcome_text = (termWidth > 40) ? "TSVM Disk Operating System, version " + _TVDOS.VERSION + : "TSVM Disk Operating System " + _TVDOS.VERSION; +const greetLeftPad = (termWidth - welcome_text.length - 6) >> 1; +const greetRightPad = termWidth - greetLeftPad - welcome_text.length - 6; + +function makeHash() { + let e = "YBNDRFG8EJKMCPQXOTLVWIS2A345H769"; + let m = e.length; + return e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] +} + +const shellID = makeHash(); + +function print_prompt_text() { + if (goFancy) { + con.color_pair(239,161); + print(" "+CURRENT_DRIVE+":"); + con.color_pair(161,253); + con.addch(16);con.curs_right(); + con.color_pair(0,253); + print(" /"+shell_pwd.join("/").substring(1)+" "); + if (errorlevel != 0) { + con.color_pair(166,253); + print("["+errorlevel+"] "); + } + con.color_pair(253,255); + con.addch(16);con.curs_right(); + con.addch(32);con.curs_right(); + con.color_pair(253,255); + } + else { +// con.color_pair(253,255); + if (errorlevel != 0) + print(CURRENT_DRIVE + ":/" + shell_pwd.join("/") + " [" + errorlevel + "]" + PROMPT_TEXT); + else + print(CURRENT_DRIVE + ":/" + shell_pwd.join("/") + PROMPT_TEXT); + } +} + +function greet() { + if (goFancy) { + con.color_pair(239,255); + con.clear(); + con.color_pair(253,255); + print(' ');con.addch(17);con.curs_right(); + con.color_pair(0,253); + print(" ".repeat(greetLeftPad)+welcome_text+" ".repeat(greetRightPad)); + con.color_pair(253,255); + con.addch(16);con.curs_right();print(' '); + con.move(3,1); + } + else + println(welcome_text); +} + +/*uninterruptible*/ function sendLcdMsg(s) { + // making it uninterruptible + sys.poke(-1025, (s === undefined) ? 0 : s.charCodeAt(0)|0); + sys.poke(-1026, (s === undefined) ? 0 : s.charCodeAt(1)|0); + sys.poke(-1027, (s === undefined) ? 0 : s.charCodeAt(2)|0); + sys.poke(-1028, (s === undefined) ? 0 : s.charCodeAt(3)|0); + sys.poke(-1029, (s === undefined) ? 0 : s.charCodeAt(4)|0); + sys.poke(-1030, (s === undefined) ? 0 : s.charCodeAt(5)|0); + sys.poke(-1031, (s === undefined) ? 0 : s.charCodeAt(6)|0); + sys.poke(-1032, (s === undefined) ? 0 : s.charCodeAt(7)|0); + sys.poke(-1033, (s === undefined) ? 0 : s.charCodeAt(8)|0); + sys.poke(-1034, (s === undefined) ? 0 : s.charCodeAt(9)|0); + sys.poke(-1035, (s === undefined) ? 0 : s.charCodeAt(10)|0); + sys.poke(-1036, (s === undefined) ? 0 : s.charCodeAt(11)|0); + sys.poke(-1037, (s === undefined) ? 0 : s.charCodeAt(12)|0); + sys.poke(-1038, (s === undefined) ? 0 : s.charCodeAt(13)|0); + sys.poke(-1039, (s === undefined) ? 0 : s.charCodeAt(14)|0); + sys.poke(-1040, (s === undefined) ? 0 : s.charCodeAt(15)|0); + sys.poke(-1041, (s === undefined) ? 0 : s.charCodeAt(16)|0); + sys.poke(-1042, (s === undefined) ? 0 : s.charCodeAt(17)|0); + sys.poke(-1043, (s === undefined) ? 0 : s.charCodeAt(18)|0); + sys.poke(-1044, (s === undefined) ? 0 : s.charCodeAt(19)|0); + sys.poke(-1045, (s === undefined) ? 0 : s.charCodeAt(20)|0); + sys.poke(-1046, (s === undefined) ? 0 : s.charCodeAt(21)|0); + sys.poke(-1047, (s === undefined) ? 0 : s.charCodeAt(22)|0); + sys.poke(-1048, (s === undefined) ? 0 : s.charCodeAt(23)|0); +} + +function trimStartRevSlash(s) { + var cnt = 0; + while (cnt < s.length) { + var chr = s[cnt]; + + if (chr != '\\') break; + + cnt += 1; + } + + return s.substring(cnt); +} + +let shell = {}; +shell.replaceVarCall = function(value) { +// syntax: +// line = literal [varcall] [literal] ; +// varcall = "$" ident ; +// ident = ? regex: [A-Za-z_]+ ? ; +// literal = ? you know what it is ? ; + let replaceMap = []; + let varMode = false; + let sb = ''; + for (let i=0; i= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp == 95 || cp >= 97 && cp <= 122)) { + replaceMap.push({s:sb,r:true}); + sb = ''+char; varMode = false; + } + else sb += char; + }; replaceMap.push({s:sb,r:(varMode)}); + + return replaceMap.map(it => (it.r) ? _TVDOS.variables[it.s] : it.s).join(''); +} +shell.getPwd = function() { return shell_pwd; } +shell.getPwdString = function() { return "\\" + (shell_pwd.concat([""])).join("\\"); } +shell.getCurrentDrive = function() { return CURRENT_DRIVE; } +// example input: echo "the string" > subdir\test.txt +shell.parse = function(input) { + var tokens = []; + var stringBuffer = ""; + var mode = "LITERAL"; // LITERAL, QUOTE, ESCAPE, LIMBO + var i = 0 + while (i < input.length) { + const c = input[i]; +/*digraph g { + LITERAL -> QUOTE [label="\""] + LITERAL -> LIMBO [label="space"] + LITERAL -> LITERAL [label=else] + + QUOTE -> LIMBO [label="\""] + QUOTE -> ESCAPE [label="\\"] + QUOTE -> QUOTE [label=else] + + ESCAPE -> QUOTE + + LIMBO -> LITERAL [label="not space"] + LIMBO -> QUOTE [label="\""] + LIMBO -> LIMBO [label="space"] +}*/ + if ("LITERAL" == mode) { + if (' ' == c) { + tokens.push(stringBuffer); stringBuffer = ""; + mode = "LIMBO"; + } + else if ('"' == c) { + tokens.push(stringBuffer); stringBuffer = ""; + mode = "QUOTE"; + } + else { + stringBuffer += c; + } + } + else if ("LIMBO" == mode) { + if ('"' == c) { + mode = "QUOTE"; + } + else if (c != ' ') { + mode = "LITERAL"; + stringBuffer += c; + } + } + else if ("QUOTE" == mode) { + if ('"' == c) { + tokens.push(stringBuffer); stringBuffer = ""; + mode = "LIMBO"; + } + else if ('^' == c) { + mode = "ESCAPE"; + } + else { + stringBuffer += c; + } + } + else if ("ESCAPE" == mode) { + TODO(); + } + + i += 1; + } + + if (stringBuffer.length > 0) { + tokens.push(stringBuffer); + } + + return tokens; +} +shell.resolvePathInput = function(input) { + // replace revslashes into rslashes + var pathstr = input.replaceAll('\\','/'); + var startsWithSlash = input.startsWith('/'); + var newPwd = []; + + // split them into an array while filtering empty elements except for the root 'head' + var ipwd = (startsWithSlash ? [""] : shell_pwd).concat(pathstr.split("/").filter(function(it) { return (it.length > 0); })); + + serial.println("command.js > resolvePathInput > ipwd = "+ipwd); + serial.println("command.js > resolvePathInput > newPwd = "+newPwd); + + // process dots + ipwd.forEach(function(it) { + serial.println("command.js > resolvePathInput > ipwd.forEach > it = "+it); + if (it === ".." && newPwd[1] !== undefined) { + newPwd.pop(); + } + else if (it !== ".." && it !== ".") { + newPwd.push(it); + } + serial.println("command.js > resolvePathInput > newPwd = "+newPwd); + }); + + // construct new pathstr from pwd arr so it will be sanitised + pathstr = newPwd.join('/').substring(1); + + return { string: pathstr, pwd: newPwd }; +} +shell.coreutils = { +/* Args follow this format: + * <1st arg> <2nd arg> ... + * NOTE: + * even if there's no 1st arg, length of args may not be 1, therefore don't: + * if (args.length < 2) + * but do instead: + * if (args[1] === undefined) + */ + cd: function(args) { + if (args[1] === undefined) { + println(CURRENT_DRIVE+":"+shell_pwd.join("/")); + return + } + var path = shell.resolvePathInput(args[1]) + if (DEBUG_PRINT) serial.println("command.js > cd > pathstr = "+path.string); + + // check if path is valid + var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'R'); + var isDir = filesystem.isDirectory(CURRENT_DRIVE); // open a dir; if path is nonexistent, file won't actually be opened + if (!isDir) { printerrln("CHDIR failed for '"+path.string+"'"); return dirOpenedStatus; } // if file is not opened, IO error code will be returned + + shell_pwd = path.pwd; + }, + mkdir: function(args) { + if (args[1] === undefined) { + printerrln("Syntax error"); + return + } + var path = shell.resolvePathInput(args[1]) + if (DEBUG_PRINT) serial.println("command.js > mkdir > pathstr = "+path.string); + + // check if path is valid + var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'W'); + var mkdird = filesystem.mkDir(CURRENT_DRIVE); + if (!mkdird) { printerrln("MKDIR failed for '"+path.string+"'"); return dirOpenedStatus; } + }, + cls: function(args) { + con.clear(); + graphics.clearPixels(255); + }, + exit: function(args) { + cmdExit = true; + }, + ver: function(args) { + println(welcome_text); + }, + echo: function(args) { + if (args[1] !== undefined) { + args.forEach(function(it,i) { if (i > 0) print(shell.replaceVarCall(it)+" ") }); + } + println(); + }, + rem: function(args) { + return 0; + }, + set: function(args) { + // print all the env vars + if (args[1] === undefined) { + Object.entries(_TVDOS.variables).forEach(function(a) { println(a[0]+"="+a[1]); }) + } + else { + // parse key-value pair with splitter '=' + var key = undefined; var value = undefined; + // if syntax " = " is used? + if ('=' == args[2]) { + key = args[1].toUpperCase(); value = args[3]; + } + else if (args[2] === undefined) { + var pair = args[1].split('='); + key = pair[0].toUpperCase(); value = pair[1]; + } + + if (key == undefined) throw SyntaxError("Input format must be 'key=value'"); + + // if value is undefined, show what envvar[key] has + if (value === undefined) { + if (_TVDOS.variables[key] === undefined) + println("Environment variable '"+key+"' not found"); + else + println(_TVDOS.variables[key]) + } + else { + _TVDOS.variables[key] = shell.replaceVarCall(value); + + // if key is KEYBOARD, reload the keyboard layout + if ("KEYBOARD" == key) + input.changeKeyLayout(_TVDOS.variables.KEYBOARD || "us_qwerty"); + } + } + }, + dir: function(args) { + var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); + + // check if path is valid + var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); + if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } + + var port = filesystem._toPorts(CURRENT_DRIVE)[0] + com.sendMessage(port, "LIST"); + println(com.pullMessage(port)); + }, + cat: function(args) { + var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); + + var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); + if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } + let contents = filesystem.readAll(CURRENT_DRIVE); + // TODO just print out what's there + print(contents); + } +}; +shell.coreutils.chdir = shell.coreutils.cd; +Object.freeze(shell.coreutils); +shell.execute = function(line) { + if (0 == line.size) return; + var tokens = shell.parse(line); + var cmd = tokens[0]; + if (cmd === undefined || cmd === '') return 0; + + // handle Ctrl-C + if (con.hitterminate()) return 1; + + if (shell.coreutils[cmd.toLowerCase()] !== undefined) { + var retval = shell.coreutils[cmd.toLowerCase()](tokens); + return retval|0; // return value of undefined will cast into 0 + } + else { + // search through PATH for execution + + var fileExists = false; + var searchDir = (cmd.startsWith("/")) ? [""] : ["/"+shell_pwd.join("/")].concat(_TVDOS.getPath()); + + var pathExt = []; // it seems Nashorn does not like 'let' too much? this line gets ignored sometimes + // fill pathExt using %PATHEXT% but also capitalise them + if (cmd.split(".")[1] === undefined) + _TVDOS.variables.PATHEXT.split(';').forEach(function(it) { pathExt.push(it); pathExt.push(it.toUpperCase()); }); + else + pathExt.push(""); // final empty extension + + searchLoop: + for (var i = 0; i < searchDir.length; i++) { + for (var j = 0; j < pathExt.length; j++) { + var search = searchDir[i]; if (!search.endsWith('\\')) search += '\\'; + var path = trimStartRevSlash(search + cmd + pathExt[j]); + + if (DEBUG_PRINT) { + serial.println("[command.js > shell.execute] file search path: "+path); + } + + if (0 == filesystem.open(CURRENT_DRIVE, path, "R")) { + fileExists = true; + break searchLoop; + } + } + } + + if (!fileExists) { + printerrln('Bad command or filename: "'+cmd+'"'); + return 127; + } + else { + var programCode = filesystem.readAll(CURRENT_DRIVE); + var extension = undefined; + // get proper extension + var dotSepTokens = cmd.split('.'); + if (dotSepTokens.length > 1) extension = dotSepTokens[dotSepTokens.length - 1].toUpperCase(); + + if ("BAT" == extension) { + // parse and run as batch file + var lines = programCode.split('\n').filter(function(it) { return it.length > 0; }); + lines.forEach(function(line) { + shell.execute(line); + }); + } + else { + let gotError = false; + + try { + errorlevel = 0; // reset the number + + if (_G.shellProgramTitles === undefined) _G.shellProgramTitles = []; + _G.shellProgramTitles.push(cmd.toUpperCase()) + sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1]); + //serial.println(_G.shellProgramTitles); + + errorlevel = execApp(programCode, tokens); // return value of undefined will cast into 0 + } + catch (e) { + gotError = true; + + serial.printerr(`[command.js] program quit with ${e}:\n${e.stack || '(stack trace unavailable)'}`); + + if (`${e}`.startsWith("InterruptedException")) + errorlevel = SIGTERM.name; + else if (e instanceof IllegalAccessException || `${e}`.startsWith("net.torvald.tsvm.ErrorIllegalAccess")) + errorlevel = SIGSEGV.name; + + // exception catched means something went wrong, so if errorlevel is found to be zero, force set to 1. + if (errorlevel === 0 || errorlevel == undefined) + errorlevel = 1; + } + finally { + // sometimes no-error program may return nothing as the errorlevel; force set to 0 then. + if (!gotError && (errorlevel == undefined || (typeof errorlevel.trim == "function" && errorlevel.trim().length == 0) || isNaN(errorlevel))) + errorlevel = 0; + + serial.printerr(`errorlevel: ${errorlevel}`); + + _G.shellProgramTitles.pop(); + sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1]); + //serial.println(_G.shellProgramTitles); + + return errorlevel; + } + } + } + } +}; +Object.freeze(shell); +_G.shell = shell; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +if (exec_args[1] !== undefined) { + // only meaningful switches would be either /c or /k anyway + var firstSwitch = exec_args[1].toLowerCase(); + + // command /c + // ^[0] ^[1] ^[2] + if ("/c" == firstSwitch) { + if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully + return shell.execute(exec_args[2]); + } + else if ("/k" == firstSwitch) { + if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully + shell.execute(exec_args[2]); + goInteractive = true; + } + else if ("/fancy" == firstSwitch) { + graphics.setBackground(2,3,4); + goFancy = true; + goInteractive = true; + } + else { + printerrln("Invalid switch: "+exec_args[1]); + return 1; + } +} +else { + goInteractive = true; +} + +let cmdExit = false; +if (goInteractive) { + con.curs_set(1); + greet(); + + let cmdHistory = []; // zeroth element is the oldest + let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent + while (!cmdExit) { + con.curs_set(1); + print_prompt_text(); + + var cmdbuf = ""; + + while (true) { + let key = con.getch(); + + // printable chars + if (key >= 32 && key <= 126) { + var s = String.fromCharCode(key); + cmdbuf += s; + print(s); + } + // backspace + else if (key === con.KEY_BACKSPACE && cmdbuf.length > 0) { + cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1); + print(String.fromCharCode(key)); + } + // enter + else if (key === 10 || key === con.KEY_RETURN) { + println(); + + shell.execute(cmdbuf); + + if (cmdbuf.trim().length > 0) + cmdHistory.push(cmdbuf); + + cmdHistoryScroll = 0; + con.curs_set(1); + + break; + } + // up arrow + else if (key === con.KEY_UP && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) { + cmdHistoryScroll += 1; + + // back the cursor in order to type new cmd + var x = 0; + for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); + cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; + // re-type the new command + print(cmdbuf); + + } + // down arrow + else if (key === con.KEY_DOWN) { + if (cmdHistoryScroll > 0) { + // back the cursor in order to type new cmd + var x = 0; + for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); + cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; + // re-type the new command + print(cmdbuf); + + cmdHistoryScroll -= 1; + } + else { + // back the cursor in order to type new cmd + var x = 0; + for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); + cmdbuf = ""; + } + } + } + } +} + +return 0; \ No newline at end of file