still wip modularisation, game somehow boots

This commit is contained in:
minjaesong
2018-06-21 17:33:22 +09:00
parent 6bbfd5d167
commit 8daf0a2c38
266 changed files with 2409 additions and 1122 deletions

View File

@@ -0,0 +1,6 @@
local args = {...}
if (#args ~= 2) then
print([[usage: cp source_file target_file
cp source_file target_directory]])
return end
fs.cp(os.expandPath(args[1]), os.expandPath(args[2]))

View File

@@ -0,0 +1,257 @@
local args = {...}
os.dshenv = {}
--[[
DUMBSHELL: semi sh-compatible language interpreter
SYNOPSIS
dsh [option] [file]
sh [option] [file]
OPTIONS
-c string If the -c option is present, then commands are read from
string. If there are arguments after the string, they are
assigned to the positional parameters, starting with $0.
]]
-- returns full path. if p starts with "/", only the p is returned
local function expandPath(p)
return (p:byte(1) == 47) and p or os.expandPath(p)
end
local function startsFromRoot(p)
return p:byte(1) == 47
end
local function endsWithSlash(p)
return p:byte(#p) == 47
end
--__DSHDEBUG__ = 0x51621D
local function debug(msg)
if __DSHDEBUG__ then print("DEBUG", msg) end
end
local function printErr(msg)
print(DLE..msg)
end
local function shallowCopy(t)
return {table.unpack(t)}
end
-- BUILTINS -------------------------------------------------------------------
local function cd(tArgs)
local dir = tArgs[1]
if (dir == nil or #dir < 1) then return end
local oldWorkingDir = shallowCopy(os.workingDir)
-- parse dir by delimeter '/'
if (dir:byte(1) == 47) then -- if dir begins with '/'
os.setWorkingDir(dir)
else
for word in string.gmatch(dir, "[^/]+") do
machine.println("CD word: "..word)
-- 'execute' directory
-- Rules: '..' pops os.workingDir
-- if dir begins with '/', re-build os.workingDir
-- otherwise, push the 'word' to os.workingDir
if (word == "..") then
os.popWorkingDir()
elseif (word == ".") then
-- pass
else
os.pushWorkingDir(word)
end
end
end
-- check if the directory exists
if not fs.isDir(os.fullWorkPath()) then
os.errorNoSuchFileOrDir("cd: "..dir)
os.workingDir = shallowCopy(oldWorkingDir)
return
end
end
local function exit(tArgs)
exitshell = true
end
local function exec(tArgs)
debug("EXECARGS\t"..table.concat(tArgs, ", "))
if (tArgs[1] == nil or #tArgs[1] < 1) then return end
local filePath = tArgs[1]
local fullFilePath = expandPath(tArgs[1])
local execArgs = {}
for i, v in ipairs(tArgs) do
if (i >= 2) then table.insert(execArgs, v) end
end
local execByPathFileExists = false
local execByPathArg = ""
-- do some sophisticated file-matching
-- step 1: exact file
if fs.isFile(fullFilePath) then
shell.run(fullFilePath, execArgs)
-- step 2: try appending ".lua"
elseif fs.isFile(fullFilePath..".lua") then
shell.run(fullFilePath..".lua", execArgs)
-- step 3: parse os.path (just like $PATH)
-- step 3.1: exact file; step 3.2: append ".lua"
elseif not startsFromRoot(filePath) then
for path in string.gmatch(os.path, "[^;]+") do
-- check if 'path' ends with '/'
if not endsWithSlash(path) then path = path.."/" end
debug(path..filePath)
if fs.isFile(path..filePath) then
execByPathArg = path..filePath
execByPathFileExists = true
break
elseif fs.isFile(path..filePath..".lua") then
execByPathArg = path..filePath..".lua"
execByPathFileExists = true
break
end
end
end
-- step else: file not found
if execByPathFileExists then
shell.run(execByPathArg, execArgs)
return EXIT_SUCCESS
else
if filePath:byte(1) == 46 or filePath:byte(1) == 47 then
os.errorNoSuchFile(filePath)
else
os.errorCmdNotFound(filePath)
end
end
return false
end
-- SYNTAX PARSER --------------------------------------------------------------
-- tables with functions
local builtins = {
cd = cd,
exit = exit,
exec = exec,
clear = term.clear
}
local function runcommand(str, recurse)
if #str < 1 then return end
local cmdFound = false
-- simple cmd parse: WORD ARG1 ARG2 ARG3 ...
local args = {}
local command = ""
for word in string.gmatch(str, "[^ ]+") do
if #command < 1 then command = word -- first word will be a name of command
else table.insert(args, word) end
end
if builtins[command] then -- try for builtins table
builtins[command](args)
cmdFound = true
return true
else
-- FIXME: 'exec programname args' works, but not 'programname args'
-- try for os.dshenv.aliases
if os.dshenv.aliases[command] then
--builtins[os.dshenv.aliases[command]](args)
if not recurse then
cmdFound = runcommand(os.dshenv.aliases[command], true)
end
else
-- try to launch as program
if not recurse then
cmdFound = runcommand("exec "..str, true)
end
end
end
-- command not found (really)
if not cmdFound then
os.errorCmdNotFound(command)
end
end
-- END OF SYNTAX PARSER -------------------------------------------------------
-- INIT SHELL -----------------------------------------------------------------
exitshell = false
-- load up aliases
if fs.isFile("/etc/.dshrc") then
fs.dofile("/etc/.dshrc")
machine.println("[dummix/dsh.lua] Dsh aliases successfully loaded.")
end
-- END OF INIT SHELL ----------------------------------------------------------
-- run interpreter and quit
if (args[1]) then
local f = fs.open(args[1], "r")
local line = ""
local s = ""
-- treat interpreter key (#!) properly
-- I'm assuming I was called because I'm the right one
-- I have a full trust on "shell.run()" that it rightfully redirected to me
--
-- NOTE: shell redirection should only apply in interactive mode AND the input
-- was like "./filename", or else I'm the right one. Period.
-- (and that's how BASH works)
repeat
line = f.readLine()
if line == nil then break end
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
s = s.." "..line
end
until line == nil
f.close()
runcommand(s)
exitshell = true
end
function getPromptText()
--return DC4..os.workingDir[#os.workingDir]..DC3.."# "..DC4 -- we're root! omgwtf
return DC4..os.fullWorkPath()..DC3.."# "..DC4 -- we're root! omgwtf
end
-- interactive mode
local time = os.date()
print(time)
repeat
term.setCursorBlink(true)
io.write(getPromptText())
local s = input.readLine()
runcommand(s)
until exitshell
collectgarbage()
return EXIT_SUCCESS

View File

@@ -0,0 +1,170 @@
--[[
LESS IS MORE
SYNOPSIS:
lessismore [filename]
less [filename]
more [filename]
]]
local args = {...}
displayLineNo = true
local prompt = function()
term.setForeCol(3)
term.emitString(" scroll ", 3, term.height())
term.emitString(" quit", 14, term.height())
term.setForeCol(1)
term.emit(18, 1, term.height())
term.emit(29, 2, term.height())
term.emit(81, 13, term.height())
term.setForeCol(3)
term.setBackCol(0)
end
local function printUsage()
print("More: no file specified.")
print("Usage: more [filename]")
end
if args[1] == nil or #args[1] <= 0 then printUsage() return end
filepath = os.expandPath(args[1])
if not fs.isFile(filepath) then os.errorNoSuchFile(filepath) return end
function log10(n)
if n < 1 then return 0
elseif n < 10 then return 1
elseif n < 100 then return 2
elseif n < 1000 then return 3
elseif n < 10000 then return 4
elseif n < 100000 then return 5
elseif n < 1000000 then return 6
elseif n < 10000000 then return 7
elseif n < 100000000 then return 8
elseif n < 1000000000 then return 9
else return 10
end
end
----------------
-- fetch text --
----------------
lines = {}
displayHeight = term.height() - 1 -- bottom one line for prompt
local file = fs.open(filepath, "r")
local line = ""
repeat
line = file.readLine()
table.insert(lines, line)
until line == nil
lineNoLen = log10(#lines)
-----------
-- input --
-----------
local function scrollDownAction(n)
term.clearLine() -- prevent prompt to be scrolled
curY = curY + n
-- prevent overscroll
if (curY > #lines - displayHeight) then
curY = #lines - displayHeight
end
term.scroll(n)
for i = 0, n - 1 do
drawString(curY + displayHeight - i, displayHeight - i) -- redraw newline
end
end
local function scrollUpAction(n)
curY = curY - n
-- prevent overscroll
if (curY < 1) then
curY = 1
end
term.scroll(-n)
for i = 0, n - 1 do
drawString(curY + i, i + 1) -- redraw prev line
end
term.setCursor(n, term.height())
end
local function processInput()
if input.isKeyDown(keys.q) then quit = true end
if input.isKeyDown(keys.down) and curY < #lines - displayHeight then
scrollDownAction(1)
prompt()
elseif input.isKeyDown(keys.pageDown) and curY < #lines - displayHeight then
scrollDownAction(8)
prompt()
elseif input.isKeyDown(keys.up) and curY > 1 then
scrollUpAction(1)
term.clearLine() -- make space for prompt
prompt()
elseif input.isKeyDown(keys.pageUp) and curY > 1 then
scrollUpAction(8)
term.clearLine() -- make space for prompt
prompt()
end
machine.sleep(50)
end
-------------
-- display --
-------------
displayWidth = term.width() - 1 - (displayLineNo and lineNoLen or 0)
function drawString(lineNo, y)
local string = (lineNo > #lines) and ""
or lines[lineNo]:sub(curX, curX + displayWidth)
if (displayLineNo) then
local lineNoStr = DC3..string.format("%"..lineNoLen.."d", curY + y - 1)..DC4
string = lineNoStr..string
end
local strDrawX = curX
term.emitString(string, strDrawX, y)
end
function redrawText()
for i = curY, #lines do
if (i >= displayHeight + curY) then break end
drawString(i, i - curY + 1)
end
end
curY = 1
curX = 1
quit = false
if term.isTeletype() then
for _, l in ipairs(line) do
term.print(l)
end
quit = true
end
term.clear()
term.setCursorBlink(false)
redrawText()
repeat
prompt()
term.setCursor(1, term.height())
processInput()
until quit
term.clearLine()
return

View File

@@ -0,0 +1,10 @@
local args = {...}
local dir = os.fullWorkPath()--(#args < 1) and os.fullWorkPath() or args[1]
local list = fs.list("/"..dir)
table.sort(list)
for _, v in ipairs(list) do
print(v)
end

View File

@@ -0,0 +1,76 @@
local args = {...}
local _APPVERSION = 0.3
--[[
MOONSHELL: basically just lua.lua
SYNOPSIS
msh [file]
msh: Runs shell in interactive mode
msh [file]: Try to execute file as Lua script
]]
-- run interpreter and quit
if (args[1]) then
local f = fs.open(args[1], "r")
local line = ""
local s = ""
-- treat interpreter key (#!) properly
-- I'm assuming I was called because I'm the right one
-- I have a full trust on "shell.run()" that it rightfully redirected to me
repeat
line = f.readLine()
if line == nil then break end
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
s = s.." "..line
end
until line == nil
f.close()
xpcall(
function() _G.runscript(s, "="..args[1]) end,
function(err) print(DLE..err) end
)
goto terminate
end
-- interactive mode. This is a copy of BOOT.lua
run = shell.run
print("Moonshell "..DC2.._APPVERSION..DC4..", running "..DC2.._VERSION..DC4)
print("Lua is copyrighted (C) 1994-2013 Lua.org, PUC-Rio")
print("Run run(path) to execute program on 'path'.")
print("Run exit() to quit.")
while not machine.isHalted() do
term.setCursorBlink(true)
io.write(DC3.."lua"..computer.prompt)
local s = input.readLine()
if s == "exit()" then break end
xpcall(
function()
if s:byte(1) == 61 then -- print out value
s1 = string.sub(s, 2)
_G.runscript("print(tostring("..s1.."))\n", "=stdin")
else
_G.runscript(s, "=stdin")
end
end,
function(err) print(DLE..err) end -- it catches logical errors
)
end
::terminate::
collectgarbage()
return EXIT_SUCCESS

View File

@@ -0,0 +1,6 @@
local args = {...}
if (#args ~= 2) then
print([[usage: mv source target
mv source ... directory]])
return end
fs.mv(os.expandPath(args[1]), os.expandPath(args[2]))

View File

@@ -0,0 +1 @@
fs.dofile("/etc/_boot.lua")

View File

@@ -0,0 +1,8 @@
-- dsh aliases
os.dshenv.aliases = {
lua = "msh",
sh = "dsh",
shutdown = "exit", -- should be a separate program that actually halts the system
less = "lessismore",
more = "lessismore"
}

View File

@@ -0,0 +1,117 @@
--[[
Bootloader for Operation System
Created by minjaesong on 2016-09-21
]]
-- check directories
dirlist = {
"/boot",
"/bin", -- crucial binaries (e.g. cat, ls, sh(ell), cp, rm, mkdir), it's loosely an UNIX system
"/usr",
"/usr/bin", -- more utilities and binaries (e.g. less/more, nano)
"/home", -- home directory for user
"/home/bin", -- user-installed apps
"/media" -- auto mounts (e.g. "/media/fd1", "/media/hdb", "/media/sda")
}
-- just make them if they don't exist
for _, dir in ipairs(dirlist) do
fs.mkdir(dir)
end
if not _G.os then _G.os = {} end
os.version = "0.0"
os.EXIT_SUCCESS = 0
os.workingDir = {"home"}
os.path = "home/bin/;/usr/bin/;/bin/" -- infamous $path
os.fullWorkPath = function()
local ret = table.concat(os.workingDir, "/") -- there's nothing wrong with this.
if computer.verbose then
machine.println("workingDir size: "..#os.workingDir)
machine.println("fullWorkPath: "..ret)
end
return ret
end
os.setWorkingDir = function(s)
if s:byte(#s) == 47 then
s = string.sub(s, 1, #s - 1)
end
if s:byte(1) == 47 then
s = string.sub(s, 2, #s)
end
if computer.verbose then
machine.println("renew working dir; '"..s.."'")
end
local oldWorkingDir = {table.unpack(os.workingDir)}
-- renew working directory, EVEN IF s STARTS WITH '/'
local t = {}
for word in string.gmatch(s, "[^/]+") do
table.insert(t, word)
end
os.workingDir = t
-- check if the directory exists
if not fs.isDir(s) then
os.errorNoSuchFileOrDir("cd: "..s)
os.workingDir = oldWorkingDir
return
end
end
os.pushWorkingDir = function(s)
if (s == "..") then
error("cannot push '..' to working directory.")
else
table.insert(os.workingDir, s)
if computer.verbose then
machine.println("pushing '"..s.."' to working directory.")
end
end
end
os.popWorkingDir = function()
if (#os.workingDir > 1) then
table.remove(os.workingDir)
end
end
-- @param "path/of/arbitrary"
-- @return /working/dir/path/of/arbitrary
-- input path's trailing '/' is PRESERVED.
os.expandPath = function(p)
-- not applicable if the path starts with /
if p:byte(1) == 47 or p:byte(1) == 92 then
return p
end
return os.fullWorkPath().."/"..p
end
os.defaultshell = "/bin/dsh.lua"
os.clock = function() return machine.milliTime() / 1000 end -- uptime of the computer, in seconds
function os.errorCmdNotFound(cmd)
print(cmd..": command not found")
end
function os.errorNoSuchFile(cmd)
print(cmd..": No such file")
end
function os.errorNoSuchFileOrDir(cmd)
print(cmd..": No such file or directory")
end
function os.errorIsDir(cmd)
print(cmd.." is a directory")
end
-- run default shell
fs.dofile(os.defaultshell)
-- quit properly
shell.status = shell.halt

View File

@@ -0,0 +1,12 @@
NAME
msh - the Moonshell
SYNOPSIS
msh [file]
COPYRIGHT
See copyright information for the game you are actually playing.
DESCRIPTION
Msh is a Lua prompt that reads lua script from the user, or execute
a file user had put as an argument.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
--[[
From https://github.com/prapin/LuaBrainFuck/blob/master/brainfuck.lua
LuaBrainFuck License
--------------------
LuaBrainFuck is placed under the same license as Lua itself,
so licensed under terms of the MIT license reproduced below.
This means that the library is free software and can be used for both academic
and commercial purposes at absolutely no cost.
===============================================================================
Copyright (C) 2012 Patrick Rapin, CH-1543 Grandcour
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================
(end of COPYRIGHT)
Example usage: require "brainfuck" "+++>>> your BF code here <<<---"
]]
return function(s)
local subst = {["+"]="v=v+1 ", ["-"]="v=v-1 ", [">"]="i=i+1 ", ["<"]="i=i-1 ",
["."] = "w(v)", [","]="v=r()", ["["]="while v~=0 do ", ["]"]="end "}
local env = setmetatable({ i=0, t=setmetatable({},{__index=function() return 0 end}),
r=function() return io.read(1):byte() end, w=function(c) io.write(string.char(c)) end },
{__index=function(t,k) return t.t[t.i] end, __newindex=function(t,k,v) t.t[t.i]=v end })
load(s:gsub("[^%+%-<>%.,%[%]]+",""):gsub(".", subst), "brainfuck", "t", env)()
end

View File

@@ -0,0 +1,159 @@
--[[
-- ComputerCraft API compatibility layer
Usage: require("CCAPI")
Created by minjaesong on 2016-09-16.
--]]
--------------
-- PREAMBLE --
--------------
if term.isTeletype() then error("This is a teletype; cannot use CCAPI layer") end
table.insert(computer.loadedCLayer, "CCAPI")
local function intLog2(i)
if i == 0 then return 0 end
local log = 0
if bit32.band(i, 0xffff0000) ~= 0 then i = bit32.rshift(i, 16) log = 16 end
if i >= 256 then i = bit32.rshift(i, 8) log = log + 8 end
if i >= 16 then i = bit32.rshift(i, 8) log = log + 4 end
if i >= 4 then i = bit32.rshift(i, 8) log = log + 2 end
return log + bit32.rshift(i, 1)
end
-----------------------
-- BIT API Extension --
-----------------------
bit.blshift = bit32.lshift(n, bits)
bit.brshift = bit32.arshift(n, bits)
bit.blogic_rshift = bit32.rshift(n, bits)
----------------
-- COLORS API --
----------------
_G.colors = {}
colors.white = 0x1
colors.orange = 0x2
colors.magenta = 0x4
colors.lightBlue = 0x8
colors.yellow = 0x10
colors.lime = 0x20
colors.pink = 0x40
colors.gray = 0x80
colors.grey = 0x80
colors.lightGray = 0x100
colors.lightGrey = 0x100
colors.cyan = 0x200
colors.purple = 0x400
colors.blue = 0x800
colors.brown = 0x1000
colors.green = 0x2000
colors.red = 0x4000
colors.black = 0x8000
local function normaliseCCcol(cccol)
if cccol >= 0x1 and cccol <= 0xFFFF then
return intLog2(cccol)
else
error("invalid CC Colors: "..cccol)
end
end
_G.colours = _G.colors
--------------
-- TERM API --
--------------
-- paint_index -> Terminal colour index
local ccToGameCol = {--pink
1, 5, 7, 10, 4, 11, 15, 2, 3, 10, 8, 9, 14, 12, 6, 0
}
-- "a" -> 10, "3" -> 3
local function cHexToInt(c)
if type(c) == "number" then -- char
if c >= 48 and c <= 57 then
return c - 48
elseif c >= 65 and c <= 70 then
return c - 65 + 10
elseif c >= 97 and c <= 102 then
return c - 97 + 10
else
return 0
end
elseif type(c) == "string" then -- single-letter string
if c:byte(1) >= 48 and c:byte(1) <= 57 then
return c:byte(1) - 48
elseif c:byte(1) >= 65 and c:byte(1) <= 70 then
return c:byte(1) - 65 + 10
elseif c:byte(1) >= 97 and c:byte(1) <= 102 then
return c:byte(1) - 97 + 10
else
--error("unrepresentable: " .. c)
-- return black, as defined in http://www.computercraft.info/wiki/Term.blit
return 0
end
else
error("bad argument (string or number expected, got "..type(c)..")")
end
end
-- str, str, str
term.blit = function(text, foreCol, backCol)
assert(
type(text) == "string" and type(backCol) == "string" and type(foreCol) == "string",
"bad argument: (string, string, string expected, got "..type(text)..", "..type(foreCol)..", "..type(backCol)..")"
)
if #text ~= #foreCol or #text ~= #backCol or #foreCol ~= #backCol then
error("arguments must be the same length")
end
for i = 1, #text do
term.setForeCol(ccToGameCol[1 + cHexToInt(foreCol:byte(i))])
term.setBackCol(ccToGameCol[1 + cHexToInt(backCol:byte(i))])
term.emit(text:byte(i))
term.moveCursor(term.getX() + 1, term.getY())
end
end
term.getCursorPos = term.getCursor
term.setCursorPos = term.moveCursor
term.setCursorBlink = term.blink
term.isColor = term.isCol
term.getSize = term.size
term.setTextColor = function(cccol) term.setForeCol(ccToGameCol[normaliseCCcol(cccol)]) end
term.getTextColor = term.getForeCol
term.setBackgroundColor = function(cccol) term.setBackCol(ccToGameCol[normaliseCCcol(cccol)]) end
term.getBackgroundColor = term.getBackCol
--------------------
-- FILESYSTEM API --
--------------------
fs.makeDir = fs.mkdir
fs.move = fs.mv
fs.copy = fs.cp
fs.delete = fs.rm
fs.combine = fs.concat
fs.getDir = fs.parent
fs.run = fs.dofile
------------------
-- DOWN AND OUT --
------------------
if computer.verbose then print("ComputerCraft compatibility layer successfully loaded.") end

View File

@@ -0,0 +1,434 @@
--[[
Created by minjaesong on 2016-09-15.
--]]
-------------
-- ALIASES --
-------------
--_G.io = {} -- we make our own sandbox'd system
--[[fs.dofile = function(p, ...)
local f = fs.open(p, "r")
local s = f.readAll()
_G.runscript(s, "="..p, ...)
end]] -- implementation moved to BOOT.lua
_G.loadstring = _G.load
--_G.dofile = function(f) fs.dofile(f) end
fs.fetchText = function(p)
local file = fs.open(p, "r")
local text = file.readAll()
file.close()
return text
end
-----------------------------------------
-- INPUTSTREAM AND SCANNER (java-like) --
-----------------------------------------
--[[
In whatever code that actually runs everything (computer),
there must be:
override fun keyPressed(key: Int, c: Char) {
super.keyPressed(key, c)
vt.keyPressed(key, c)
if (key == Key.RETURN) {
val input = vt.closeInputString()
}
}
...it basically says to close the input if RETURN is hit,
and THIS exact part will close the input for this function.
]]
_G.__scanforline__ = function(echo) -- pass '1' to not echo; pass nothing to echo
machine.closeInputString()
machine.openInput(echo or 0)
_G.__scanMode__ = "line"
local s
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
s = machine.__readFromStdin()
until s
-- input is closed when RETURN is hit. See above comments.
return s
end
-- use Keys API to identify the keycode
--[[_G.__scanforkey__ = function(echo) -- pass '1' to not echo; pass nothing to echo
machine.closeInputString()
machine.openInput(echo or 0)
_G.__scanMode__ = "a_key"
local key
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
key = machine.getLastKeyPress()
until key
-- input is closed when any key is hit. See above comments.
return key
end]] -- DELETED: use _G.input.isKeyDown(keycode)
--- ---
-- IO IMPLEMENTATION --
--- ---
io.__openfile__ = "stdin"
io.stdin = "stdin"
io.stdout = "stdout"
io.stderr = "stderr"
io.open = fs.open
io.input = function(luafile)
io.__openfile__ = luafile
end
io.read = function(option)
if io.__openfile__ == "stdin" then
local input = {}
-- RETURN not hit
while true do
local inkey = machine.__readFromStdin()
if inkey == 13 or inkey == 10 then
break
elseif inkey == 8 or inkey == 127 then
io.write(string.char(inkey))
table.remove(input)
elseif inkey > 0 then
io.write(string.char(inkey))
table.insert(input, string.char(inkey))
end
end
-- RETURN finally hit
io.write("\n")
return table.concat(input)
end
function _readAll()
return io.open(io.__openfile__).readAll()
end
function _readLine()
return io.open(io.__openfile__).readLine()
end
options = {}
options["*n"] = function() error("Read number is not supported, yet!") end--_readNumber
options["*a"] = _readAll
options["*l"] = _readLine
end
-----------------
-- PRINTSTREAM --
-----------------
-- only useful when IO is "opening" stdin
--[[io.write = function(...)
local args = {...}
for _, v in ipairs(args) do
local s
if v == nil then
s = "nil"
else
s = tostring(v)
end
term.write(s)
end
end]]
-- for some reason, inputstream above kills 'print' function.
-- So we rewrite it.
--[[_G.print = function(...) -- dependent on above io.write reimpl
local args = {...}
io.write(args[1])
if (#args > 1) then
for i = 2, #args do
io.write("\t")
io.write(args[i])
end
end
io.write("\n")
end]]
---------------
-- SHELL API --
---------------
_G.shell = {}
shell.status = shell.ok
-- run a script with path (string) and argstable (table)
shell.run = function(path, argstable)
-- check for interpreter key "#!"
local f = fs.open(path, "r")
local s = f.readAll()
f.close()
if s:sub(1,2) == "#!" then
local interpreter = s:sub(3)
if not argstable then
xpcall(function() fs.dofile(interpreter..".lua", path) end, function(err) print(DLE..err) end)
else
xpcall(function() fs.dofile(interpreter..".lua", path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
end
else
if not argstable then
xpcall(function() fs.dofile(path) end, function(err) print(DLE..err) end)
else
xpcall(function() fs.dofile(path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
end
end
end
shell.ok = 0
shell.halt = 127
--------------
-- HEXUTILS --
--------------
_G.hexutils = {}
_G.hexutils.toHexString = function(byteString)
assert(type(byteString) == "string", error("Expected string."))
-- speedup
local function iToHex(i)
if i == 0 then return "0"
elseif i == 1 then return "1"
elseif i == 2 then return "2"
elseif i == 3 then return "3"
elseif i == 4 then return "4"
elseif i == 5 then return "5"
elseif i == 6 then return "6"
elseif i == 7 then return "7"
elseif i == 8 then return "8"
elseif i == 9 then return "9"
elseif i == 10 then return "a"
elseif i == 11 then return "b"
elseif i == 12 then return "c"
elseif i == 13 then return "d"
elseif i == 14 then return "e"
elseif i == 15 then return "f"
else error("unrepresentable: " .. i)
end
end
local ret = ""
for i = 1, #byteString do
local c = byteString:byte(i)
local msb = iToHex(bit32.rshift(c, 4) % 16)
local lsb = iToHex(c % 16)
ret = ret .. (msb .. lsb)
end
return ret
end
--------------
-- KEYS API --
--------------
-- ComputerCraft compliant
local keycodeNumToName = {
["30"] = "a",
["48"] = "b",
["46"] = "c",
["32"] = "d",
["18"] = "e",
["33"] = "f",
["34"] = "g",
["35"] = "h",
["23"] = "i",
["36"] = "j",
["37"] = "k",
["38"] = "l",
["50"] = "m",
["49"] = "n",
["24"] = "o",
["25"] = "p",
["16"] = "q",
["19"] = "r",
["31"] = "s",
["20"] = "t",
["22"] = "u",
["47"] = "v",
["17"] = "w",
["45"] = "x",
["21"] = "y",
["44"] = "z",
["2"] = "one",
["3"] = "two",
["4"] = "three",
["5"] = "four",
["6"] = "five",
["7"] = "six",
["8"] = "seven",
["9"] = "eight",
["10"] = "nine",
["11"] = "zero",
["12"] = "minus",
["13"] = "equals",
["14"] = "backspace",
["15"] = "tab",
["26"] = "leftBracket",
["27"] = "rightBracket",
["28"] = "enter",
["29"] = "leftCtrl",
["39"] = "semiColon",
["40"] = "apostrophe",
["41"] = "grave",
["42"] = "leftShift",
["43"] = "backslash",
["51"] = "comma",
["52"] = "period",
["53"] = "slash",
["54"] = "rightShift",
["55"] = "multiply",
["56"] = "leftAlt",
["57"] = "space",
["58"] = "capsLock",
["59"] = "f1",
["60"] = "f2",
["61"] = "f3",
["62"] = "f4",
["63"] = "f5",
["64"] = "f6",
["65"] = "f7",
["66"] = "f8",
["67"] = "f9",
["68"] = "f10",
["69"] = "numLock",
["70"] = "scollLock",
["87"] = "f11",
["88"] = "f12",
["89"] = "f13",
["90"] = "f14",
["91"] = "f15",
["144"] = "cimcumflex",
["145"] = "at",
["146"] = "colon",
["147"] = "underscore",
["157"] = "rightCtrl",
["184"] = "rightAlt",
["197"] = "pause",
["199"] = "home",
["200"] = "up",
["201"] = "pageUp",
["203"] = "left",
["205"] = "right",
["207"] = "end",
["208"] = "down",
["209"] = "pageDown",
["210"] = "insert",
["211"] = "delete",
["219"] = "leftCommand"
}
_G.keys = {
["a"] = 30,
["b"] = 48,
["c"] = 46,
["d"] = 32,
["e"] = 18,
["f"] = 33,
["g"] = 34,
["h"] = 35,
["i"] = 23,
["j"] = 36,
["k"] = 37,
["l"] = 38,
["m"] = 50,
["n"] = 49,
["o"] = 24,
["p"] = 25,
["q"] = 16,
["r"] = 19,
["s"] = 31,
["t"] = 20,
["u"] = 22,
["v"] = 47,
["w"] = 17,
["x"] = 45,
["y"] = 21,
["z"] = 44,
["one"] = 2,
["two"] = 3,
["three"] = 4,
["four"] = 5,
["five"] = 6,
["six"] = 7,
["seven"] = 8,
["eight"] = 9,
["nine"] = 10,
["zero"] = 11,
["minus"] = 12,
["equals"] = 13,
["backspace"] = 14,
["tab"] = 15,
["leftBracket"] = 26,
["rightBracket"] = 27,
["enter"] = 28,
["leftCtrl"] = 29,
["semiColon"] = 39,
["apostrophe"] = 40,
["grave"] = 41,
["leftShift"] = 42,
["backslash"] = 43,
["comma"] = 51,
["period"] = 52,
["slash"] = 53,
["rightShift"] = 54,
["multiply"] = 55,
["leftAlt"] = 56,
["space"] = 57,
["capsLock"] = 58,
["f1"] = 59,
["f2"] = 60,
["f3"] = 61,
["f4"] = 62,
["f5"] = 63,
["f6"] = 64,
["f7"] = 65,
["f8"] = 66,
["f9"] = 67,
["f10"] = 68,
["numLock"] = 69,
["scollLock"] = 70,
["f11"] = 87,
["f12"] = 88,
["f13"] = 89,
["f14"] = 90,
["f15"] = 91,
["cimcumflex"] = 144,
["at"] = 145,
["colon"] = 146,
["underscore"] = 147,
["rightCtrl"] = 157,
["rightAlt"] = 184,
["pause"] = 197,
["home"] = 199,
["up"] = 200,
["pageUp"] = 201,
["left"] = 203,
["right"] = 205,
["end"] = 207,
["down"] = 208,
["pageDown"] = 209,
["insert"] = 210,
["delete"] = 211,
["leftCommand"] = 219
}
_G.keys.getName = function(code) return keycodeNumToName[tostring(code)] end

View File

@@ -0,0 +1,497 @@
--[[
TBASIC: Simple BASIC language based on the Commodore BASIC Version 2.
(C64 rulz? Nope.)
How to use in your program:
1. load the script by:
if you're using ComputerCraft, use:
os.loadAPI "TBASEXEC.lua"
else, use:
require "TBASEXEC"
2. run:
_TBASIC.EXEC(string of whole command)
]]
if os and os.loadAPI then -- ComputerCraft
os.loadAPI "TBASINCL.lua"
else
require "TBASINCL"
end
table.concat = function(t, delimeter)
if #t == 0 then return "" end
local outstr = t[1]
for i = 2, #t do
outstr = outstr..delimeter..tostring(t[i])
end
return outstr
end
-- Copy from TBASINCL; looks like OpenComputers has a bug...
function string_hash(str)
local hash = 2166136261
for i = 1, #str do
hash = hash * 16777619
hash = bit.bxor(hash, str:byte(i))
end
return hash
end
-- INTERPRETER STATUS ---------------------------------------------------------
local programlist = {}
-- LEXER ----------------------------------------------------------------------
local function appendcommand(lineno, statement)
if lineno > _TBASIC._INTPRTR.MAXLINES then
_TBASIC._ERROR.LINETOOBIG()
elseif lineno < 0 then
_TBASIC._ERROR.NOLINENUM()
else
programlist[lineno] = statement
end
end
do -- Avoid heap allocs for performance
local tokens = {" ", "\t", ",", "(", ")"} -- initial obvious tokens
local longest_token_len = 0
-- build 'tokens' table from list of operators from the language
for _, v in ipairs(_TBASIC._OPERATR) do
if not v:match("[A-Za-z]") then -- we want non-alphabetic operators as a token
table.insert(tokens, v)
-- get longest_token_len, will be used for 'lookahead'
local tokenlen = #v
if longest_token_len < #v then
longest_token_len = #v
end
end
end
-- sort them out using ther hash for binary search
table.sort(tokens, function(a, b) return string_hash(a) < string_hash(b) end)
function parsewords(line)
if line == nil then return end
-----------------------
-- check line sanity --
-----------------------
-- filter for IF statement
if line:sub(1, 2):upper() == "IF" then
-- no matching THEN
if not line:match("[Tt][Hh][Ee][Nn]") then
_TBASIC._ERROR.NOMATCHING("IF", "THEN")
-- assignment on IF clause
elseif line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match("[^=+%-*/%%<>!]=[^=<>]") or
line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match(":=") then
_TBASIC._ERROR.ASGONIF()
end
end
--------------------------------------------------
-- automatically infer and insert some commands --
--------------------------------------------------
-- (This is starting to get dirty...)
-- unary minus
for matchobj in line:gmatch("%-[0-9]+") do
local newline = line:gsub(matchobj, "MINUS "..matchobj:sub(2, #matchobj))
line = newline
end
-- conditional for IF
-- if IF statement has no appended paren
if line:sub(1, 2):upper() == "IF" and not line:match("[Ii][Ff][ ]*%(") then
local newline = line:gsub("[Ii][Ff]", "IF ( ", 1):gsub("[Tt][Hh][Ee][Nn]", " ) THEN", 1)
line = newline
end
-- special treatment for FOR
if line:sub(1, 3):upper() == "FOR" then
if line:match("[0-9]?%.[0-9]") then -- real number used (e.g. "3.14", ".5")
_TBASIC._ERROR.ILLEGALARG()
else
local varnameintm = line:match(" [^\n]+[ =]")
if varnameintm then
local varname = varnameintm:match("[^= ]+")
if varname then
local newline = line:gsub(" "..varname.."[ =]", " $"..varname.." "..varname.." = ")
line = newline:gsub("= =", "=")
else
_TBASIC._ERROR.SYNTAX()
end
end
-- basically, "FOR x x = 1 TO 10", which converts to "x x 1 10 TO = FOR",
-- which is executed (in RPN) in steps of:
-- "x x 1 10 TO = FOR"
-- "x x (arr) = FOR"
-- "x FOR" -- see this part? we need extra 'x' to feed for the FOR statement to function
end
end
printdbg("parsing line", line)
lextable = {}
isquote = false
quotemode = false
wordbuffer = ""
local function flush()
if (#wordbuffer > 0) then
table.insert(lextable, wordbuffer)
wordbuffer = ""
end
end
local function append(char)
wordbuffer = wordbuffer..char
end
local function append_no_whitespace(char)
if char ~= " " and char ~= "\t" then
wordbuffer = wordbuffer..char
end
end
-- return: lookless_count on success, nil on failure
local function isdelimeter(string)
local cmpval = function(table_elem) return string_hash(table_elem) end
local lookless_count = #string
local ret = nil
repeat
ret = table.binsearch(tokens, string:sub(1, lookless_count), cmpval)
lookless_count = lookless_count - 1
until ret or lookless_count < 1
return ret and lookless_count + 1 or false
end
local i = 1 -- Lua Protip: variable in 'for' is immutable, and is different from general variable table, even if they have same name
while i <= #line do
local c = string.char(line:byte(i))
local lookahead = line:sub(i, i+longest_token_len)
if isquote then
if c == [["]] then
flush()
isquote = false
else
append(c)
end
else
if c == [["]] then
isquote = true
append_no_whitespace("~")
else
local delimsize = isdelimeter(lookahead) -- returns nil if no matching delimeter found
if delimsize then
flush() -- flush buffer
append_no_whitespace(lookahead:sub(1, delimsize))
flush() -- flush this delimeter
i = i + delimsize - 1
else
append_no_whitespace(c)
end
end
end
i = i + 1
end
flush() -- don't forget this!
return lextable
end
end
local function readprogram(program)
for line in program:gmatch("[^\n]+") do
lineno = line:match("[0-9]+ ", 1)
if not lineno then
_TBASIC._ERROR.NOLINENUM()
end
statement = line:sub(#lineno + 1)
appendcommand(tonumber(lineno), statement)
end
end
do -- Avoid heap allocs for performance
local function stackpush(t, v)
t[#t + 1] = v
end
local function stackpop(t)
local v = t[#t]
t[#t] = nil
return v
end
local function stackpeek(t)
local v = t[#t]
return v
end
local function unmark(word)
if type(word) == "table" then return word end
return word:sub(2, #word)
end
local function isoperator(word)
if word == nil then return false end
return word:byte(1) == 35
end
local isvariable = _TBASIC.isvariable
local isnumber = _TBASIC.isnumber
local isstring = _TBASIC.isstring
local function isuserfunc(word)
if type(word) == "table" then return false end
if word == nil then return false end
return word:byte(1) == 64
end
local function isbuiltin(word)
if type(word) == "table" then return false end
if word == nil then return false end
return word:byte(1) == 38
end
local function iskeyword(word)
if word == nil then return false end
return isoperator(word) or isuserfunc(word) or isbuiltin(word)
end
local function isassign(word)
if word == nil then return false end
return word ~= "==" and word ~= ">=" and word ~= "<=" and word:byte(#word) == 61
end
-- returns truthy value "terminate_loop" upon termination of loop; nil otherwise.
local function execword(word, args)
if not _TBASIC.__appexit then
printdbg("--> execword", word)
printdbg("--> execword_args", table.unpack(args))
if word == "IF" then
printdbg("--> branch statement 'IF'")
if not _TBASIC.__readvar(args[1]) then -- if condition 'false'
printdbg("--> if condition 'false'", table.unpack(args))
return "terminate_loop" -- evaluated as 'true' to Lua
else
printdbg("--> if condition 'true'", table.unpack(args))
end
end
printdbg("--> execword_outarg", table.unpack(args))
result = _TBASIC.LUAFN[word][1](table.unpack(args))
printdbg("--> result", result)
stackpush(execstack, result)
end
end
function printdbg(...)
local debug = false
if debug then print("DBG", ...) end
end
function interpretline(line)
if not _TBASIC.__appexit then
--[[
impl
1. (normalise expr using parsewords)
2. use _TBASIC.RPNPARSR to convert to RPN
3. execute RPN op set like FORTH
* "&" - internal functions
* "@" - user-defined functions
* "$" - variables (builtin constants and user-defined) -- familiar, eh?
* "#" - operators
* "~" - strings
* none prepended - data (number or string)
]]
lextable = parsewords(line)
local vararg = -13 -- magic
if lextable and lextable[1] ~= nil then
if lextable[1]:upper() == "REM" then return nil end
printdbg("lextable", table.concat(lextable, "|"))
-- execute expression
exprlist = _TBASIC.TORPN(lextable) -- 2 2 #+ &PRINT for "PRINT 2+2"
printdbg("trying to exec", table.concat(exprlist, " "), "\n--------")
execstack = {}
for _, word in ipairs(exprlist) do
printdbg("stack before", table.concat(execstack, " "))
printdbg("word", word)
if iskeyword(word) then
printdbg("is keyword")
funcname = unmark(word)
args = {}
argsize = _TBASIC._GETARGS(funcname)
printdbg("argsize", argsize)
if not argsize then
_TBASIC._ERROR.DEV_UNIMPL(funcname)
else
if argsize ~= vararg then
-- consume 'argsize' elements from the stack
for argcnt = argsize, 1, -1 do
if #execstack == 0 then
_TBASIC._ERROR.ARGMISSING(funcname)
end
args[argcnt] = stackpop(execstack)
end
else
-- consume entire stack
local reversedargs = {}
while #execstack > 0 and
(isvariable(stackpeek(execstack)) or isnumber(stackpeek(execstack)) or
isstring(stackpeek(execstack)))
do
stackpush(reversedargs, stackpop(execstack))
end
-- reverse 'args'
while #reversedargs > 0 do
stackpush(args, stackpop(reversedargs))
end
end
local terminate_loop = execword(funcname, args)
if terminate_loop then
printdbg("--> termination of loop")
printdbg("--------")
break
end
end
elseif isvariable(word) then
printdbg("is variable")
stackpush(execstack, word) -- push raw variable ($ sign retained)
else
printdbg("is data")
stackpush(execstack, word) -- push number or string
end
printdbg("stack after", table.concat(execstack, " "))
printdbg("--------")
end
-- if execstack is not empty, something is wrong
if #execstack > 0 then
_TBASIC._ERROR.SYNTAX() -- cannot reliably pinpoint which statement has error; use generic error
end
end
end
end
end
local function termination_condition()
return terminated or
_TBASIC.__appexit or
#_TBASIC._INTPRTR.CALLSTCK > _TBASIC._INTPRTR.STACKMAX
end
local function fetchnextcmd()
cmd = nil
repeat
_TBASIC._INTPRTR.PROGCNTR = _TBASIC._INTPRTR.PROGCNTR + 1
cmd = programlist[_TBASIC._INTPRTR.PROGCNTR]
if _TBASIC._INTPRTR.PROGCNTR > _TBASIC._INTPRTR.MAXLINES then
terminated = true
break
end
until cmd ~= nil
if cmd ~= nil then
if _TBASIC._INTPRTR.TRACE then
print("PC", _TBASIC._INTPRTR.PROGCNTR)
end
return cmd
end
end
local function interpretall()
terminated = false
repeat
interpretline(fetchnextcmd())
until termination_condition()
end
-- END OF LEXER ---------------------------------------------------------------
-- _TBASIC.SHOWLUAERROR = false -- commented; let the shell handle it
local testprogram = nil
_G._TBASIC.EXEC = function(cmdstring) -- you can access this interpreter with this global function
_TBASIC._INTPRTR.RESET()
programlist = {} -- wipe out previous commands from interpreter (do not delete)
readprogram(cmdstring)
interpretall()
end
if testprogram then
_TBASIC._INTPRTR.RESET()
programlist = {} -- wipe out previous commands from interpreter (do not delete)
readprogram(testprogram)
interpretall()
end
--[[
Terran BASIC (TBASIC)
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the Software), to deal in the
Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

View File

@@ -0,0 +1,34 @@
-- TBASIC extension
-- these are utilities. Do not touch these lines
local __assert = _TBASIC.__assert
local __assertlhand = _TBASIC.__assertlhand
local __assertrhand = _TBASIC.__assertrhand
local __checknumber = _TBASIC.__checknumber
local __checkstring = _TBASIC.__checkstring
local __readvar = _TBASIC.__readvar
local __resolvevararg = _TBASIC.__resolvevarar
local vararg = -13 -- magic
-- end of utilities
-- these are the sample code for defining your own words
--[[
-- actual function that does the job
local function _fnupgoer(n)
print("Up-goer "..__checknumber(n).." goes up!")
end
-- add the word UPGOER to word list
table.insert(_TBASIC._FNCTION, "UPGOER")
-- add the actual function '_fnupgoer' and its number of arguments (1) to
-- '_TBASIC.LUAFN'. 'UPGOER' part should match with the word you just
-- inserted to _TBASIC._FNCTION.
_TBASIC.LUAFN.UPGOER = {_fnupgoer, 1}
]]
-- little debugger's blessing
local function _fnenableluatrace() _TBASIC.SHOWLUAERROR = true end
table.insert(_TBASIC._FNCTION, "LUATRACEON")
_TBASIC.LUAFN.LUATRACEON = {_fnenableluatrace, 0}

View File

@@ -0,0 +1,292 @@
--[[
TBASIC shell
Synopsis: TBASIC (filename)
If no file is specified, interactive mode will be started
To debug EXEC and/or INCL, there's line ```local debug = false``` on each file; change it to ```true``` manually
and you are all set.
]]
if os and os.loadAPI then -- ComputerCraft
os.loadAPI "TBASINCL.lua"
os.loadAPI "TBASEXEC.lua"
else
require "TBASINCL"
require "TBASEXEC"
end
args = {...}
print(_G._TBASIC._HEADER)
_TBASIC.PROMPT()
_TBASIC.SHOWLUAERROR = false
local function concat_lines(lines, startindex, endindex)
local out = ""
for i = startindex or 1, endindex or _TBASIC._INTPRTR.MAXLINES do
if lines[i] ~= nil then
out = out.."\n"..tostring(i).." "..lines[i]
end
end
return out
end
if args[1] then
local prog = nil
if fs and fs.open then -- ComputerCraft
local inp = assert(fs.open(args[1], "r"))
prog = inp:readAll()
inp:close()
else
local inp = assert(io.open(args[1], "r"))
prog = inp:read("*all")
inp:close()
end
_TBASIC.EXEC(prog)
else
local terminate_app = false
local ptn_nums = "[0-9]+"
local renum_targets = {"GOTO[ ]+"..ptn_nums, "GOSUB[ ]+"..ptn_nums }
local lines = {}
local linenum_match = "[0-9]+ "
local get_linenum = function(line) return line:sub(1,6):match(linenum_match, 1) end -- line:sub(1,6) limits max linumber to be 99999
local split_num_and_statements = function(line)
local linenum = get_linenum(line)
local statements = line:sub(#linenum + 1)
return tonumber(linenum), statements
end
while not terminate_app do
local __read = false
local line = io.read()
-- tokenise line by " "
local args = {} -- shadows system args
for word in line:gmatch("[^ ]+") do
table.insert(args, word:upper())
end
-- TODO more elegant code than IF-ELSEIF-ELSE
-- massive if-else for running command, cos implementing proper command executor is too expensive here
if line:sub(1,6):match(linenum_match) then -- enter new command
local linenum, statements = split_num_and_statements(line)
lines[tonumber(linenum)] = statements
__read = true
elseif args[1] == "NEW" then
lines = {}
elseif args[1] == "RUN" then
_TBASIC.EXEC(concat_lines(lines))
elseif args[1] == "LIST" then -- LIST, LIST 42, LIST 10-80
if not args[2] then
print(concat_lines(lines))
else
if args[2]:match("-") then -- ranged
local range = {}
for n in args[2]:gmatch("[^-]+") do
table.insert(range, n)
end
local rangestart = tonumber(range[1])
local rangeend = tonumber(range[2])
if not rangestart or not rangeend then
_TBASIC._ERROR.ILLEGALARG()
else
print(concat_lines(lines, rangestart, rangeend))
end
else
local linenum = tonumber(args[2])
if not linenum then
_TBASIC._ERROR.ILLEGALARG()
else
print(concat_lines(lines, linenum, linenum))
end
end
end
_TBASIC.PROMPT()
__read = true
elseif args[1] == "DELETE" then -- DELETE 30, DELETE 454-650
if not args[2] then
_TBASIC._ERROR.ILLEGALARG()
else
if args[2]:match("-") then -- ranged
local range = {}
for n in args[2]:gmatch("[^-]+") do
table.insert(range, n)
end
local rangestart = tonumber(range[1])
local rangeend = tonumber(range[2])
if not rangestart or not rangeend then
_TBASIC._ERROR.ILLEGALARG()
else
for i = rangestart, rangeend do
lines[i] = nil
end
end
else
local linenum = tonumber(args[2])
if not linenum then
_TBASIC._ERROR.ILLEGALARG()
else
lines[linenum] = nil
end
end
end
elseif args[1] == "EXIT" then
terminate_app = true
break
elseif args[1] == "SAVE" then
local status, err = pcall(function()
if fs and fs.open then -- computercraft
local file = fs.open(args[2], "w")
file.write(concat_lines(lines))
file.close()
else
local file = assert(io.open(args[2], "w"))
file:write(concat_lines(lines))
file:close()
end
end
)
if err then
if _TBASIC.SHOWLUAERROR then
print(err)
end
_TBASIC._ERROR.IOERR()
else
print("FILE SAVED")
end
elseif args[1] == "LOAD" then
local status, err = pcall(function()
lines = {}
if fs and fs.open then -- computercraft
local file = fs.open(args[2], "r")
local data = file.readAll("*all")
for dataline in data:gmatch("[^\n]+") do
if #dataline > 0 then
local linenum, statements = split_num_and_statements(dataline)
lines[linenum] = statements
end
end
file.close()
else
local file = assert(io.open(args[2], "r"))
local data = file:read("*all")
for dataline in data:gmatch("[^\n]+") do
if #dataline > 0 then
local linenum, statements = split_num_and_statements(dataline)
lines[linenum] = statements
end
end
file:close()
end
end
)
if err then
if _TBASIC.SHOWLUAERROR then
error(err)
end
_TBASIC._ERROR.IOERR()
else
print("FILE LOADED")
end
elseif args[1] == "RENUM" then
local statement_table = {}
local renumbering_table = {}
local new_linenum_counter = 10
-- first, get the list of commands, without line number indexing
for i = 1, _TBASIC._INTPRTR.MAXLINES do
if lines[i] ~= nil then
--table.insert(statement_table, lines[i])
statement_table[new_linenum_counter] = lines[i]
renumbering_table[i] = new_linenum_counter
-- test
--print("old line", i, "new line", new_linenum_counter)
new_linenum_counter = new_linenum_counter + 10
end
end
-- copy statement_table into lines table
lines = statement_table
-- re-number GOTO and GOSUB line numbers
local line_counter = 0 -- loop counter
for line_pc = 0, _TBASIC._INTPRTR.MAXLINES do
local line = lines[line_pc]
if line then
line_counter = line_counter + 1
-- replace
-- extract a <- "GOTO 320"
-- extract n_from from a (320), make n_to from it
-- make new string b <- "GOTO "..n_to
for _, match_string in ipairs(renum_targets) do
local match = line:match(match_string)
if match then
local matching_statement = match:gsub("[ ]+"..ptn_nums, "")
local target_line_old = tonumber(match:match(ptn_nums))
local target_line_new = renumbering_table[target_line_old]
local gsub_from = match
local gsub_to = matching_statement.." "..target_line_new
-- test
--print("matching_statement", matching_statement, "target_line_old", target_line_old, "target_line_new", target_line_new)
--print("gsub_from", gsub_from, "gsub_to", gsub_to)
-- substitute
lines[line_pc] = line:gsub(gsub_from, gsub_to)
end
end
end
end
elseif #line == 0 and line:byte(1) ~= 10 and line:byte(1) ~= 13 then
__read = true
else
_TBASIC.EXEC("1 "..line) -- execute command right away
end
-- reset
if not __read then
_TBASIC.PROMPT()
end
end
end
--[[
Terran BASIC (TBASIC)
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the Software), to deal in the
Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
// TODO Fill in from work_files/romapidoc/romapidoc.tex

View File

@@ -0,0 +1,585 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.computer
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaError
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import org.luaj.vm2.lib.jse.JsePlatform
import net.torvald.terrarum.KVHashMap
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.Second
import net.torvald.terrarum.ceilInt
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk
import net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject.ComputerPartsCodex
import org.lwjgl.BufferUtils
import org.lwjgl.openal.AL10
import java.io.*
import java.nio.ByteBuffer
import java.util.*
import java.util.logging.Level
import kotlin.collections.HashMap
/**
* A part that makes "computer fixture" actually work
*
* @param avFixtureComputer : actor values for FixtureComputerBase
*
* @param term : terminal that is connected to the computer fixtures, null if not connected any.
* Created by minjaesong on 2016-09-10.
*/
class TerrarumComputer(peripheralSlots: Int) {
val DEBUG_UNLIMITED_MEM = false
val DEBUG = true
val maxPeripherals: Int = if (DEBUG) 32 else peripheralSlots
lateinit var luaJ_globals: Globals
private set
var stdout: PrintStream? = null
private set
var stderr: PrintStream? = null
private set
var stdin: InputStream? = null
private set
val processorCycle: Int // number of Lua statement to process per tick (1/100 s)
get() = ComputerPartsCodex.getProcessorCycles(computerValue.getAsInt("processor") ?: 0)
val memSize: Int // in bytes; max: 8 GB
get() {
if (DEBUG_UNLIMITED_MEM) return 16.shl(20)// 16 MB
var size = 0
for (i in 0..3)
size += ComputerPartsCodex.getRamSize(computerValue.getAsInt("memSlot$i")!!)
return size
}
val UUID = java.util.UUID.randomUUID().toString()
val computerValue = KVHashMap()
var isHalted = false
lateinit var term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
private set
val peripheralTable = Array<net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral?>(peripheralSlots, { null }) // index == slot number
var stdinInput: Int = -1
private set
// os-related functions. These are called "machine" library-wise.
private val startupTimestamp: Long = System.currentTimeMillis()
/** Time elapsed since the power is on. */
val milliTime: Int
get() = (System.currentTimeMillis() - startupTimestamp).toInt()
/** String:
* if it's UUID, formatted UUID as string, always 36 chars
* if not (test purpose only!), just String
*/
val diskRack = HashMap<String, VirtualDisk>()
fun attachDisk(slot: String, filename: String) {
computerValue[slot] = filename
// put disk in diskRack
if (filename.isNotEmpty() && filename.isNotBlank()) {
diskRack[slot] = VDUtil.readDiskArchive(
File(Terrarum.currentSaveDir.path + "/computers/$filename").absoluteFile,
Level.WARNING,
{ },
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem.sysCharset
)
}
}
init {
computerValue["memslot0"] = 4864 // -1 indicates mem slot is empty
computerValue["memslot1"] = -1 // put index of item here
computerValue["memslot2"] = -1 // ditto.
computerValue["memslot3"] = -1 // do.
computerValue["processor"] = -1 // do.
// as in "dev/hda"; refers hard disk drive (and no partitioning)
attachDisk("hda", "uuid_testhda")
attachDisk("hdb", "")
attachDisk("hdc", "")
attachDisk("hdd", "")
// as in "dev/fd1"; refers floppy disk drive
attachDisk("fd1", "")
attachDisk("fd2", "")
attachDisk("fd3", "")
attachDisk("fd4", "")
// SCSI connected optical drive
attachDisk("sda", "")
// boot device
computerValue["boot"] = "hda"
}
fun getPeripheral(tableName: String): net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral? {
peripheralTable.forEach {
if (it?.tableName == tableName)
return it
}
return null
}
fun getPeripheralSlot(tableName: String): Int? {
peripheralTable.forEachIndexed { index, peri ->
if (peri?.tableName == tableName)
return index
}
return null
}
/** @return installed slot */
fun attachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral): Int {
(0..maxPeripherals - 1).forEach {
try {
attachPeripheralTo(peri, it)
return it
}
catch (tryNext: RuntimeException) { }
}
throw RuntimeException("No vacant peripheral slot")
}
fun attachPeripheralTo(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral, slot: Int) {
if (peripheralTable[slot] == null) {
peripheralTable[slot] = peri
peri.loadLib(luaJ_globals)
println("[TerrarumComputer] loading peripheral $peri")
}
else {
throw RuntimeException("Peripheral slot is already taken by: ${peripheralTable[slot]?.tableName}")
}
}
fun detachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral) {
// search for the peripheral
var found = -1
for (i in 0..maxPeripherals - 1) {
if (peripheralTable[i] == peri) {
found = i
break
}
}
if (found >= 0) {
peripheralTable[found] = null
println("[TerrarumComputer] unloading peripheral $peri")
}
else {
throw IllegalArgumentException("Peripheral not exists: $peri")
}
}
fun attachTerminal(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
this.term = term
initSandbox(term)
}
fun initSandbox(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
luaJ_globals = JsePlatform.debugGlobals()
stdout = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
stderr = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
stdin = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalInputStream(this)
luaJ_globals.STDOUT = stdout
luaJ_globals.STDERR = stderr
luaJ_globals.STDIN = stdin
luaJ_globals["bit"] = luaJ_globals["bit32"]
// load libraries
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term(luaJ_globals, term)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Security(luaJ_globals)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem(luaJ_globals, this)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.HostAccessProvider(luaJ_globals, this)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Input(luaJ_globals, this)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.PcSpeakerDriver(luaJ_globals, this)
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.WorldInformationProvider(luaJ_globals)
// secure the sandbox
//luaJ_globals["io"] = LuaValue.NIL
// dubug should be sandboxed in BOOT.lua (use OpenComputers code)
//val sethook = luaJ_globals["debug"]["sethook"]
//luaJ_globals["debug"] = LuaValue.NIL
// ROM BASIC
val inputStream = javaClass.getResourceAsStream("/net/torvald/terrarum/modulecomputers/virtualcomputer/virtualcomputer/assets/lua/BOOT.lua")
runCommand(InputStreamReader(inputStream), "=boot")
// computer-related global functions
luaJ_globals["totalMemory"] = LuaFunGetTotalMem(this)
luaJ_globals["computer"] = LuaTable()
// rest of the "computer" APIs should be implemented in BOOT.lua
// load every peripheral if we're in DEBUG
if (DEBUG) {
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralInternet(this))
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralPSG(this))
// ...
}
}
fun update(delta: Float) {
if (currentExecutionThread.state == Thread.State.TERMINATED) {
threadRun = false
}
if (!isHalted) {
runBeepQueueManager(delta)
}
}
fun keyPressed(key: Int, c: Char) {
stdinInput = c.toInt()
// wake thread
runnableRunCommand.resume()
synchronized(stdin!!) {
(stdin as java.lang.Object).notifyAll()
}
}
fun openStdin() {
stdinInput = -1
// sleep the thread
runnableRunCommand.pause()
}
lateinit var currentExecutionThread: Thread
private set
lateinit var runnableRunCommand: ThreadRunCommand
private set
private var threadRun = false
fun runCommand(line: String, env: String) {
if (!threadRun) {
runnableRunCommand = ThreadRunCommand(luaJ_globals, line, env)
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
currentExecutionThread.start()
threadRun = true
}
}
fun runCommand(reader: Reader, filename: String) {
if (!threadRun) {
runnableRunCommand = ThreadRunCommand(luaJ_globals, reader, filename)
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
currentExecutionThread.start()
threadRun = true
}
}
class ThreadRunCommand : Runnable {
private val mode: Int
private val arg1: Any
private val arg2: String
private val lua: Globals
@Volatile private var running = true
@Volatile private var paused = false
private val pauseLock = java.lang.Object()
constructor(luaInstance: Globals, line: String, env: String) {
mode = 0
arg1 = line
arg2 = env
lua = luaInstance
}
constructor(luaInstance: Globals, reader: Reader, filename: String) {
mode = 1
arg1 = reader
arg2 = filename
lua = luaInstance
}
override fun run() {
synchronized(pauseLock) {
if (!running) { // may have changed while waiting to
// synchronize on pauseLock
return
}
if (paused) {
try {
pauseLock.wait() // will cause this Thread to block until
// another thread calls pauseLock.notifyAll()
// Note that calling wait() will
// relinquish the synchronized lock that this
// thread holds on pauseLock so another thread
// can acquire the lock to call notifyAll()
// (link with explanation below this code)
}
catch (ex: InterruptedException) {
return
}
if (!running) { // running might have changed since we paused
return
}
}
}
try {
val chunk: LuaValue
if (mode == 0)
chunk = lua.load(arg1 as String, arg2)
else if (mode == 1)
chunk = lua.load(arg1 as Reader, arg2)
else
throw IllegalArgumentException("Unsupported mode: $mode")
chunk.call()
}
catch (e: LuaError) {
e.printStackTrace(System.err)
//lua.STDERR.println("${SimpleTextTerminal.ASCII_DLE}${e.message}${SimpleTextTerminal.ASCII_DC4}")
}
}
fun stop() {
running = false
// you might also want to do this:
//interrupt()
}
fun pause() {
// you may want to throw an IllegalStateException if !running
paused = true
}
fun resume() {
synchronized(pauseLock) {
paused = false
pauseLock.notifyAll() // Unblocks thread
}
}
}
class LuaFunGetTotalMem(val computer: TerrarumComputer) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(computer.memSize)
}
}
class ComputerEmitTone(val computer: TerrarumComputer) : TwoArgFunction() {
override fun call(millisec: LuaValue, freq: LuaValue): LuaValue {
computer.playTone(millisec.checkdouble().toFloat(), freq.checkdouble())
return LuaValue.NONE
}
}
///////////////////
// BEEPER DRIVER //
///////////////////
private val beepMaxLen = 10f
// let's regard it as a tracker...
private val beepQueue = ArrayList<Pair<Second, Double>>()
private var beepCursor = -1
private var beepQueueLineExecTimer: Second = 0f
private var beepQueueFired = false
private fun runBeepQueueManager(delta: Float) {
// start emitTone queue
if (beepQueue.size > 0 && beepCursor == -1) {
beepCursor = 0
}
// advance emitTone queue
if (beepCursor >= 0 && beepQueueLineExecTimer >= beepQueueGetLenOfPtn(beepCursor)) {
beepQueueLineExecTimer -= beepQueueGetLenOfPtn(beepCursor)
beepCursor += 1
beepQueueFired = false
}
// complete emitTone queue
if (beepCursor >= beepQueue.size) {
clearBeepQueue()
}
// actually play queue
if (beepCursor >= 0 && beepQueue.size > 0 && !beepQueueFired) {
playTone(beepQueue[beepCursor].first, beepQueue[beepCursor].second)
beepQueueFired = true
// delete sources that is finished. AL is limited to 256 sources. If you exceed it,
// we won't get any more sounds played.
AL10.alSourcei(oldBeepSource, AL10.AL_BUFFER, 0)
AL10.alDeleteSources(oldBeepSource)
AL10.alDeleteBuffers(oldBeepBuffer)
}
if (beepQueueFired) beepQueueLineExecTimer += delta
}
fun clearBeepQueue() {
beepQueue.clear()
beepCursor = -1
beepQueueLineExecTimer = 0f
//AL.destroy()
if (DEBUG) println("[TerrarumComputer] !! Beep queue clear")
}
fun enqueueBeep(duration: Double, freq: Double) {
beepQueue.add(Pair(Math.min(duration.toFloat(), beepMaxLen), freq))
}
fun beepQueueGetLenOfPtn(ptnIndex: Int) = beepQueue[ptnIndex].first
////////////////////
// TONE GENERATOR //
////////////////////
private val sampleRate = 44100
private var beepSource: Int = -1
private var beepBuffer: Int = -1
private var oldBeepSource: Int = -1
private var oldBeepBuffer: Int = -1
var audioData: ByteBuffer? = null
/**
* @param duration : milliseconds
* @param rampUp
* @param rampDown
*
* ,---. (true, true) ,---- (true, false) ----. (false, true) ----- (false, false)
*/
private fun makeAudioData(duration: Second, freq: Double,
rampUp: Boolean = true, rampDown: Boolean = true): ByteBuffer {
TODO("with duration as Seconds")
val audioDataSize = duration.times(sampleRate).ceilInt()
val audioData = BufferUtils.createByteBuffer(audioDataSize)
/*val realDuration = duration * sampleRate / 1000
val chopSize = freq / sampleRate
val amp = Math.max(4600.0 / freq, 1.0)
val nHarmonics = if (freq >= 22050.0) 1
else if (freq >= 11025.0) 2
else if (freq >= 5512.5) 3
else if (freq >= 2756.25) 4
else if (freq >= 1378.125) 5
else if (freq >= 689.0625) 6
else 7
val transitionThre = 974.47218
// TODO volume ramping?
if (freq == 0.0) {
for (_ in 0..audioDataSize - 1) {
audioData.put(0x00.toByte())
}
}
else if (freq < transitionThre) { // chopper generator (for low freq)
for (tsart in 0..audioDataSize - 1) {
var sine: Double = amp * Math.cos(Math.PI * 2 * () * chopSize)
if (sine > 0.79) sine = 0.79
else if (sine < -0.79) sine = -0.79
audioData.put(
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
)
}
}
else { // harmonics generator (for high freq)
for (x in 0..realDuration - 1) {
var sine: Double = 0.0
for (k in 1..nHarmonics) { // mix only odd harmonics in order to make a squarewave
sine += Math.sin(Math.PI * 2 * (2*k - 1) * chopSize * x) / (2*k - 1)
}
audioData.put(
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
)
}
}*/
audioData.rewind()
return audioData
}
private fun playTone(length: Second, freq: Double) {
/*audioData = makeAudioData(leninmilli, freq)
if (!AL.isCreated()) AL.create()
// Clear error stack.
AL10.alGetError()
oldBeepBuffer = beepBuffer
beepBuffer = AL10.alGenBuffers()
checkALError()
try {
AL10.alBufferData(beepBuffer, AL10.AL_FORMAT_MONO8, audioData, sampleRate)
checkALError()
oldBeepSource = beepSource
beepSource = AL10.alGenSources()
checkALError()
try {
AL10.alSourceQueueBuffers(beepSource, beepBuffer)
checkALError()
AL10.alSource3f(beepSource, AL10.AL_POSITION, 0f, 0f, 1f)
AL10.alSourcef(beepSource, AL10.AL_REFERENCE_DISTANCE, 1f)
AL10.alSourcef(beepSource, AL10.AL_MAX_DISTANCE, 1f)
AL10.alSourcef(beepSource, AL10.AL_GAIN, 0.3f)
checkALError()
AL10.alSourcePlay(beepSource)
checkALError()
}
catch (e: ALException) {
AL10.alDeleteSources(beepSource)
}
}
catch (e: ALException) {
AL10.alDeleteSources(beepSource)
}*/
}
// Custom implementation of Util.checkALError() that uses our custom exception.
private fun checkALError() {
val errorCode = AL10.alGetError()
if (errorCode != AL10.AL_NO_ERROR) {
throw net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.ALException(errorCode)
}
}
}

View File

@@ -0,0 +1,521 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.*
import org.luaj.vm2.lib.OneArgFunction
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
import java.io.*
import java.nio.file.Files
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
/**
* computer directory:
* .../computers/
* media/hda/ -> .../computers/<uuid for the hda>/
*
* Created by minjaesong on 2016-09-17.
*
*
* NOTES:
* Don't convert '\' to '/'! Rev-slash is used for escape character in sh, and we're sh-compatible!
* Use .absoluteFile whenever possible; there's fuckin oddity! (http://bugs.java.com/bugdatabase/view_bug.do;:YfiG?bug_id=4483097)
*/
@Deprecated("Fuck permission and shit, we go virtual. Use FilesystemTar")
internal class FilesystemDir(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
init {
// load things. WARNING: THIS IS MANUAL!
globals["fs"] = LuaValue.tableOf()
globals["fs"]["list"] = ListFiles(computer) // CC compliant
globals["fs"]["exists"] = FileExists(computer) // CC/OC compliant
globals["fs"]["isDir"] = IsDirectory(computer) // CC compliant
globals["fs"]["isFile"] = IsFile(computer)
globals["fs"]["isReadOnly"] = IsReadOnly(computer) // CC compliant
globals["fs"]["getSize"] = GetSize(computer) // CC compliant
globals["fs"]["mkdir"] = Mkdir(computer)
globals["fs"]["mv"] = Mv(computer)
globals["fs"]["cp"] = Cp(computer)
globals["fs"]["rm"] = Rm(computer)
globals["fs"]["concat"] = ConcatPath(computer) // OC compliant
globals["fs"]["open"] = OpenFile(computer) //CC compliant
globals["fs"]["parent"] = GetParentDir(computer)
// fs.dofile defined in BOOT
// fs.fetchText defined in ROMLIB
}
companion object {
fun ensurePathSanity(path: LuaValue) {
if (path.checkIBM437().contains(Regex("""\.\.""")))
throw LuaError("'..' on path is not supported.")
if (!isValidFilename(path.checkIBM437()))
throw IOException("path contains invalid characters")
}
var isCaseInsensitive: Boolean
init {
try {
val uuid = UUID.randomUUID().toString()
val lowerCase = File(Terrarum.currentSaveDir, uuid + "oc_rox")
val upperCase = File(Terrarum.currentSaveDir, uuid + "OC_ROX")
// This should NEVER happen but could also lead to VERY weird bugs, so we
// make sure the files don't exist.
if (lowerCase.exists()) lowerCase.delete()
if (upperCase.exists()) upperCase.delete()
lowerCase.createNewFile()
val insensitive = upperCase.exists()
lowerCase.delete()
isCaseInsensitive = insensitive
println("[Filesystem] Case insensitivity: $isCaseInsensitive")
}
catch (e: IOException) {
System.err.println("[Filesystem] Couldn't determine if the file system is case sensitive, falling back to insensitive.")
e.printStackTrace(System.out)
isCaseInsensitive = true
}
}
// Worst-case: we're on Windows or using a FAT32 partition mounted in *nix.
// Note: we allow / as the path separator and expect all \s to be converted
// accordingly before the path is passed to the file system.
private val invalidChars = Regex("""[<>:"|?*\u0000-\u001F]""") // original OC uses Set(); we use regex
fun isValidFilename(name: String) = !name.contains(invalidChars)
fun String.validatePath() : String {
if (!isValidFilename(this)) {
throw IOException("path contains invalid characters")
}
return this
}
/** actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
* directs media/ directory to /<uuid> directory
*/
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getRealPath(luapath: LuaValue) : String {
// direct mounted paths to real path
val computerDir = Terrarum.currentSaveDir.absolutePath + "/computers/"
/* if not begins with "(/?)media/", direct to boot
* else, to corresponding drives
*
* List of device names (these are auto-mounted. why? primitivism :p):
* = hda - hdd: hard disks
* = fd1 - fd4: floppy drives
* = sda: whatever external drives, usually a CD
* = boot: current boot device
*/
// remove first '/' in path
var path = luapath.checkIBM437().validatePath()
if (path.startsWith('/')) path = path.substring(1)
val finalPath: String
if (path.startsWith("media/")) {
val device = path.substring(6, 9)
val subPath = path.substring(9)
finalPath = computerDir + this.computerValue.getAsString("device") + subPath
}
else {
finalPath = computerDir + this.computerValue.getAsString("boot") + "/" + path
}
// remove trailing slash
return if (finalPath.endsWith("\\") || finalPath.endsWith("/"))
finalPath.substring(0, finalPath.length - 1)
else
finalPath
}
fun combinePath(base: String, local: String) : String {
return "$base$local".replace("//", "/")
}
}
/**
* @param cname == UUID of the drive
*
* actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
*/
class ListFiles(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
println("ListFiles: got path ${path.checkIBM437()}")
val table = LuaTable()
val file = File(computer.getRealPath(path)).absoluteFile
try {
file.list().forEachIndexed { i, s -> table.insert(i, LuaValue.valueOf(s)) }
}
catch (e: NullPointerException) {}
return table
}
}
/** Don't use this. Use isFile */
class FileExists(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
return LuaValue.valueOf(Files.exists(Paths.get(computer.getRealPath(path)).toAbsolutePath()))
}
}
class IsDirectory(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
val isDir = Files.isDirectory(Paths.get(computer.getRealPath(path)).toAbsolutePath())
val exists = Files.exists(Paths.get(computer.getRealPath(path)).toAbsolutePath())
return LuaValue.valueOf(isDir || exists)
}
}
class IsFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
// check if the path is file by checking:
// 1. isfile
// 2. canwrite
// 3. length
// Why? Our Java simply wants to fuck you.
val path = Paths.get(computer.getRealPath(path)).toAbsolutePath()
var result = false
result = Files.isRegularFile(path)
if (!result) result = Files.isWritable(path)
if (!result)
try { result = Files.size(path) > 0 }
catch (e: NoSuchFileException) { result = false }
return LuaValue.valueOf(result)
}
}
class IsReadOnly(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
return LuaValue.valueOf(!Files.isWritable(Paths.get(computer.getRealPath(path)).toAbsolutePath()))
}
}
/** we have 4GB file size limit */
class GetSize(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
return LuaValue.valueOf(Files.size(Paths.get(computer.getRealPath(path)).toAbsolutePath()).toInt())
}
}
// TODO class GetFreeSpace
/**
* difference with ComputerCraft: it returns boolean, true on successful.
*/
class Mkdir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
return LuaValue.valueOf(File(computer.getRealPath(path)).absoluteFile.mkdir())
}
}
/**
* moves a directory, overwrites the target
*/
class Mv(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(from)
FilesystemDir.ensurePathSanity(to)
val fromFile = File(computer.getRealPath(from)).absoluteFile
var success = fromFile.copyRecursively(
File(computer.getRealPath(to)).absoluteFile, overwrite = true
)
if (success) success = fromFile.deleteRecursively()
else return LuaValue.valueOf(false)
return LuaValue.valueOf(success)
}
}
/**
* copies a directory, overwrites the target
* difference with ComputerCraft: it returns boolean, true on successful.
*/
class Cp(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(from)
FilesystemDir.ensurePathSanity(to)
return LuaValue.valueOf(
File(computer.getRealPath(from)).absoluteFile.copyRecursively(
File(computer.getRealPath(to)).absoluteFile, overwrite = true
)
)
}
}
/**
* difference with ComputerCraft: it returns boolean, true on successful.
*/
class Rm(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
return LuaValue.valueOf(
File(computer.getRealPath(path)).absoluteFile.deleteRecursively()
)
}
}
class ConcatPath(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(base: LuaValue, local: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(base)
FilesystemDir.ensurePathSanity(local)
val combinedPath = combinePath(base.checkIBM437().validatePath(), local.checkIBM437().validatePath())
return LuaValue.valueOf(combinedPath)
}
}
/**
* @param mode: r, rb, w, wb, a, ab
*
* Difference: TEXT MODE assumes CP437 instead of UTF-8!
*
* When you have opened a file you must always close the file handle, or else data may not be saved.
*
* FILE class in CC:
* (when you look thru them using file = fs.open("./test", "w")
*
* file = {
* close = function()
* -- write mode
* write = function(string)
* flush = function() -- write, keep the handle
* writeLine = function(string) -- text mode
* -- read mode
* readLine = function() -- text mode
* readAll = function()
* -- binary read mode
* read = function() -- read single byte. return: number or nil
* -- binary write mode
* write = function(byte)
* writeBytes = function(string as bytearray)
* }
*/
class OpenFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(path: LuaValue, mode: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
val mode = mode.checkIBM437().toLowerCase()
val luaClass = LuaTable()
val file = File(computer.getRealPath(path)).absoluteFile
if (mode.contains(Regex("""[aw]""")) && !file.canWrite())
throw LuaError("Cannot open file for " +
"${if (mode.startsWith('w')) "read" else "append"} mode" +
": is readonly.")
when (mode) {
"r" -> {
try {
val fr = FileReader(file)
luaClass["close"] = FileClassClose(fr)
luaClass["readLine"] = FileClassReadLine(fr)
luaClass["readAll"] = FileClassReadAll(file.toPath())
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError(
if (e.message != null && e.message!!.contains(Regex("""[Aa]ccess (is )?denied""")))
"$path: access denied."
else
"$path: no such file."
)
}
}
"rb" -> {
try {
val fis = FileInputStream(file)
luaClass["close"] = FileClassClose(fis)
luaClass["read"] = FileClassReadByte(fis)
luaClass["readAll"] = FileClassReadAll(file.toPath())
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: no such file.")
}
}
"w", "a" -> {
try {
val fw = FileWriter(file, (mode.startsWith('a')))
luaClass["close"] = FileClassClose(fw)
luaClass["write"] = FileClassPrintText(fw)
luaClass["writeLine"] = FileClassPrintlnText(fw)
luaClass["flush"] = FileClassFlush(fw)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: is a directory.")
}
}
"wb", "ab" -> {
try {
val fos = FileOutputStream(file, (mode.startsWith('a')))
luaClass["close"] = FileClassClose(fos)
luaClass["write"] = FileClassWriteByte(fos)
luaClass["writeBytes"] = FileClassWriteBytes(fos)
luaClass["flush"] = FileClassFlush(fos)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: is a directory.")
}
}
}
return luaClass
}
}
class GetParentDir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
FilesystemDir.ensurePathSanity(path)
var pathSB = StringBuilder(path.checkIBM437())
// backward travel, drop chars until '/' has encountered
while (!pathSB.endsWith('/'))
pathSB.deleteCharAt(pathSB.lastIndex - 1)
// drop trailing '/'
if (pathSB.endsWith('/'))
pathSB.deleteCharAt(pathSB.lastIndex - 1)
return LuaValue.valueOf(pathSB.toString())
}
}
//////////////////////////////
// OpenFile implementations //
//////////////////////////////
private class FileClassClose(val fo: Any) : ZeroArgFunction() {
override fun call() : LuaValue {
if (fo is FileOutputStream)
fo.close()
else if (fo is FileWriter)
fo.close()
else if (fo is FileReader)
fo.close()
else if (fo is FileInputStream)
fo.close()
else
throw IllegalArgumentException("Unacceptable file output: must be either Input/OutputStream or Reader/Writer.")
return LuaValue.NONE
}
}
private class FileClassWriteByte(val fos: FileOutputStream) : OneArgFunction() {
override fun call(byte: LuaValue) : LuaValue {
fos.write(byte.checkint())
return LuaValue.NONE
}
}
private class FileClassWriteBytes(val fos: FileOutputStream) : OneArgFunction() {
override fun call(byteString: LuaValue) : LuaValue {
val byteString = byteString.checkIBM437()
val bytearr = ByteArray(byteString.length, { byteString[it].toByte() })
fos.write(bytearr)
return LuaValue.NONE
}
}
private class FileClassPrintText(val fw: FileWriter) : OneArgFunction() {
override fun call(string: LuaValue) : LuaValue {
val text = string.checkIBM437()
fw.write(text)
return LuaValue.NONE
}
}
private class FileClassPrintlnText(val fw: FileWriter) : OneArgFunction() {
override fun call(string: LuaValue) : LuaValue {
val text = string.checkIBM437() + "\n"
fw.write(text)
return LuaValue.NONE
}
}
private class FileClassFlush(val fo: Any) : ZeroArgFunction() {
override fun call() : LuaValue {
if (fo is FileOutputStream)
fo.flush()
else if (fo is FileWriter)
fo.flush()
else
throw IllegalArgumentException("Unacceptable file output: must be either OutputStream or Writer.")
return LuaValue.NONE
}
}
private class FileClassReadByte(val fis: FileInputStream) : ZeroArgFunction() {
override fun call() : LuaValue {
val readByte = fis.read()
return if (readByte == -1) LuaValue.NIL else LuaValue.valueOf(readByte)
}
}
private class FileClassReadAllBytes(val path: Path) : ZeroArgFunction() {
override fun call() : LuaValue {
val byteArr = Files.readAllBytes(path)
val s: String = java.lang.String(byteArr, "IBM437").toString()
return LuaValue.valueOf(s)
}
}
private class FileClassReadAll(val path: Path) : ZeroArgFunction() {
override fun call() : LuaValue {
return FileClassReadAllBytes(path).call()
}
}
/** returns NO line separator! */
private class FileClassReadLine(val fr: FileReader) : ZeroArgFunction() {
val scanner = Scanner(fr.readText()) // no closing; keep the scanner status persistent
override fun call() : LuaValue {
return if (scanner.hasNextLine()) LuaValue.valueOf(scanner.nextLine())
else LuaValue.NIL
}
}
}

View File

@@ -0,0 +1,7 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
/**
* Created by minjaesong on 2016-09-17.
*/
class FilesystemFactory {
}

View File

@@ -0,0 +1,513 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.*
import org.luaj.vm2.lib.OneArgFunction
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil.VDPath
import java.io.*
import java.nio.charset.Charset
import java.util.*
/**
* computer directory:
* .../computers/
* media/hda/ -> .../computers/<uuid for the hda>/
*
* Created by minjaesong on 2016-09-17.
*
*
* NOTES:
* Don't convert '\' to '/'! Rev-slash is used for escape character in sh, and we're sh-compatible!
* Use .absoluteFile whenever possible; there's fuckin oddity! (http://bugs.java.com/bugdatabase/view_bug.do;:YfiG?bug_id=4483097)
*/
internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
init {
// load things. WARNING: THIS IS MANUAL!
globals["fs"] = LuaValue.tableOf()
globals["fs"]["list"] = ListFiles(computer) // CC compliant
globals["fs"]["exists"] = FileExists(computer) // CC/OC compliant
globals["fs"]["isDir"] = IsDirectory(computer) // CC compliant
globals["fs"]["isFile"] = IsFile(computer)
globals["fs"]["isReadOnly"] = IsReadOnly(computer) // CC compliant
globals["fs"]["getSize"] = GetSize(computer) // CC compliant
globals["fs"]["mkdir"] = Mkdir(computer)
globals["fs"]["mv"] = Mv(computer)
globals["fs"]["cp"] = Cp(computer)
globals["fs"]["rm"] = Rm(computer)
globals["fs"]["concat"] = ConcatPath(computer) // OC compliant
globals["fs"]["open"] = OpenFile(computer) //CC compliant
globals["fs"]["parent"] = GetParentDir(computer)
// fs.dofile defined in BOOT
// fs.fetchText defined in ROMLIB
}
companion object {
val sysCharset = Charset.forName("CP437")
fun LuaValue.checkPath(): String {
if (this.checkIBM437().contains(Regex("""\.\.""")))
throw LuaError("'..' on path is not supported.")
return this.checkIBM437().validatePath()
}
// Worst-case: we're on Windows or using a FAT32 partition mounted in *nix.
// Note: we allow / as the path separator and expect all \s to be converted
// accordingly before the path is passed to the file system.
private val invalidChars = Regex("""[<>:"|?*\u0000-\u001F]""") // original OC uses Set(); we use regex
fun isValidFilename(name: String) = !name.contains(invalidChars)
fun String.validatePath() : String {
if (!isValidFilename(this)) {
throw IOException("path contains invalid characters")
}
return this
}
/**
* return value is there for chaining only.
*/
fun VDPath.dropMount(): VDPath {
if (this.hierarchy.size >= 2 && this[0].toCanonicalString(sysCharset) == "media") {
this.hierarchy.removeAt(0) // drop "media"
this.hierarchy.removeAt(0) // drop whatever mount symbol
}
return this
}
/**
* if path is {media, someUUID, subpath}, redirects to
* computer.diskRack[SOMEUUID]->subpath
* else, computer.diskRack["hda"]->subpath
*/
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getFile(path: VDPath) : DiskEntry? {
val disk = this.getTargetDisk(path)
if (disk == null) return null
path.dropMount()
return VDUtil.getFile(disk, path)
}
/**
* if path is like {media, fd1, subpath}, return
* computer.diskRack["fd1"]
* else, computer.diskRack[<boot device>]
*/
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getTargetDisk(path: VDPath) : VirtualDisk? {
if (path.hierarchy.size >= 2 &&
Arrays.equals(path[0], "media".toEntryName(DiskEntry.NAME_LENGTH, sysCharset))) {
val diskName = path[1].toCanonicalString(sysCharset)
val disk = this.diskRack[diskName]
return disk
}
else {
return this.diskRack[this.computerValue.getAsString("boot")]
}
}
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getDirectoryEntries(path: VDPath) : Array<DiskEntry>? {
val directory = this.getFile(path)
if (directory == null) return null
return VDUtil.getDirectoryEntries(this.getTargetDisk(path)!!, directory)
}
fun combinePath(base: String, local: String) : String {
return "$base$local".replace("//", "/")
}
private fun tryBool(action: () -> Unit): LuaValue {
try {
action()
return LuaValue.valueOf(true)
}
catch (gottaCatchemAll: Exception) {
return LuaValue.valueOf(false)
}
}
} // end of Companion Object
/**
* @param cname == UUID of the drive
*
* actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
*/
class ListFiles(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset)
val table = LuaTable()
try {
val directoryContents = computer.getDirectoryEntries(path)!!
println("[Filesystem] directoryContents size: ${directoryContents.size}")
directoryContents.forEachIndexed { index, diskEntry ->
table.insert(index + 1, LuaValue.valueOf(diskEntry.filename.toCanonicalString(sysCharset)))
}
}
catch (e: KotlinNullPointerException) {}
return table
}
}
class FileExists(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset)
val disk = computer.getTargetDisk(path)
if (disk == null) return LuaValue.valueOf(false)
return LuaValue.valueOf(
VDUtil.getFile(disk, path.dropMount()) != null
)
}
}
class IsDirectory(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset)
return LuaValue.valueOf(computer.getFile(path)?.contents is EntryDirectory)
}
}
class IsFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset)
return LuaValue.valueOf(computer.getFile(path)?.contents is EntryFile)
}
}
class IsReadOnly(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
return LuaValue.valueOf(false)
}
}
/** we have 2 GB file size limit */
class GetSize(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDUtil.VDPath(path.checkPath(), sysCharset)
val file = computer.getFile(path)
try {
if (file!!.contents is EntryFile)
return LuaValue.valueOf(file.contents.getSizePure().toInt())
else if (file.contents is EntryDirectory)
return LuaValue.valueOf(file.contents.entryCount)
}
catch (e: KotlinNullPointerException) {
}
return LuaValue.NONE
}
}
// TODO class GetFreeSpace
/**
* returns true on success
*/
class Mkdir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
return tryBool {
val path = VDPath(path.checkPath(), sysCharset)
val disk = computer.getTargetDisk(path)!!
val dirList = computer.getDirectoryEntries(path.getParent())
var makeNew = true
// check dupes
if (dirList != null) {
for (entry in dirList) {
if (Arrays.equals(path.last(), entry.filename)) {
makeNew = false
break
}
}
}
if (makeNew) {
VDUtil.addDir(disk, path.getParent(), path.last())
}
}
}
}
/**
* moves a directory, overwrites the target
*/
class Mv(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
return tryBool {
val pathFrom = VDPath(from.checkPath(), sysCharset)
val disk1 = computer.getTargetDisk(pathFrom)
val pathTo = VDPath(to.checkPath(), sysCharset)
val disk2 = computer.getTargetDisk(pathTo)
VDUtil.moveFile(disk1!!, pathFrom, disk2!!, pathTo)
}
}
}
/**
* copies a directory, overwrites the target
* difference with ComputerCraft: it returns boolean, true on successful.
*/
class Cp(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
return tryBool {
val pathFrom = VDPath(from.checkPath(), sysCharset)
val disk1 = computer.getTargetDisk(pathFrom)!!
val pathTo = VDPath(to.checkPath(), sysCharset)
val disk2 = computer.getTargetDisk(pathTo)!!
val oldFile = VDUtil.getFile(disk2, pathTo)
try {
VDUtil.deleteFile(disk2, pathTo)
}
catch (e: FileNotFoundException) {
"Nothing to delete beforehand"
}
val file = VDUtil.getFile(disk1, pathFrom)!!
try {
VDUtil.addFile(disk2, pathTo.getParent(), file)
}
catch (e: FileNotFoundException) {
// roll back delete on disk2
if (oldFile != null) {
VDUtil.addFile(disk2, oldFile.parentEntryID, oldFile)
throw FileNotFoundException("No such destination")
}
}
}
}
}
/**
* difference with ComputerCraft: it returns boolean, true on successful.
*/
class Rm(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
return tryBool {
val path = VDPath(path.checkPath(), sysCharset)
val disk = computer.getTargetDisk(path)!!
VDUtil.deleteFile(disk, path)
}
}
}
class ConcatPath(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(base: LuaValue, local: LuaValue) : LuaValue {
TODO()
}
}
/**
* @param mode: r, rb, w, wb, a, ab
*
* Difference: TEXT MODE assumes CP437 instead of UTF-8!
*
* When you have opened a file you must always close the file handle, or else data may not be saved.
*
* FILE class in CC:
* (when you look thru them using file = fs.open("./test", "w")
*
* file = {
* close = function()
* -- write mode
* write = function(string)
* flush = function() -- write, keep the handle
* writeLine = function(string) -- text mode
* -- read mode
* readLine = function() -- text mode
* readAll = function()
* -- binary read mode
* read = function() -- read single byte. return: number or nil
* -- binary write mode
* write = function(byte)
* writeBytes = function(string as bytearray)
* }
*/
class OpenFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
override fun call(path: LuaValue, mode: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset)
val disk = computer.getTargetDisk(path)!!
path.dropMount()
val mode = mode.checkIBM437().toLowerCase()
val luaClass = LuaTable()
val fileEntry = computer.getFile(path)!!
if (fileEntry.contents is EntryDirectory) {
throw LuaError("File '${fileEntry.getFilenameString(sysCharset)}' is directory.")
}
val file = fileEntry.contents as EntryFile
if (mode.contains(Regex("""[aw]""")))
throw LuaError("Cannot open file for " +
"${if (mode.startsWith('w')) "read" else "append"} mode" +
": is readonly.")
when (mode) {
"r" -> {
try {
val fr = StringReader(String(file.bytes.toByteArray(), sysCharset))//FileReader(file)
luaClass["close"] = FileClassClose(fr)
luaClass["readLine"] = FileClassReadLine(fr)
luaClass["readAll"] = FileClassReadAll(file)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError(
if (e.message != null && e.message!!.contains(Regex("""[Aa]ccess (is )?denied""")))
"$path: access denied."
else
"$path: no such file."
)
}
}
"rb" -> {
try {
val fis = ByteArrayInputStream(file.bytes.toByteArray())
luaClass["close"] = FileClassClose(fis)
luaClass["read"] = FileClassReadByte(fis)
luaClass["readAll"] = FileClassReadAll(file)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: no such file.")
}
}
"w", "a" -> {
try {
val fw = VDFileWriter(fileEntry, mode.startsWith('a'), sysCharset)
luaClass["close"] = FileClassClose(fw)
luaClass["write"] = FileClassPrintText(fw)
luaClass["writeLine"] = FileClassPrintlnText(fw)
luaClass["flush"] = FileClassFlush(fw)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: is a directory.")
}
}
"wb", "ab" -> {
try {
val fos = VDFileOutputStream(fileEntry, mode.startsWith('a'), sysCharset)
luaClass["close"] = FileClassClose(fos)
luaClass["write"] = FileClassWriteByte(fos)
luaClass["writeBytes"] = FileClassWriteBytes(fos)
luaClass["flush"] = FileClassFlush(fos)
}
catch (e: FileNotFoundException) {
e.printStackTrace()
throw LuaError("$path: is a directory.")
}
}
}
return luaClass
}
}
class GetParentDir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(path: LuaValue) : LuaValue {
val path = VDPath(path.checkPath(), sysCharset).getParent()
return LuaValue.valueOf(path.toString())
}
}
//////////////////////////////
// OpenFile implementations //
//////////////////////////////
private class FileClassClose(val fo: Closeable) : ZeroArgFunction() {
override fun call() : LuaValue {
fo.close()
return LuaValue.NONE
}
}
private class FileClassWriteByte(val fos: VDFileOutputStream) : OneArgFunction() {
override fun call(byte: LuaValue) : LuaValue {
fos.write(byte.checkint())
return LuaValue.NONE
}
}
private class FileClassWriteBytes(val fos: VDFileOutputStream) : OneArgFunction() {
override fun call(byteString: LuaValue) : LuaValue {
val byteString = byteString.checkIBM437()
val bytearr = ByteArray(byteString.length, { byteString[it].toByte() })
fos.write(bytearr)
return LuaValue.NONE
}
}
private class FileClassPrintText(val fw: VDFileWriter) : OneArgFunction() {
override fun call(string: LuaValue) : LuaValue {
val text = string.checkIBM437()
fw.write(text)
return LuaValue.NONE
}
}
private class FileClassPrintlnText(val fw: VDFileWriter) : OneArgFunction() {
override fun call(string: LuaValue) : LuaValue {
val text = string.checkIBM437() + "\n"
fw.write(text)
return LuaValue.NONE
}
}
private class FileClassFlush(val fo: Flushable) : ZeroArgFunction() {
override fun call() : LuaValue {
fo.flush()
return LuaValue.NONE
}
}
private class FileClassReadByte(val fis: ByteArrayInputStream) : ZeroArgFunction() {
override fun call() : LuaValue {
val readByte = fis.read()
return if (readByte == -1) LuaValue.NIL else LuaValue.valueOf(readByte)
}
}
private class FileClassReadAllBytes(val file: EntryFile) : ZeroArgFunction() {
override fun call() : LuaValue {
return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset))
}
}
private class FileClassReadAll(val file: EntryFile) : ZeroArgFunction() {
override fun call() : LuaValue {
return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset))
}
}
/** returns NO line separator! */
private class FileClassReadLine(fr: Reader) : ZeroArgFunction() {
val scanner = Scanner(fr.readText()) // no closing; keep the scanner status persistent
override fun call() : LuaValue {
return if (scanner.hasNextLine()) LuaValue.valueOf(scanner.nextLine())
else LuaValue.NIL
}
}
}

View File

@@ -0,0 +1,78 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import net.torvald.terrarum.gameactors.ai.toLua
import org.luaj.vm2.lib.OneArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
import org.luaj.vm2.*
/**
* Provide Lua an access to computer object that is in Java
*
* The "machine" refers to the computer fixture itself in the game world.
*
* Created by minjaesong on 2016-09-19.
*/
internal class HostAccessProvider(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
init {
globals["machine"] = LuaTable()
globals["machine"]["println"] = PrintLn()
globals["machine"]["isHalted"] = IsHalted(computer)
globals["machine"]["__readFromStdin"] = NativeReadStdin(computer)
globals["machine"]["milliTime"] = NativeGetMilliTime(computer)
globals["machine"]["sleep"] = NativeThreadSleep(computer)
globals["__haltsystemexplicit__"] = HaltComputer(computer)
}
class PrintLn(): OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
if (p0.isnumber())
println(p0.checkdouble())
else
println(p0.checkIBM437())
return LuaValue.NONE
}
}
class IsHalted(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer): ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(computer.isHalted)
}
}
class NativeReadStdin(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
override fun call(): LuaValue {
return computer.stdin!!.read().toLua()
}
}
class HaltComputer(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
override fun call() : LuaValue {
computer.isHalted = true
computer.luaJ_globals.load("""print(DC4.."system halted")""").call()
return LuaValue.NONE
}
}
/** Time elapsed since the power is on. */
class NativeGetMilliTime(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(computer.milliTime)
}
}
class NativeThreadSleep(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
override fun call(mills: LuaValue): LuaValue {
computer.currentExecutionThread.join(mills.checklong())
return LuaValue.NONE
}
}
}

View File

@@ -0,0 +1,16 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import com.badlogic.gdx.Gdx
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.OneArgFunction
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
/**
* Created by minjaesong on 2016-09-25.
*/
class Input(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
}

View File

@@ -0,0 +1,149 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import org.luaj.vm2.LuaFunction
import org.luaj.vm2.lib.OneArgFunction
/**
* PC Speaker driver and arpeggiator (MONOTONE-style 4 channels)
*
* Notes are tuned to A440, equal temperament. This is an ISO standard.
*
* Created by minjaesong on 2016-09-27.
*/
class PcSpeakerDriver(val globals: Globals, host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
init {
globals["speaker"] = LuaTable()
globals["speaker"]["enqueue"] = EnqueueTone(host)
globals["speaker"]["clear"] = ClearQueue(host)
globals["speaker"]["retune"] = Retune(globals)
globals["speaker"]["resetTune"] = ResetTune(globals)
globals["speaker"]["toFreq"] = StringToFrequency(globals)
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ) // every other PSGs should use this very variable
// constants
// e.g. speaker.A0 returns number 1
fun Int.toNote(): String = NOTE_NAMES[this % 12] + this.plus(8).div(12).toString()
fun Int.toNoteAlt(): String = NOTE_NAMES_ALT[this % 12] + this.plus(8).div(12).toString()
for (i in 1..126) {
globals["speaker"][i.toNote()] = i // sharps
globals["speaker"][i.toNoteAlt()] = i // flats
}
}
companion object {
val BASE_FREQ = 27.5 // frequency of A0
val NOTE_NAMES = arrayOf("GS", "A", "AS", "B", "C", "CS",
"D", "DS", "E", "F", "FS", "G")
val NOTE_NAMES_ALT = arrayOf("Ab", "A", "Bb", "B", "C", "Db",
"D", "Eb", "E", "F", "Gb", "G")
/** @param basefreq: Frequency of A-0 */
fun Int.toFreq(basefreq: Double): Double = basefreq * Math.pow(2.0, (this - 1.0) / 12.0)
/** @param "A-5", "B4", "C#5", ... */
fun String.toNoteIndex(): Int {
var notestr = this.replace("-", "")
notestr = notestr.replace("#", "S")
val baseNote = if (notestr.contains("S") || notestr.contains("b"))
notestr.substring(0, 2)
else
notestr.substring(0, 1)
var note: Int = NOTE_NAMES.indexOf(baseNote) // [0-11]
if (note < 0) note = NOTE_NAMES_ALT.indexOf(baseNote) // search again
if (note < 0) throw IllegalArgumentException("Unknown note: $this") // failed to search
val octave: Int = notestr.replace(Regex("""[^0-9]"""), "").toInt()
return octave.minus(if (note >= 4) 1 else 0) * 12 + note
}
}
class EnqueueTone(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
/**
* @param freq: number (hertz) or string (A-4, A4, B#2, ...)
*/
override fun call(second: LuaValue, freq: LuaValue): LuaValue {
if (freq.isnumber())
host.enqueueBeep(second.checkdouble(), freq.checkdouble())
else {
host.enqueueBeep(second.checkdouble(),
freq.checkjstring().toNoteIndex()
.toFreq(host.luaJ_globals["speaker"]["__basefreq__"].checkdouble())
)
}
return LuaValue.NONE
}
}
class ClearQueue(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
override fun call(): LuaValue {
host.clearBeepQueue()
return LuaValue.NONE
}
}
class Retune(val globals: Globals) : LuaFunction() {
/**
* Examples: C256, A440, A#440, ...
*/
override fun call(arg: LuaValue): LuaValue {
val tuneName = arg.checkjstring()
val baseNote = if (tuneName.contains("#") || tuneName.contains("b")) tuneName.substring(0, 2) else tuneName.substring(0, 1)
val freq = tuneName.replace(Regex("""[^0-9]"""), "").toInt()
// we're assuming the input to be C4, C#4, ... A4, A#4, B4
// diffPivot corsp. to G#4, A4, ...
val diffPivot = arrayOf(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 2^(12 / n)
var diff = diffPivot[NOTE_NAMES.indexOf(baseNote)]
if (diff < 0) diff = diffPivot[NOTE_NAMES_ALT.indexOf(baseNote)] // search again
if (diff < 0) throw IllegalArgumentException("Unknown note: $baseNote") // failed to search
val exp = -diff / 12.0
val basefreq = freq * Math.pow(2.0, exp) / if (diff >= 3) 8.0 else 16.0 // converts whatever baseNote to A0
globals["speaker"]["__basefreq__"] = basefreq
return LuaValue.NONE
}
override fun call(): LuaValue {
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ)
return LuaValue.NONE
}
}
class ResetTune(val globals: Globals) : ZeroArgFunction() {
override fun call(): LuaValue {
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ)
return LuaValue.NONE
}
}
/**
* usage = speaker.toFreq(speaker.AS5) --'S' is a substitution for '#'
*/
class StringToFrequency(val globals: Globals) : OneArgFunction() {
/**
* @param arg: number (note index) or string (A-4, A4, B#2, ...)
*/
override fun call(arg: LuaValue): LuaValue {
val note = if (arg.isint()) arg.checkint()
else {
arg.checkjstring().toNoteIndex()
}
val basefreq = globals["speaker"]["__basefreq__"].checkdouble()
return LuaValue.valueOf(note.toFreq(basefreq))
}
}
}

View File

@@ -0,0 +1,98 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.OneArgFunction
import net.torvald.terrarum.gameworld.toUint
import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.digest.DigestUtils
import java.security.SecureRandom
/**
* Hashes, CSPRNG, Base64
*
* Created by minjaesong on 2016-09-15.
*/
internal class Security(globals: Globals) {
init {
// load things. WARNING: THIS IS MANUAL!
globals["security"] = LuaValue.tableOf()
globals["security"]["toSHA256"] = SHA256sum()
globals["security"]["toSHA1"] = SHA1sum()
globals["security"]["toMD5"] = MD5sum()
globals["security"]["randomBytes"] = SecureRandomHex()
globals["security"]["decodeBase64"] = DecodeBase64()
globals["security"]["encodeBase64"] = EncodeBase64()
}
/** @return byteArray as String */
class SHA256sum : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
val hashBytes = DigestUtils.sha256(p0.checkjstring())
return LuaValue.valueOf(hashBytes.toStringRepresentation())
}
}
/** @return byteArray as String */
class SHA1sum: OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
val hashBytes = DigestUtils.sha1(p0.checkjstring())
return LuaValue.valueOf(hashBytes.toStringRepresentation())
}
}
/** @return byteArray as String */
class MD5sum: OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
val hashBytes = DigestUtils.md5(p0.checkjstring())
return LuaValue.valueOf(hashBytes.toStringRepresentation())
}
}
/** @return byteArray as String */
class SecureRandomHex: OneArgFunction() {
override fun call(byteSize: LuaValue): LuaValue {
val bytes = ByteArray(byteSize.checkint())
SecureRandom().nextBytes(bytes)
return LuaValue.valueOf(bytes.toStringRepresentation())
}
}
/** @return String */
class DecodeBase64: OneArgFunction() {
override fun call(base64: LuaValue): LuaValue {
val decodedBytes = Base64.decodeBase64(base64.checkjstring())
return LuaValue.valueOf(decodedBytes.toStringRepresentation())
}
}
/** @return byteArray as String */
class EncodeBase64: OneArgFunction() {
override fun call(inputString: LuaValue): LuaValue {
val inputBytes = inputString.checkjstring().toByteArray(charset("UTF-8"))
return LuaValue.valueOf(Base64.encodeBase64(inputBytes).toStringRepresentation())
}
}
companion object {
val hexLookup = charArrayOf(
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
)
fun Byte.toHexString(): String {
val bInt = this.toUint()
return "${hexLookup[bInt.shr(8).and(0xf)]}${hexLookup[bInt.and(0xf)]}"
}
/** essentially, 0xFC to 0xFC.toChar() */
fun ByteArray.toStringRepresentation(): String {
val sb = StringBuilder()
for (b in this)
sb.append(b.toChar())
return sb.toString()
}
}
}

View File

@@ -0,0 +1,284 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.*
import org.luaj.vm2.lib.*
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal
import java.nio.charset.Charset
/**
* Controls terminal as if it was a monitor
* (not sending control sequences but just drives it directly)
*
* Created by minjaesong on 2016-09-12.
*/
internal class Term(globals: Globals, term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
init {
// load things. WARNING: THIS IS MANUAL!
globals["term"] = LuaValue.tableOf()
globals["term"]["write"] = Term.WriteString(term)
globals["term"]["print"] = Term.PrintString(term)
globals["term"]["newLine"] = Term.NewLine(term)
globals["term"]["moveCursor"] = Term.MoveCursor(term) // TTY function
globals["term"]["width"] = Term.GetWidth(term)
globals["term"]["scroll"] = Term.Scroll(term)
globals["term"]["isTeletype"] = Term.IsTeletype(term)
globals["term"]["bell"] = Term.Bell(term)
if (term is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) {
globals["term"]["emitRaw"] = Term.EmitRaw(term)
globals["term"]["emit"] = Term.Emit(term)
globals["term"]["emitString"] = Term.EmitString(term)
globals["term"]["resetColor"] = Term.ResetColour(term)
globals["term"]["resetColour"] = Term.ResetColour(term)
globals["term"]["clear"] = Term.Clear(term)
globals["term"]["clearLine"] = Term.ClearLine(term)
globals["term"]["setCursor"] = Term.SetCursor(term)
globals["term"]["getCursor"] = Term.GetCursorPos(term)
globals["term"]["getX"] = Term.GetCursorX(term)
globals["term"]["getY"] = Term.GetCursorY(term)
globals["term"]["setX"] = Term.SetCursorX(term)
globals["term"]["setY"] = Term.SetCursorY(term)
globals["term"]["setCursorBlink"] = Term.SetCursorBlink(term)
globals["term"]["size"] = Term.GetSize(term)
globals["term"]["height"] = Term.GetHeight(term)
globals["term"]["isCol"] = Term.IsColour(term)
globals["term"]["setForeCol"] = Term.SetForeColour(term)
globals["term"]["setBackCol"] = Term.SetBackColour(term)
globals["term"]["foreCol"] = Term.GetForeColour(term)
globals["term"]["backCol"] = Term.GetBackColour(term)
}
}
companion object {
fun LuaValue.checkIBM437(): String {
if (this is LuaString)
return m_bytes.copyOfRange(m_offset, m_offset + m_length).toString(Charset.forName("CP437"))
// it only works if Charset is ISO-8859, despite of the name "IBM437"
// --> then would "CP437" work? -- Torvald at 2017-04-05
else
throw LuaError("bad argument (string expected, got ${this.typename()})")
}
}
class Bell(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
override fun call(pattern: LuaValue): LuaValue {
tty.bell(pattern.checkjstring())
return LuaValue.NONE
}
}
class WriteString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
override fun call(p0: LuaValue): LuaValue {
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
tty.writeString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
else
tty.writeChars(p0.checkIBM437())
return LuaValue.NONE
}
override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
tty.writeString(s.checkIBM437(), x.checkint(), y.checkint())
else
throw LuaError("couldn't move cursor; TTY is one-dimensional")
return LuaValue.NONE
}
}
class PrintString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
override fun call(p0: LuaValue): LuaValue {
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
tty.printString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
else
tty.printChars(p0.checkIBM437())
return LuaValue.NONE
}
override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
tty.printString(s.checkIBM437(), x.checkint(), y.checkint())
else
throw LuaError("couldn't move cursor; TTY is one-dimensional")
return LuaValue.NONE
}
}
class NewLine(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
override fun call(): LuaValue {
tty.newLine()
return LuaValue.NONE
}
}
class EmitRaw(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
term.emitChar(p0.checkint(), x.checkint() - 1, y.checkint() - 1)
return LuaValue.NONE
}
}
// emitchar
class Emit(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
term.emitChar(p0.checkint().toChar(), x.checkint() - 1, y.checkint() - 1)
return LuaValue.NONE
}
}
class EmitString(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
term.emitString(p0.checkIBM437(), x.checkint() - 1, y.checkint() - 1)
return LuaValue.NONE
}
}
class ResetColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
term.resetColour()
return LuaValue.NONE
}
}
class Clear(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
term.clear()
return LuaValue.NONE
}
}
class ClearLine(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
term.clearLine()
return LuaValue.NONE
}
}
/** term.setCursorPos(number x) */
class MoveCursor(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
for (i in 1..p0.checkint())
tty.printChar(' ')
return LuaValue.NONE
}
}
class SetCursor(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : TwoArgFunction() {
override fun call(x: LuaValue, y: LuaValue): LuaValue {
term.setCursor(x.checkint() - 1, y.checkint() - 1)
return LuaValue.NONE
}
}
/** One-based */
class GetCursorPos(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
override fun invoke(args: Varargs?): Varargs {
val ret = arrayOf(LuaValue.valueOf(term.cursorX + 1), LuaValue.valueOf(term.cursorY + 1))
return LuaValue.varargsOf(ret)
}
}
class GetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(term.cursorX + 1)
}
}
class GetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(term.cursorY + 1)
}
}
class SetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
term.setCursor(p0.checkint() - 1, term.cursorY)
return LuaValue.NONE
}
}
class SetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
term.setCursor(term.cursorX - 1, p0.checkint())
return LuaValue.NONE
}
}
/** term.setCursorBlink(boolean bool) */
class SetCursorBlink(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
term.cursorBlink = p0.toboolean()
return LuaValue.NONE
}
}
class GetSize(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
override fun invoke(args: Varargs?): Varargs {
val ret = arrayOf(LuaValue.valueOf(term.width), LuaValue.valueOf(term.height))
return LuaValue.varargsOf(ret)
}
}
class GetWidth(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(tty.width)
}
}
class GetHeight(val terminal: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(terminal.height)
}
}
class IsColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(term.coloursCount > 4)
}
}
/** term.scroll(number n) */
class Scroll(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) tty.scroll(p0.checkint())
else for (i in 1..p0.checkint()) tty.newLine()
return LuaValue.NONE
}
}
/** term.setTextColor(number color) */
class SetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
term.foreColour = p0.checkint()
return LuaValue.NONE
}
}
/** term.setBackgroundColor(number color) */
class SetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
override fun call(p0: LuaValue): LuaValue {
term.backColour = p0.checkint()
return LuaValue.NONE
}
}
class GetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(term.foreColour)
}
}
class GetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(term.backColour)
}
}
class IsTeletype(val termInQuestion: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
override fun call(): LuaValue {
return LuaValue.valueOf(termInQuestion.coloursCount == 0)
}
}
}

View File

@@ -0,0 +1,93 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaFunction
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.modulebasegame.Ingame
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
/**
* Implementation of lua's os.date, to return world info of the game world.
*
* Created by minjaesong on 2016-09-28.
*/
class WorldInformationProvider(globals: Globals) {
init {
globals["os"]["time"] = LuaValue.NIL // history is LONG! Our 32-bit Lua's epoch is destined to break down...
globals["os"]["date"] = OsDateImpl()
}
companion object {
fun getWorldTimeInLuaFormat() : LuaTable {
val t = LuaTable()
val time = if (Terrarum.ingame != null) (Terrarum.ingame!! as Ingame).world.time else WorldTime()
// int Terrarum World Time format
t["hour"] = time.hours
t["min"] = time.minutes
t["wday"] = time.dayOfWeek
t["year"] = time.years
t["yday"] = time.yearlyDays
t["month"] = time.months
t["sec"] = time.seconds
t["day"] = time.days
return t
}
val defaultDateFormat = "%a %d %B %Y %X"
/** evaluate single C date format */
fun String.evalAsDate(): String {
val time = if (Terrarum.ingame != null) (Terrarum.ingame!! as Ingame).world.time else WorldTime()
return when (this) {
"%a" -> time.getDayNameShort()
"%A" -> time.getDayNameFull()
"%b" -> time.getMonthNameShort()
"%B" -> time.getMonthNameFull()
"%c" -> "%x".evalAsDate() + " " + "%X".evalAsDate()
"%d" -> time.days.toString()
"%H" -> time.hours.toString()
"%I" -> throw IllegalArgumentException("%I: AM/PM concept does not exists.")
"%M" -> time.minutes.toString()
"%m" -> time.months.toString()
"%p" -> throw IllegalArgumentException("%p: AM/PM concept does not exists.")
"%S" -> time.seconds.toString()
"%w" -> time.dayOfWeek.toString()
"%x" -> "${String.format("%02d", time.years)}-${String.format("%02d", time.months)}-${String.format("%02d", time.days)}"
"%X" -> "${String.format("%02d", time.hours)}:${String.format("%02d", time.minutes)}:${String.format("%02d", time.seconds)}"
"%Y" -> time.years.toString()
"%y" -> time.years.mod(100).toString()
"%%" -> "%"
else -> throw IllegalArgumentException("Unknown format string: $this")
}
}
val acceptedDateFormats = arrayOf("%a", "%A", "%b", "%B", "%c", "%d", "%H", "%I", "%M", "%m", "%p", "%S", "%w", "%x", "%X", "%Y", "%y", "%%" )
}
/**
* Changes: cannot get a representation of arbitrary time.
*/
class OsDateImpl() : LuaFunction() {
// no args
override fun call(): LuaValue {
return call(defaultDateFormat)
}
override fun call(format: LuaValue): LuaValue {
var arg = format.checkjstring()
acceptedDateFormats.forEach {
if (arg.contains(it))
arg = arg.replace(it, it.evalAsDate(), ignoreCase = false)
}
return LuaValue.valueOf(arg)
}
}
}

View File

@@ -0,0 +1,17 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
/**
* Created by minjaesong on 2016-09-29.
*/
abstract class Peripheral(val tableName: String) {
abstract fun loadLib(globals: Globals)
override fun toString(): String = "Peripheral:$tableName"
abstract val memSize: Int
}

View File

@@ -0,0 +1,51 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.ModMgr
import org.luaj.vm2.Globals
/**
* Created by minjaesong on 2017-05-31.
*/
class PeripheralCharLCD(val width: Int, val height: Int) : net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("charLCD") {
/*companion object {
private val fontSheet = BitmapFont(ModMgr.getPath("dwarventech", "mt-32.tga"), 16, 16)
private val font = BitmapFont(fontSheet, 0.toChar())
private val fontW = fontSheet.width / fontSheet.horizontalCount
private val fontH = fontSheet.height / fontSheet.verticalCount
}
override fun loadLib(globals: Globals) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun toString(): String {
return super.toString()
}
override val memSize = width * height
var cursor: Int = 0 // character LCDs are mostly single long line wrapped
val memory = ByteArray(memSize) // temporary; replace with proper VMPeripheralWrapper
/**
* @param g Frame Buffer that holds the display of LCD screen
*/
fun render(batch: SpriteBatch) {
memory.forEachIndexed { index, byte ->
font.draw(batch, "${byte.toChar()}", (index % width) * fontW.toFloat(), (index / width) * fontH.toFloat())
}
}*/
override fun loadLib(globals: Globals) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun toString(): String {
return super.toString()
}
override val memSize: Int
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
}

View File

@@ -0,0 +1,44 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
import org.luaj.vm2.Globals
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.OneArgFunction
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
/**
* Provides internet access.
*
* Created by minjaesong on 2016-09-24.
*/
internal class PeripheralInternet(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer)
: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("internet"){
override val memSize = 1024
override fun loadLib(globals: Globals) {
globals["internet"] = LuaTable()
globals["internet"]["fetch"] = FetchWebPage()
}
class FetchWebPage() : OneArgFunction() {
override fun call(urlstr: LuaValue): LuaValue {
val url = URL(urlstr.checkjstring())
val inputstream = BufferedReader(InputStreamReader(url.openStream()))
var inline = ""
var readline = inputstream.readLine()
while (readline != null) {
inline += readline
readline = inputstream.readLine()
}
inputstream.close()
return LuaValue.valueOf(inline)
}
}
}

View File

@@ -0,0 +1,23 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
/**
* Virtual driver for 4-track squarewave PSG, which has no ability of changing a duty cycle
* but has a volume control (you'll need some other tracker than MONOTONE)
*
* Created by minjaesong on 2016-09-27.
*/
internal class PeripheralPSG(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer)
: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("psg") {
override val memSize = 1024
override fun loadLib(globals: Globals) {
globals["psg"] = LuaTable()
}
}

View File

@@ -0,0 +1,659 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
import org.luaj.vm2.*
import org.luaj.vm2.lib.*
/**
* Resolution: 640 x 200, non-square pixels
*
* Created by minjaesong on 2017-02-08.
*/
/*class PeripheralVideoCard(val host: TerrarumComputer, val termW: Int = 80, val termH: Int = 25) :
Peripheral("ppu") {
companion object {
val blockW = 8 // MUST BE 8
val blockH = 8 // MUST BE 8
/**
* Converts consecutive lua table indexed from 1 as IntArray.
* The lua table must not contain any nils in the sequence.
*/
fun LuaTable.toIntArray(): IntArray {
val arr = IntArray(this.keyCount())
var k = 1
while (true) {
if (this[k].isnil()) break
arr[k - 1] = this[k].checkint()
k += 1
}
return arr
}
}
val width = termW * blockW
val height = termH * blockH
val spritesCount = 256
val vram = VRAM(width, height, spritesCount)
val frameBuffer = ImageBuffer(width, height)
val frameBufferImage = frameBuffer.image
// hard-coded 8x8
var fontRom = Array<IntArray>(256, { IntArray(blockH) })
var showCursor = true
private var cursorBlinkOn = true
private var cursorBlinkTimer = 0
private val cursorBlinkTime = 250
init {
// build it for first time
resetTextRom()
frameBufferImage.filter = Image.FILTER_NEAREST
}
val CLUT = VRAM.CLUT
val colorsCount = CLUT.size
val luaSpriteTable = LuaTable()
var color = 15 // black
set(value) {
if (value >= colorsCount || value < 0) {
throw IllegalArgumentException("Unknown colour: $value")
}
else {
field = value
}
}
val cursorSprite = ImageBuffer(blockW, blockH * 2)
val cursorImage: Image
override val memSize = 256 * 8 + (width * height * 2) + spritesCount * 16 * 7
// fontRom + framebuffers + sprites
init {
Arrays.fill(cursorSprite.rgba, 0xFF.toByte())
cursorImage = cursorSprite.image
fun composeSpriteObject(spriteIndex: Int) : LuaValue {
val sprite = vram.sprites[spriteIndex]
val t = LuaTable()
t["getColFromPal"] = SpriteGetColFromPal(sprite)
t["setPixel"] = SpriteSetPixel(sprite)
t["setPalSet"] = SpriteSetPaletteSet(sprite)
t["setLine"] = SpriteSetLine(sprite)
t["setAll"] = SpriteSetAll(sprite)
t["setRotation"] = SpriteSetRotation(sprite)
t["setFlipH"] = SpriteSetFlipH(sprite)
t["setFlipV"] = SpriteSetFlipV(sprite)
return t
}
(0..spritesCount - 1).forEach { luaSpriteTable[it + 1] = composeSpriteObject(it) }
}
fun buildFontRom(ref: String) {
// load font rom out of TGA
val imageRef = Image(ref)
val image = imageRef.texture.textureData
val imageWidth = imageRef.width
for (i in 0..255) {
for (y in 0..blockH - 1) {
// letter mirrored horizontally!
var scanline = 0
for (x in 0..blockW - 1) {
val subX = i % 16
val subY = i / 16
val bit = image[4 * ((subY * blockH + y) * imageWidth + blockW * subX + x) + 3] != 0.toByte()
if (bit) scanline = scanline or (1 shl x)
}
fontRom[i][y] = scanline
}
}
}
override fun loadLib(globals: Globals) {
globals["ppu"] = LuaTable()
globals["ppu"]["setTextForeColor"] = SetTextForeColor(this)
globals["ppu"]["getTextForeColor"] = GetTextForeColor(this)
globals["ppu"]["setTextBackColor"] = SetTextBackColor(this)
globals["ppu"]["getTextBackColor"] = GetTextBackColor(this)
globals["ppu"]["setColor"] = SetDrawColor(this)
globals["ppu"]["getColor"] = GetDrawColor(this)
globals["ppu"]["emitChar"] = EmitChar(this)
globals["ppu"]["clearAll"] = ClearAll(this)
globals["ppu"]["clearBack"] = ClearBackground(this)
globals["ppu"]["clearFore"] = ClearForeground(this)
globals["ppu"]["getSpritesCount"] = GetSpritesCount(this)
globals["ppu"]["width"] = GetWidth(this)
globals["ppu"]["height"] = GetHeight(this)
globals["ppu"]["getSprite"] = GetSprite(this)
globals["ppu"]["drawRectBack"] = DrawRectBack(this)
globals["ppu"]["drawRectFore"] = DrawRectFore(this)
globals["ppu"]["fillRectBack"] = FillRectBack(this)
globals["ppu"]["fillRectFore"] = FillRectFore(this)
globals["ppu"]["drawString"] = DrawString(this)
}
private val spriteBuffer = ImageBuffer(VSprite.width * 2, VSprite.height)
fun render(g: Graphics) {
cursorBlinkTimer += Terrarum.deltaTime
if (cursorBlinkTimer > cursorBlinkTime) {
cursorBlinkTimer -= cursorBlinkTime
cursorBlinkOn = !cursorBlinkOn
}
fun VSprite.render() {
if (this.isVisible) {
val h = VSprite.height
val w = VSprite.width
if (rotation and 1 == 0) { // deg 0, 180
(if (rotation == 0 && !vFlip || rotation == 2 && vFlip) 0..h - 1 else h - 1 downTo 0).forEachIndexed { ordY, y ->
(if (rotation == 0 && !hFlip || rotation == 2 && hFlip) 0..w - 1 else w - 1 downTo 0).forEachIndexed { ordX, x ->
val pixelData = data[y].ushr(2 * x).and(0b11)
val col = getColourFromPalette(pixelData)
if (this.drawWide) {
spriteBuffer.setRGBA(ordX * 2, ordY, col.red, col.green, col.blue, col.alpha)
spriteBuffer.setRGBA(ordX * 2 + 1, ordY, col.red, col.green, col.blue, col.alpha)
}
else {
spriteBuffer.setRGBA(ordX, ordY, col.red, col.green, col.blue, col.alpha)
}
}
}
}
else { // deg 90, 270
(if (rotation == 3 && !hFlip || rotation == 1 && hFlip) 0..w - 1 else w - 1 downTo 0).forEachIndexed { ordY, y ->
(if (rotation == 3 && !vFlip || rotation == 1 && vFlip) h - 1 downTo 0 else 0..h - 1).forEachIndexed { ordX, x ->
val pixelData = data[y].ushr(2 * x).and(0b11)
val col = getColourFromPalette(pixelData)
if (this.drawWide) {
spriteBuffer.setRGBA(ordY * 2, ordX, col.red, col.green, col.blue, col.alpha)
spriteBuffer.setRGBA(ordY * 2 + 1, ordX, col.red, col.green, col.blue, col.alpha)
}
else {
spriteBuffer.setRGBA(ordY, ordX, col.red, col.green, col.blue, col.alpha)
}
}
}
}
}
}
System.arraycopy(vram.background.rgba, 0, frameBuffer.rgba, 0, vram.background.rgba.size)
vram.sprites.forEach {
if (it.isBackground) {
it.render()
frameBuffer.softwareRender(spriteBuffer, it.posX, it.posY)
}
}
frameBuffer.softwareRender(vram.foreground, 0, 0)
vram.sprites.forEach {
if (!it.isBackground) {
it.render()
frameBuffer.softwareRender(spriteBuffer, it.posX, it.posY)
}
}
val img = frameBuffer.image
img.filter = Image.FILTER_NEAREST
g.drawImage(img.getScaledCopy(blockW * termW, blockH * termH * 2), 0f, 0f)
if (cursorBlinkOn && showCursor) {
g.drawImage(
cursorImage,
host.term.cursorX * blockW.toFloat(),
(host.term as Terminal).cursorY * blockH * 2f
)
}
// scanlines
g.color = Color(0, 0, 0, 40)
g.lineWidth = 1f
for (i in 1..blockH * termH * 2 step 2) {
g.drawLine(0f, i.toFloat(), blockW * termW - 1f, i.toFloat())
}
img.destroy()
}
private var textColorFore = 49 // white
private var textColorBack = 64 // transparent
fun drawChar(c: Char, x: Int, y: Int, colFore: Int = textColorFore, colBack: Int = textColorBack) {
val glyph = fontRom[c.toInt()]
// software render
for (gy in 0..blockH - 1) { for (gx in 0..blockW - 1) {
val glyAlpha = glyph[gy] and (1 shl gx)
if (glyAlpha != 0) {
vram.setForegroundPixel(x + gx, y + gy, colFore)
}
else {
vram.setForegroundPixel(x + gx, y + gy, colBack)
}
}}
}
fun clearBackground() {
for (i in 0..width * height - 1) {
vram.background.rgba[i] = if (i % 4 == 3) 0xFF.toByte() else 0x00.toByte()
}
}
fun clearForeground() {
for (i in 0..width * height - 1) {
vram.foreground.rgba[i] = 0x00.toByte()
}
}
fun clearAll() {
for (i in 0..width * height - 1) {
vram.background.rgba[i] = if (i % 4 == 3) 0xFF.toByte() else 0x00.toByte()
vram.foreground.rgba[i] = 0x00.toByte()
}
}
fun drawRectBack(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
for (it in 0..w - 1) {
vram.setBackgroundPixel(x + it, y, c)
vram.setBackgroundPixel(x + it, y + h - 1, c)
}
for (it in 1..h - 2) {
vram.setBackgroundPixel(x, y + it, c)
vram.setBackgroundPixel(x + w - 1, y + it, c)
}
}
fun fillRectBack(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
for (py in 0..h - 1) { for (px in 0..w - 1) {
vram.setBackgroundPixel(x + px, y + py, c)
}}
}
fun drawRectFore(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
for (it in 0..w - 1) {
vram.setForegroundPixel(x + it, y, c)
vram.setForegroundPixel(x + it, y + h - 1, c)
}
for (it in 1..h - 2) {
vram.setForegroundPixel(x, y + it, c)
vram.setForegroundPixel(x + w - 1, y + it, c)
}
}
fun fillRectFore(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
for (py in 0..h - 1) { for (px in 0..w - 1) {
vram.setForegroundPixel(x + px, y + py, c)
}}
}
fun getSprite(index: Int) = vram.sprites[index]
private fun ImageBuffer.softwareRender(other: ImageBuffer, posX: Int, posY: Int) {
for (y in 0..other.height - 1) {
for (x in 0..other.width - 1) {
val ix = posX + x
val iy = posY + y
if (ix >= 0 && iy >= 0 && ix < this.width && iy < this.height) {
if (other.rgba[4 * (y * other.texWidth + x) + 3] != 0.toByte()) { // if not transparent
this.rgba[4 * (iy * this.texWidth + ix) + 0] = other.rgba[4 * (y * other.texWidth + x) + 0]
this.rgba[4 * (iy * this.texWidth + ix) + 1] = other.rgba[4 * (y * other.texWidth + x) + 1]
this.rgba[4 * (iy * this.texWidth + ix) + 2] = other.rgba[4 * (y * other.texWidth + x) + 2]
this.rgba[4 * (iy * this.texWidth + ix) + 3] = other.rgba[4 * (y * other.texWidth + x) + 3]
}
}
}
}
}
/**
* Array be like, in binary; notice that glyphs are flipped horizontally:
* ...
* 00011000
* 00011100
* 00011000
* 00011000
* 00011000
* 00011000
* 01111111
* 00000000
* 00111111
* 01100011
* 01100000
* 00111111
* 00000011
* 00000011
* 01111111
* 00000000
* ...
*/
fun setTextRom(data: Array<Int>) {
for (i in 0..255) {
for (y in 0..blockH - 1) {
// letter mirrored horizontally!
fontRom[i][y] = data[blockH * i + y]
}
}
}
fun resetTextRom() {
buildFontRom("./assets/graphics/fonts/milky.tga")
}
///////////////////
// Lua functions //
///////////////////
class SetTextForeColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
videoCard.textColorFore = arg.checkint()
return LuaValue.NONE
}
}
class GetTextForeColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.textColorFore.toLua()
}
}
class SetTextBackColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
videoCard.textColorBack = arg.checkint()
return LuaValue.NONE
}
}
class GetTextBackColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.textColorBack.toLua()
}
}
class SetDrawColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
videoCard.color = arg.checkint()
return LuaValue.NONE
}
}
class GetDrawColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.color.toLua()
}
}
class GetWidth(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.width.toLua()
}
}
class GetHeight(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.height.toLua()
}
}
class EmitChar(val videoCard: PeripheralVideoCard) : ThreeArgFunction() {
/** emitChar(char, x, y) */
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
videoCard.drawChar(arg1.checkint().toChar(), arg2.checkint(), arg3.checkint())
return LuaValue.NONE
}
}
class ClearAll(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
videoCard.clearAll()
return LuaValue.NONE
}
}
class ClearBackground(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
videoCard.clearBackground()
return LuaValue.NONE
}
}
class ClearForeground(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
videoCard.clearForeground()
return LuaValue.NONE
}
}
class GetSpritesCount(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
override fun call(): LuaValue {
return videoCard.spritesCount.toLua()
}
}
class GetSprite(val videoCard: PeripheralVideoCard) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
return videoCard.luaSpriteTable[arg.checkint() - 1]
}
}
class DrawRectBack(val videoCard: PeripheralVideoCard) : FourArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
videoCard.drawRectBack(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
return LuaValue.NONE
}
}
class FillRectBack(val videoCard: PeripheralVideoCard) : FourArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
videoCard.fillRectBack(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
return LuaValue.NONE
}
}
class DrawRectFore(val videoCard: PeripheralVideoCard) : FourArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
videoCard.drawRectFore(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
return LuaValue.NONE
}
}
class FillRectFore(val videoCard: PeripheralVideoCard) : FourArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
videoCard.fillRectFore(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
return LuaValue.NONE
}
}
class DrawString(val videoCard: PeripheralVideoCard) : ThreeArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
val str = arg1.checkjstring()
val x = arg2.checkint()
val y = arg3.checkint()
str.forEachIndexed { i, c ->
videoCard.drawChar(c, x + blockW * i, y)
}
return LuaValue.NONE
}
}
/////////////
// Sprites //
/////////////
private class SpriteGetColFromPal(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
return when (arg.checkint()) {
0 -> sprite.pal0.toLua()
1 -> sprite.pal1.toLua()
2 -> sprite.pal2.toLua()
3 -> sprite.pal3.toLua()
else -> throw IndexOutOfBoundsException("Palette size: 4, input: ${arg.checkint()}")
}
}
}
private class SpriteSetPixel(val sprite: VSprite) : ThreeArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
sprite.setPixel(arg1.checkint(), arg2.checkint(), arg3.checkint())
return LuaValue.NONE
}
}
private class SpriteSetPaletteSet(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
sprite.setPaletteSet(arg(1).checkint(), arg(2).checkint(), arg(3).checkint(), arg(4).checkint())
return LuaValue.NONE
}
}
private class SpriteSetLine(val sprite: VSprite) : TwoArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
sprite.setLine(arg1.checkint(), arg2.checktable().toIntArray())
return LuaValue.NONE
}
}
private class SpriteSetAll(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
sprite.setAll(arg.checktable().toIntArray())
return LuaValue.NONE
}
}
private class SpriteSetRotation(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
sprite.rotation = arg.checkint()
return LuaValue.NONE
}
}
private class SpriteSetFlipH(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
sprite.hFlip = arg.checkboolean()
return LuaValue.NONE
}
}
private class SpriteSetFlipV(val sprite: VSprite) : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
sprite.vFlip = arg.checkboolean()
return LuaValue.NONE
}
}
}
class VRAM(pxlWidth: Int, pxlHeight: Int, nSprites: Int) {
val sprites = Array(nSprites, { VSprite() })
val background = ImageBuffer(pxlWidth, pxlHeight)
val foreground = ImageBuffer(pxlWidth, pxlHeight) // text mode glyphs rendered here
companion object {
val CLUT = DecodeTapestry.colourIndices64 + Color(0, 0, 0, 0)
}
fun setBackgroundPixel(x: Int, y: Int, color: Int) {
val col = CLUT[color]
background.setRGBA(x, y, col.red, col.green, col.blue, 255)
}
fun setForegroundPixel(x: Int, y: Int, color: Int) {
val col = CLUT[color]
foreground.setRGBA(x, y, col.red, col.green, col.blue, col.alpha)
}
}
class VSprite {
companion object {
val width = 8
val height = 8
}
internal val CLUT = VRAM.CLUT
internal val data = IntArray(height)
var pal0 = 64 // transparent
var pal1 = 56 // light cyan
var pal2 = 19 // magenta
var pal3 = 49 // white
var posX = 0
var posY = 0
var hFlip = false
var vFlip = false
var rotation = 0
set(value) { field = value % 4 }
var isBackground = false
var isVisible = false
var drawWide = false
fun setPaletteSet(col0: Int, col1: Int, col2: Int, col3: Int) {
pal0 = col0
pal1 = col1
pal2 = col2
pal3 = col3
}
fun getColourFromPalette(swatchNumber: Int): Color {
val clutIndex = when (swatchNumber) {
0 -> pal0
1 -> pal1
2 -> pal2
3 -> pal3
else -> throw IndexOutOfBoundsException("Palette size: 4, input: $swatchNumber")
}
return CLUT[clutIndex]
}
fun setPos(x: Int, y: Int) {
posX = x
posY = y
}
fun setPixel(x: Int, y: Int, color: Int) {
data[y] = data[y] xor data[y].and(3 shl (2 * x)) // mask off desired area to 0b00
data[y] = data[y] or (color shl (2 * x))
}
fun setLine(y: Int, rowData: IntArray) {
for (i in 0..width - 1) {
setPixel(i, y, rowData[i])
}
}
fun setAll(data: IntArray) {
for (i in 0..width * height - 1) {
setPixel(i % width, i / width, data[i])
}
}
}*/
abstract class FourArgFunction : LibFunction() {
abstract override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue
override fun call(): LuaValue {
return call(LuaValue.NIL, LuaValue.NIL, LuaValue.NIL, LuaValue.NIL)
}
override fun call(arg: LuaValue): LuaValue {
return call(arg, LuaValue.NIL, LuaValue.NIL, LuaValue.NIL)
}
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
return call(arg1, arg2, LuaValue.NIL, LuaValue.NIL)
}
override fun invoke(varargs: Varargs): Varargs {
return call(varargs.arg1(), varargs.arg(2), varargs.arg(3), varargs.arg(4))
}
}

View File

@@ -0,0 +1,305 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
/**
* Printing text using Term API triggers 'compatibility' mode, where you are limited to 16 colours.
* Use PPU API for full 64 colours!
*
* Created by minjaesong on 2017-02-08.
*/
/*class GraphicsTerminal(private val host: TerrarumComputer) : Terminal {
lateinit var videoCard: PeripheralVideoCard
override val width: Int; get() = videoCard.termW
override val height: Int; get() = videoCard.termH
override val coloursCount: Int; get() = videoCard.colorsCount
override var cursorX = 0
override var cursorY = 0
override var cursorBlink = true
val backDefault = 0 // black
val foreDefault = 1 // white
override var backColour = backDefault
override var foreColour = foreDefault
private val colourKey: Int
get() = backColour.shl(4) or (foreColour).and(0xFF)
override fun getColor(index: Int) = videoCard.CLUT[index]
override val displayW: Int; get() = videoCard.width //+ 2 * borderSize
override val displayH: Int; get() = videoCard.height //+ 2 * borderSize
private lateinit var videoScreen: Image
var TABSIZE = 4
val errorColour = 6
override fun printChars(s: String) {
printString(s, cursorX, cursorY)
}
override fun update(gc: GameContainer, delta: Int) {
wrap()
}
// copied from SimpleTextTerminal
private fun wrap() {
// wrap cursor
if (cursorX < 0 && cursorY <= 0) {
setCursor(0, 0)
}
else if (cursorX >= width) {
setCursor(0, cursorY + 1)
}
else if (cursorX < 0) {
setCursor(width - 1, cursorY - 1)
}
// auto scroll up
if (cursorY >= height) {
scroll()
}
}
override fun render(gc: GameContainer, g: Graphics) {
videoCard.render(g)
}
override fun keyPressed(key: Int, c: Char) {
host.keyPressed(key, c)
}
override fun writeChars(s: String) {
writeString(s, cursorX, cursorY)
}
/** Unlike lua function, this one in Zero-based. */
override fun setCursor(x: Int, y: Int) {
cursorX = x
cursorY = y
}
override fun emitChar(bufferChar: Int, x: Int, y: Int) {
videoCard.drawChar(
bufferChar.and(0xFF).toChar(),
x * PeripheralVideoCard.blockW,
y * PeripheralVideoCard.blockH,
CLUT16_TO_64[bufferChar.ushr(8).and(0xF)],
CLUT16_TO_64[bufferChar.ushr(12).and(0xF)]
)
}
override fun emitChar(c: Char, xx: Int, yy: Int) {
wrap() // needed
var nx = xx
var ny = yy
// wrap argument cursor
if (xx < 0 && yy <= 0) {
nx = 0
ny = 0
}
else if (cursorX >= width) {
println("arstenioarstoneirastneo")
nx = 0
ny += 1
}
else if (cursorX < 0) {
nx = width - 1
ny -= 1
}
// auto scroll up
if (cursorY >= height) {
scroll()
ny -= 1
}
println("xx: $xx, yy: $yy")
println("nx: $nx, ny: $ny")
videoCard.drawChar(
c,
nx * PeripheralVideoCard.blockW,
ny * PeripheralVideoCard.blockH,
CLUT16_TO_64[foreColour]
)
}
override fun printChar(c: Char) {
if (c >= ' ' && c.toInt() != 127) {
emitChar(c, cursorX, cursorY)
cursorX += 1
}
else {
if (BACKSPACE.contains(c)) {
cursorX -= 1
//wrap()
emitChar(0.toChar(), cursorX, cursorY)
}
else {
when (c) {
ASCII_BEL -> bell(".")
ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
ASCII_LF -> { newLine(); System.err.println("LF ${Random().nextInt(100)}") }
ASCII_FF -> clear()
ASCII_CR -> { cursorX = 0 }
ASCII_DC1, ASCII_DC2, ASCII_DC3,
ASCII_DC4 -> { foreColour = c - ASCII_DC1 }
ASCII_DLE -> { foreColour = errorColour }
}
}
}
}
override fun emitString(s: String, x: Int, y: Int) {
setCursor(x, y)
for (i in 0..s.length - 1) {
printChar(s[i])
}
setCursor(x, y)
}
override fun printString(s: String, x: Int, y: Int) {
writeString(s, x, y)
newLine()
}
override fun writeString(s: String, x: Int, y: Int) {
setCursor(x, y)
for (i in 0..s.length - 1) {
printChar(s[i])
}
}
override fun clear() {
videoCard.clearForeground()
}
override fun clearLine() {
//TODO("not implemented")
}
override fun newLine() {
cursorX = 0; cursorY += 1
//wrap()
}
override fun scroll(amount: Int) {
val rgba = videoCard.vram.foreground.rgba
val displacement = amount.abs() * PeripheralVideoCard.blockH * videoCard.vram.foreground.texWidth * 4
if (amount >= 0) {
System.arraycopy(
rgba, displacement,
rgba, 0,
rgba.size - displacement
)
for (it in rgba.size - 1 downTo rgba.size - displacement + 1) { rgba[it] = 0.toByte() }
}
else {
System.arraycopy(
rgba, 0,
rgba, displacement,
rgba.size - displacement
)
for (it in 0..displacement - 1) { rgba[it] = 0.toByte() }
}
cursorY += -amount
}
/**
* does not changes color setting in PPU
*/
override fun setColour(back: Int, fore: Int) {
foreColour = fore
backColour = back
}
/**
* does not changes color setting in PPU
*/
override fun resetColour() {
foreColour = foreDefault
backColour = backDefault
}
/** // copied from SimpleTextTerminal
* @param duration: milliseconds
* @param freg: Frequency (float)
*/
override fun emitTone(duration: Int, freq: Double) {
host.clearBeepQueue()
host.enqueueBeep(duration, freq)
}
// copied from SimpleTextTerminal
/** for "emitTone code" on modern BIOS. */
override fun bell(pattern: String) {
host.clearBeepQueue()
val freq: Double =
if (host.luaJ_globals["computer"]["bellpitch"].isnil())
1000.0
else
host.luaJ_globals["computer"]["bellpitch"].checkdouble()
for (c in pattern) {
when (c) {
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0.0) }
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0.0) }
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0.0) }
' ' -> { host.enqueueBeep(200, 0.0) }
',' -> { host.enqueueBeep(50, 0.0) }
else -> throw IllegalArgumentException("Unacceptable pattern: $c (from '$pattern')")
}
}
}
companion object {
private val WHITE7500 = Color(0xe4eaff)
val ASCII_NUL = 0.toChar()
val ASCII_BEL = 7.toChar() // *BEEP!*
val ASCII_TAB = 9.toChar() // move cursor to next (TABSIZE * yy) pos (5 -> 8, 3- > 4, 4 -> 8)
val ASCII_LF = 10.toChar() // new line
val ASCII_FF = 12.toChar() // new page
val ASCII_CR = 13.toChar() // x <- 0
val BACKSPACE = arrayOf(127.toChar(), 8.toChar()) // backspace and delete char (8 for WIN, 127 for OSX)
val ASCII_DC1 = 17.toChar() // foreground colour 0
val ASCII_DC2 = 18.toChar() // foreground colour 1
val ASCII_DC3 = 19.toChar() // foreground colour 2
val ASCII_DC4 = 20.toChar() // foreground colour 3
val ASCII_DLE = 16.toChar() // error message colour
val asciiControlInUse = charArrayOf(
ASCII_NUL,
ASCII_BEL,
8.toChar(),
ASCII_TAB,
ASCII_LF,
ASCII_FF,
ASCII_CR,
127.toChar(),
ASCII_DC1,
ASCII_DC2,
ASCII_DC3,
ASCII_DC4,
ASCII_DLE
)
val CLUT = DecodeTapestry.colourIndices64
val CLUT16_TO_64 = intArrayOf(
15, 49, 16, 48, 44, 29, 33, 18,
5, 22, 39, 26, 25, 10, 31, 13
)
}
fun attachVideoCard(videocard: PeripheralVideoCard) {
this.videoCard = videocard
videoScreen = Image(videoCard.width, videoCard.height)
}
}*/

View File

@@ -0,0 +1,381 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
/**
* Default text terminal.
*
* Created by minjaesong on 2016-09-07.
*/
/*open class SimpleTextTerminal(
phosphorColour: Color, override val width: Int, override val height: Int, private val host: TerrarumComputer,
colour: Boolean = false, hires: Boolean = false
) : Terminal {
private val DEBUG = false
/**
* Terminals must support AT LEAST 4 colours.
* Color index 0 must be default background, index 3 must be default foreground
*/
open protected val colours = if (colour) CLUT else CLUT.copyOfRange(0, 4)
val phosphor = if (colour) WHITE7500 else phosphorColour
open val colourScreen = if (colour) Color(8, 8, 8) else Color(19, 19, 19)
override val coloursCount: Int
get() = colours.size
val errorColour = if (coloursCount > 4) 6 else 1
open val backDefault = 0 // STANDARD
open val foreDefault = 3 // STANDARD
override var backColour = backDefault
override var foreColour = foreDefault
private val colourKey: Int
get() = backColour.shl(4) or (foreColour).and(0xFF)
override var cursorX = 0
override var cursorY = 0
override var cursorBlink = true
val screenBuffer = AAFrame(width, height, this)
open protected val fontRef =
"./assets/graphics/fonts/${
if (hires) "milky.tga"
else if (phosphor == GREEN || phosphor == AMBER) "MDA.tga"
else "milkymda.tga"
}"
open protected val fontImg = Image(fontRef)
open val fontW = fontImg.width / 16
open val fontH = fontImg.height / 16
open protected val font = SpriteSheetFont(SpriteSheet(fontRef, fontW, fontH), 0.toChar())
val borderSize = 20
override val displayW = fontW * width + 2 * borderSize
override val displayH = fontH * height + 2 * borderSize
var TABSIZE = 4
private var cursorBlinkTimer = 0
private val cursorBlinkLen = 250
var cursorBlinkOn = true
private set
override fun getColor(index: Int): Color = colours[index]
override fun update(gc: GameContainer, delta: Int) {
cursorBlinkTimer = cursorBlinkTimer.plus(delta)
if (cursorBlinkTimer > cursorBlinkLen) {
cursorBlinkTimer -= cursorBlinkLen
cursorBlinkOn = !cursorBlinkOn
}
wrap()
}
private fun wrap() {
// wrap cursor
if (cursorX < 0 && cursorY <= 0) {
setCursor(0, 0)
}
else if (cursorX >= width) {
setCursor(0, cursorY + 1)
}
else if (cursorX < 0) {
setCursor(width - 1, cursorY - 1)
}
// auto scroll up
if (cursorY >= height) {
scroll()
}
}
/**
* pass UIcanvas to the parameter "g"
*/
override fun render(gc: GameContainer, g: Graphics) {
g.font = font
blendNormal()
// black background (this is mandatory)
g.color = Color.black
g.fillRect(0f, 0f, displayW.toFloat(), displayH.toFloat())
// screen buffer
for (y in 0..height - 1) {
for (x in 0..width - 1) {
val ch = screenBuffer.getChar(x, y)
// background
g.color = getColor(screenBuffer.getBackgroundColour(x, y))
g.fillRect(fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize,
fontW.toFloat(), fontH.toFloat())
// foreground
if (ch.toInt() != 0 && ch.toInt() != 32) {
g.color = getColor(screenBuffer.getForegroundColour(x, y))
g.drawString(
Character.toString(ch),
fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize)
}
}
}
// cursor
if (cursorBlinkOn) {
g.color = getColor(if (cursorBlink) foreDefault else backDefault)
g.fillRect(
fontW * cursorX.toFloat() + borderSize,
fontH * cursorY.toFloat() + borderSize,
fontW.toFloat(),
fontH.toFloat()
)
}
// colour base
g.color = colourScreen
blendScreen()
g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize)
// colour overlay
g.color = phosphor
blendMul()
g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize)
blendNormal()
}
/** Unlike lua function, this one in Zero-based. */
override fun setCursor(x: Int, y: Int) {
cursorX = x
cursorY = y
}
/** Emits a bufferChar. Does not move cursor
* It is also not affected by the control sequences; just print them out as symbol */
override fun emitChar(bufferChar: Int, x: Int, y: Int) {
try { screenBuffer.drawBuffer(x, y, bufferChar.toChar()) }
catch (e: ArrayIndexOutOfBoundsException) { e.printStackTrace() }
}
/** Emits a char. Does not move cursor
* It is also not affected by the control sequences; just print them out as symbol */
override fun emitChar(c: Char, x: Int, y: Int) {
try { screenBuffer.drawBuffer(x, y, c.toInt().and(0xFF).toChar(), colourKey) }
catch (e: ArrayIndexOutOfBoundsException) { e.printStackTrace() }
}
/** Prints a char and move cursor accordingly. */
override fun printChar(c: Char) {
wrap()
if (c >= ' ' && c.toInt() != 127) {
emitChar(c, cursorX, cursorY)
cursorX += 1
}
else {
when (c) {
ASCII_BEL -> bell(".")
ASCII_BS -> { cursorX -= 1; wrap() }
ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
ASCII_LF -> newLine()
ASCII_FF -> clear()
ASCII_CR -> { cursorX = 0 }
ASCII_DEL -> { cursorX -= 1; wrap(); emitChar(colourKey.shl(8), cursorX, cursorY) }
ASCII_DC1, ASCII_DC2, ASCII_DC3, ASCII_DC4 -> { foreColour = c - ASCII_DC1 }
ASCII_DLE -> { foreColour = errorColour }
}
}
}
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
* (term): printString() on current cursor pos */
override fun printChars(s: String) {
printString(s, cursorX, cursorY)
}
/** (TTY): Prints a series of chars and move cursor accordingly
* (term): writeString() on current cursor pos */
override fun writeChars(s: String) {
writeString(s, cursorX, cursorY)
}
/** Emits a string and move cursor accordingly, then do LF */
override fun printString(s: String, x: Int, y: Int) {
writeString(s, x, y)
newLine()
}
/** Emits a string and move cursor accordingly. */
override fun writeString(s: String, x: Int, y: Int) {
setCursor(x, y)
for (i in 0..s.length - 1) {
printChar(s[i])
wrap()
}
}
/** Emits a string, does not affected by control sequences. Does not move cursor */
override fun emitString(s: String, x: Int, y: Int) {
setCursor(x, y)
for (i in 0..s.length - 1) {
printChar(s[i])
wrap()
}
setCursor(x, y)
}
override fun clear() {
screenBuffer.clear(backColour)
cursorX = 0
cursorY = 0
}
override fun clearLine() {
for (i in 0..width - 1)
screenBuffer.drawBuffer(i, cursorY, 0.toChar(), colourKey)
}
override fun newLine() {
cursorX = 0; cursorY += 1; wrap()
}
override fun scroll(amount: Int) {
val offset = amount.times(width).abs()
val bsize = screenBuffer.sizeof.ushr(1)
if (amount == 0) return
if (amount > 0) {
for (i in 0..bsize - 1) {
if (i < Math.min(bsize, bsize - offset - 1))
// displace contents in buffer
screenBuffer.frameBuffer[i] = screenBuffer.frameBuffer[i + offset]
else
// clean up garbages
screenBuffer.frameBuffer[i] = 0.toChar()
}
cursorY -= amount
}
else {
for (i in bsize - 1 downTo 0) {
if (i > Math.max(offset - 1, 0))
// displace contents in buffer
screenBuffer.frameBuffer[i] = screenBuffer.frameBuffer[i - offset]
else
screenBuffer.frameBuffer[i] = 0.toChar()
}
cursorY += amount
}
}
override fun setColour(back: Int, fore: Int) {
backColour = back
foreColour = fore
}
override fun resetColour() {
backColour = backDefault
foreColour = foreDefault
}
/**
* @param duration: milliseconds
* @param freg: Frequency (float)
*/
override fun emitTone(duration: Int, freq: Double) {
// println("!! Beep playing row $beepCursor, d ${Math.min(duration, maxDuration)} f $freq")
host.clearBeepQueue()
host.enqueueBeep(duration, freq)
}
/** for "emitTone code" on modern BIOS. */
override fun bell(pattern: String) {
host.clearBeepQueue()
val freq: Double =
if (host.luaJ_globals["computer"]["bellpitch"].isnil())
1000.0
else
host.luaJ_globals["computer"]["bellpitch"].checkdouble()
for (c in pattern) {
when (c) {
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0.0) }
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0.0) }
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0.0) }
' ' -> { host.enqueueBeep(200, 0.0) }
',' -> { host.enqueueBeep(50, 0.0) }
else -> throw IllegalArgumentException("Unacceptable pattern: $c (from '$pattern')")
}
}
}
override fun keyPressed(key: Int, c: Char) {
host.keyPressed(key, c)
}
private fun isOOB(x: Int, y: Int) =
(x < 0 || y < 0 || x >= width || y >= height)
companion object {
val AMBER = Color(255, 183, 0) // P3, 602 nm
val GREEN = Color(74, 255, 0) // P39, 525 nm
val WHITE = Color(204, 223, 255) // approximation of white CRT I own
private val WHITE7500 = Color(0xe4eaff)
val BLUE_NOVELTY = Color(0x27d7ff)
val RED = Color(250, 51, 0) // 632 nm
val AMETHYST_NOVELTY = Color(0xc095ff)
val ASCII_NUL = 0.toChar()
val ASCII_BEL = 7.toChar() // *BEEP!*
val ASCII_BS = 8.toChar() // x = x - 1
val ASCII_TAB = 9.toChar() // move cursor to next (TABSIZE * yy) pos (5 -> 8, 3- > 4, 4 -> 8)
val ASCII_LF = 10.toChar() // new line
val ASCII_FF = 12.toChar() // new page
val ASCII_CR = 13.toChar() // x <- 0
val ASCII_DEL = 127.toChar() // backspace and delete char
val ASCII_DC1 = 17.toChar() // foreground colour 0
val ASCII_DC2 = 18.toChar() // foreground colour 1
val ASCII_DC3 = 19.toChar() // foreground colour 2
val ASCII_DC4 = 20.toChar() // foreground colour 3
val ASCII_DLE = 16.toChar() // error message colour
val asciiControlInUse = charArrayOf(
ASCII_NUL,
ASCII_BEL,
ASCII_BS,
ASCII_TAB,
ASCII_LF,
ASCII_FF,
ASCII_CR,
ASCII_DEL,
ASCII_DC1,
ASCII_DC2,
ASCII_DC3,
ASCII_DC4,
ASCII_DLE
)
val CLUT = DecodeTapestry.colourIndices16
}
}*/
class ALException(errorCode: Int) : Exception("ALerror: $errorCode") {
}

View File

@@ -0,0 +1,37 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
import com.badlogic.gdx.graphics.g2d.SpriteBatch
/**
* Created by minjaesong on 2016-09-14.
*/
interface Teletype {
val width: Int
val displayW: Int
var cursorX: Int
/**
* 0: Teletype
* 4: Non-colour terminal (Note that '2' is invalid!)
* >4: Colour terminal
*/
val coloursCount: Int
fun update(delta: Float)
fun render(batch: SpriteBatch)
fun keyPressed(key: Int, c: Char)
/** Prints a char and move cursor accordingly */
fun printChar(c: Char)
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
* (term): printString() on current cursor pos */
fun printChars(s: String)
/** (TTY): Prints a series of chars and move cursor accordingly
* (term): writeString() on current cursor pos */
fun writeChars(s: String)
fun newLine()
fun scroll(amount: Int = 1)
fun bell(pattern: String = ".")
}

View File

@@ -0,0 +1,205 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
import net.torvald.terrarumsansbitmap.slick2d.GameFontBase
import net.torvald.terrarum.blendMul
import net.torvald.terrarum.blendNormal
import java.util.*
/**
* Created by minjaesong on 2016-09-15.
*/
/*class TeletypeTerminal : Teletype {
override val width = 40
override val displayW: Int
get() = width * font.W
/**
* 0: Teletype
* 4: Non-colour terminal (Note that '2' is invalid!)
* >4: Colour terminal
*/
override val coloursCount = 0
override var cursorX = 0
private val font = TTYFont()
private var lineBuffer = StringBuilder()
private var currentJob = 0
private var currentJobStep = 0
private var currentJobLen = 0
private var currentJobQueue: Any? = null
// make sure certain jobs deliberately take long time by doing them one-by-one, for each frame.
private val JOB_IDLE = 0
private val JOB_MOVEHEAD = 1
private val JOB_PRINTCHAR = 2
private val JOB_LINEFEED = 3
override fun update(gc: GameContainer, delta: Int) {
wrap()
/*when (currentJob) {
JOB_PRINTCHAR -> {
printChar((currentJobQueue!! as String)[currentJobStep])
currentJobStep += 1
}
JOB_LINEFEED -> {
newLine()
currentJobStep += 1
}
}*/
if (currentJobStep > currentJobLen) {
currentJob = JOB_IDLE
currentJobLen = 0
currentJobStep = 0
currentJobQueue = null
}
}
override fun render(gc: GameContainer, g: Graphics) {
g.font = font
g.drawString(lineBuffer.toString(), 0f, 0f)
}
val TABSIZE = 4
/** Prints a char and move cursor accordingly */
override fun printChar(c: Char) {
wrap()
if (c >= ' ' && c.toInt() != 127) {
lineBuffer.append(c)
cursorX += 1
}
else {
when (c) {
SimpleTextTerminal.ASCII_BEL -> bell()
SimpleTextTerminal.ASCII_BS -> { cursorX -= 1; wrap() }
SimpleTextTerminal.ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
SimpleTextTerminal.ASCII_LF -> newLine()
SimpleTextTerminal.ASCII_FF -> newLine()
SimpleTextTerminal.ASCII_CR -> { cursorX = 0 }
SimpleTextTerminal.ASCII_DEL -> { } // NOT supported, do nothing
SimpleTextTerminal.ASCII_DC1, SimpleTextTerminal.ASCII_DC2, SimpleTextTerminal.ASCII_DC3, SimpleTextTerminal.ASCII_DC4
-> { } // NOT supported, do nothing
}
}
}
/** (TTY): Prints a series of chars and move cursor accordingly
* (term): writeString() on current cursor pos */
override fun writeChars(s: String) {
/*currentJob = JOB_PRINTCHAR
currentJobLen = s.length
currentJobQueue = s*/
for (i in 0..s.length - 1)
printChar(s[i])
}
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
* (term): writeString() on current cursor pos */
override fun printChars(s: String) {
/*currentJob = JOB_PRINTCHAR
currentJobLen = s.length + 1
currentJobQueue = "$s\n"*/
writeChars("$s\n")
}
override fun newLine() {
lineBuffer = StringBuilder()
}
override fun scroll(amount: Int) {
if (amount < 0) throw IllegalArgumentException("cannot scroll up")
if (amount == 1) { newLine(); return }
currentJob = JOB_LINEFEED
currentJobLen = amount
}
override fun bell(pattern: String) {
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private fun wrap() {
if (cursorX < 0) cursorX = 0
else if (cursorX >= width) newLine()
}
var sb: StringBuilder = StringBuilder()
private var inputOpen = false
val DEBUG = true
override fun keyPressed(key: Int, c: Char) {
if (inputOpen) {
if (c == SimpleTextTerminal.ASCII_CR)
printChar(SimpleTextTerminal.ASCII_LF)
else
printChar(c)
if (!SimpleTextTerminal.asciiControlInUse.contains(c)) sb.append(c)
else if (c == SimpleTextTerminal.ASCII_DEL && sb.length > 0) sb.deleteCharAt(sb.length - 1)
}
}
class TTYFont : Font {
internal val fontSheet: SpriteSheet
internal val W = 9
internal val H = 12
private val chars = arrayOf(
'0','1','2','3','4','5','6','7',
'8','9','[','#','@',':','>','?',
' ','A','B','C','D','E','F','G',
'H','I','&','.',']','(','<','\\',
'^','J','K','L','M','N','O','P', // ^: escape for capital letter
'Q','R','-','¤','*',')',';','\'',
'+','/','S','T','U','V','W','X',
'Y','Z','_',',','%','=','"','!'
)
private val mappingTable = HashMap<Int, Int>()
init {
fontSheet = SpriteSheet("./assets/graphics/fonts/teletype_9x12.tga", W, H)
chars.forEachIndexed { i, c -> mappingTable[c.toInt()] = i }
}
override fun getHeight(str: String): Int = H
override fun getWidth(str: String): Int {
var ret = 0
for (i in 0..str.length - 1)
ret += W
return ret
}
override fun getLineHeight(): Int = H
override fun drawString(x: Float, y: Float, text: String) = drawString(x, y, text, Color.white)
override fun drawString(x: Float, y: Float, text: String, col: Color) {
var thisCol = col
var textPosOffset = 0
for (i in 0..text.length - 1) {
val index = charToSpriteNum(text.toUpperCase().codePointAt(i))
val ch = text[i]
if (index != null) {
// main
fontSheet.getSubImage(index % 8, index / 8).draw(
x + textPosOffset, y, thisCol
)
}
textPosOffset += W
}
}
override fun drawString(x: Float, y: Float, text: String, col: Color, startIndex: Int, endIndex: Int) {
throw UnsupportedOperationException()
}
private fun charToSpriteNum(ch: Int): Int? = mappingTable[ch]
}
}*/

View File

@@ -0,0 +1,64 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.Second
/**
* A terminal
*
* Framebuffer: USE net.torvald.aa.AAFrame
*
* Background color is fixed; text color is variable
*
* Created by minjaesong on 2016-09-07.
*/
interface Terminal : net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype {
override val width: Int
val height: Int
override val coloursCount: Int
override var cursorX: Int
var cursorY: Int
var cursorBlink: Boolean
var backColour: Int
var foreColour: Int
// to be used in UI
override val displayW: Int
val displayH: Int
fun getColor(index: Int): Color
override fun update(delta: Float)
override fun render(batch: SpriteBatch)
override fun keyPressed(key: Int, c: Char)
// API calls
fun setCursor(x: Int, y: Int)
/** Emits a bufferChar. Does not move cursor
* It is also not affected by the control sequences; just print them out as symbol */
fun emitChar(bufferChar: Int, x: Int, y: Int)
/** Emits a char. Does not move cursor
* It is also not affected by the control sequences; just print them out as symbol */
fun emitChar(c: Char, x: Int, y: Int)
/** Prints a char and move cursor accordingly. */
override fun printChar(c: Char)
/** Emits a string, does not affected by control sequences. Does not move cursor */
fun emitString(s: String, x: Int, y: Int)
/** Emits a string and move cursor accordingly, then do LF */
fun printString(s: String, x: Int, y: Int)
/** Emits a string and move cursor accordingly. */
fun writeString(s: String, x: Int, y: Int)
fun clear()
fun clearLine()
override fun newLine()
override fun scroll(amount: Int)
fun setColour(back: Int, fore: Int)
fun resetColour()
/**
* @param duration: milliseconds
* @param freg: Frequency (float)
*/
fun emitTone(duration: Second, freq: Double)
override fun bell(pattern: String)
}

View File

@@ -0,0 +1,23 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import java.io.InputStream
/**
* Created by minjaesong on 2016-09-10.
*/
class TerminalInputStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : InputStream() {
override fun read(): Int {
//System.err.println(Thread.currentThread().name)
// would display "LuaJ Separated", which means this InputStream will not block main thread
host.openStdin()
synchronized(this) {
(this as java.lang.Object).wait()
}
return host.stdinInput
}
}

View File

@@ -0,0 +1,14 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
import java.io.OutputStream
import java.io.PrintStream
/**
* Created by minjaesong on 2016-09-10.
*/
class TerminalPrintStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : PrintStream(TerminalOutputStream(host))
class TerminalOutputStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OutputStream() {
override fun write(b: Int) = host.term.printChar(b.and(0xFF).toChar())
}

View File

@@ -0,0 +1,157 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
/**
* ByteArray that can hold larger than 2 GiB of Data.
*
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
*
* Created by Minjaesong on 2017-04-12.
*/
class ByteArray64(val size: Long) {
companion object {
val bankSize: Int = 8192
}
private val data: Array<ByteArray>
init {
if (size < 0)
throw IllegalArgumentException("Invalid array size!")
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
data = Array<ByteArray>(
requiredBanks,
{ bankIndex ->
kotlin.ByteArray(
if (bankIndex == requiredBanks - 1)
size.toBankOffset()
else
bankSize,
{ 0.toByte() }
)
}
)
}
private fun Long.toBankNumber(): Int = (this / bankSize).toInt()
private fun Long.toBankOffset(): Int = (this % bankSize).toInt()
operator fun set(index: Long, value: Byte) {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
data[index.toBankNumber()][index.toBankOffset()] = value
}
operator fun get(index: Long): Byte {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
return data[index.toBankNumber()][index.toBankOffset()]
}
operator fun iterator(): ByteIterator {
return object : ByteIterator() {
var iterationCounter = 0L
override fun nextByte(): Byte {
iterationCounter += 1
return this@ByteArray64[iterationCounter - 1]
}
override fun hasNext() = iterationCounter < this@ByteArray64.size
}
}
fun iteratorChoppedToInt(): IntIterator {
return object : IntIterator() {
var iterationCounter = 0L
val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt()
override fun nextInt(): Int {
var byteCounter = iterationCounter * 4L
var int = 0
(0..3).forEach {
if (byteCounter + it < this@ByteArray64.size) {
int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8)
}
else {
int += 0 shl (it * 8)
}
}
iterationCounter += 1
return int
}
override fun hasNext() = iterationCounter < iteratorSize
}
}
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
fun forEachBanks(consumer: (ByteArray) -> Unit) = data.forEach(consumer)
fun sliceArray64(range: LongRange): ByteArray64 {
val newarr = ByteArray64(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index]
}
return newarr
}
fun sliceArray(range: IntRange): ByteArray {
val newarr = ByteArray(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index.toLong()]
}
return newarr
}
fun toByteArray(): ByteArray {
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
throw TypeCastException("Impossible cast; too large to fit")
return ByteArray(this.size.toInt(), { this[it.toLong()] })
}
fun writeToFile(file: File) {
var fos = FileOutputStream(file, false)
fos.write(data[0])
fos.flush()
fos.close()
if (data.size > 1) {
fos = FileOutputStream(file, true)
for (i in 1..data.lastIndex) {
fos.write(data[i])
fos.flush()
}
fos.close()
}
}
}
class ByteArray64InputStream(val byteArray64: ByteArray64): InputStream() {
private var readCounter = 0L
override fun read(): Int {
readCounter += 1
return try {
byteArray64[readCounter - 1].toUint()
}
catch (e: ArrayIndexOutOfBoundsException) {
-1
}
}
}

View File

@@ -0,0 +1,102 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import java.io.File
import java.io.FileInputStream
/**
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
*
* Created by minjaesong on 2017-11-17.
*/
class DiskSkimmer(diskFile: File) {
class EntryOffsetPair(val entryID: Int, val offset: Long)
val entryToOffsetTable = ArrayList<EntryOffsetPair>()
init {
val fis = FileInputStream(diskFile)
var currentPosition = fis.skip(47) // skip disk header
fun skipRead(bytes: Long) {
currentPosition += fis.skip(bytes)
}
/**
* Reads a byte and adds up the position var
*/
fun readByte(): Byte {
currentPosition++
val read = fis.read()
if (read < 0) throw InternalError("Unexpectedly reached EOF")
return read.toByte()
}
/**
* Reads specific bytes to the buffer and adds up the position var
*/
fun readBytes(buffer: ByteArray): Int {
val readStatus = fis.read(buffer)
currentPosition += readStatus
return readStatus
}
fun readIntBig(): Int {
val buffer = ByteArray(4)
val readStatus = readBytes(buffer)
if (readStatus != 4) throw InternalError("Unexpected error -- EOF reached? (expected 4, got $readStatus)")
return buffer.toIntBig()
}
fun readInt48(): Long {
val buffer = ByteArray(6)
val readStatus = readBytes(buffer)
if (readStatus != 6) throw InternalError("Unexpected error -- EOF reached? (expected 6, got $readStatus)")
return buffer.toInt48()
}
while (true) {
val entryID = readIntBig()
// footer
if (entryID == 0xFEFEFEFE.toInt()) break
// fill up table
entryToOffsetTable.add(EntryOffsetPair(entryID, currentPosition))
skipRead(4) // skip entryID of parent
val entryType = readByte()
skipRead(256 + 6 + 6 + 4) // skips rest of the header
// figure out the entry size so that we can skip
val entrySize: Long = when(entryType) {
0x01.toByte() -> readInt48()
0x11.toByte() -> readInt48() + 6 // size of compressed payload + 6 (header elem for uncompressed size)
0x02.toByte() -> readIntBig().shl(16).toLong() * 4 - 2 // #entris is 2 bytes, we read 4 bytes, so we subtract 2
0x03.toByte() -> 4 // symlink
else -> throw InternalError("Unknown entry type: ${entryType.toUint()}")
}
skipRead(entrySize) // skips rest of the entry's actual contents
}
}
private fun ByteArray.toIntBig(): Int {
return this[0].toUint().shl(24) or this[1].toUint().shl(16) or
this[2].toUint().shl(8) or this[2].toUint()
}
private fun ByteArray.toInt48(): Long {
return this[0].toUlong().shl(56) or this[1].toUlong().shl(48) or
this[2].toUlong().shl(40) or this[3].toUlong().shl(32) or
this[4].toUlong().shl(24) or this[5].toUlong().shl(16) or
this[6].toUlong().shl(8) or this[7].toUlong()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import java.util.zip.CRC32
import kotlin.experimental.and
import kotlin.experimental.or
/**
* Created by minjaesong on 2017-03-31.
*/
typealias EntryID = Int
val specversion = 0x03.toByte()
class VirtualDisk(
/** capacity of 0 makes the disk read-only */
var capacity: Long,
var diskName: ByteArray = ByteArray(NAME_LENGTH),
footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(8) // default to mandatory 8-byte footer
) {
var footerBytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = footer
private set
val entries = HashMap<EntryID, DiskEntry>()
var isReadOnly: Boolean
set(value) { footerBytes[0] = (footerBytes[0] and 0xFE.toByte()) or value.toBit() }
get() = capacity == 0L || (footerBytes.size > 0 && footerBytes[0].and(1) == 1.toByte())
fun getDiskNameString(charset: Charset) = String(diskName, charset)
val root: DiskEntry
get() = entries[0]!!
private fun Boolean.toBit() = if (this) 1.toByte() else 0.toByte()
internal fun __internalSetFooter__(footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) {
footerBytes = footer
}
private fun serializeEntriesOnly(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 {
val bufferList = ArrayList<Byte>() // FIXME this part would take up excessive memory for large files
entries.forEach {
val serialised = it.value.serialize()
serialised.forEach { bufferList.add(it) }
}
val byteArray = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(bufferList.size.toLong())
bufferList.forEachIndexed { index, byte -> byteArray[index.toLong()] = byte }
return byteArray
}
fun serialize(): AppendableByteBuffer {
val entriesBuffer = serializeEntriesOnly()
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE + footerBytes.size)
val crc = hashCode().toBigEndian()
buffer.put(MAGIC)
buffer.put(capacity.toInt48())
buffer.put(diskName.forceSize(NAME_LENGTH))
buffer.put(crc)
buffer.put(specversion)
buffer.put(entriesBuffer)
buffer.put(FOOTER_START_MARK)
buffer.put(footerBytes)
buffer.put(EOF_MARK)
return buffer
}
override fun hashCode(): Int {
val crcList = IntArray(entries.size)
var crcListAppendCursor = 0
entries.forEach { _, u ->
crcList[crcListAppendCursor] = u.hashCode()
crcListAppendCursor++
}
crcList.sort()
val crc = CRC32()
crcList.forEach { crc.update(it) }
return crc.value.toInt()
}
/** Expected size of the virtual disk */
val usedBytes: Long
get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE
fun generateUniqueID(): Int {
var id: Int
do {
id = Random().nextInt()
} while (null != entries[id] || id == FOOTER_MARKER)
return id
}
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})"
companion object {
val HEADER_SIZE = 47L // according to the spec
val FOOTER_SIZE = 6L // footer mark + EOF
val NAME_LENGTH = 32
val MAGIC = "TEVd".toByteArray()
val FOOTER_MARKER = 0xFEFEFEFE.toInt()
val FOOTER_START_MARK = FOOTER_MARKER.toBigEndian()
val EOF_MARK = byteArrayOf(0xFF.toByte(), 0x19.toByte())
}
}
class DiskEntry(
// header
var entryID: EntryID,
var parentEntryID: EntryID,
var filename: ByteArray = ByteArray(NAME_LENGTH),
var creationDate: Long,
var modificationDate: Long,
// content
val contents: DiskEntryContent
) {
fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset)
val serialisedSize: Long
get() = contents.getSizeEntry() + HEADER_SIZE
companion object {
val HEADER_SIZE = 281L // according to the spec
val ROOTNAME = "(root)"
val NAME_LENGTH = 256
val NORMAL_FILE = 1.toByte()
val DIRECTORY = 2.toByte()
val SYMLINK = 3.toByte()
val COMPRESSED_FILE = 0x11.toByte()
private fun DiskEntryContent.getTypeFlag() =
if (this is EntryFile) NORMAL_FILE
else if (this is EntryDirectory) DIRECTORY
else if (this is EntrySymlink) SYMLINK
else 0 // NULL
fun getTypeString(entry: DiskEntryContent) = when(entry.getTypeFlag()) {
NORMAL_FILE -> "File"
DIRECTORY -> "Directory"
SYMLINK -> "Symbolic Link"
else -> "(unknown type)"
}
}
fun serialize(): AppendableByteBuffer {
val serialisedContents = contents.serialize()
val buffer = AppendableByteBuffer(HEADER_SIZE + serialisedContents.size)
buffer.put(entryID.toBigEndian())
buffer.put(parentEntryID.toBigEndian())
buffer.put(contents.getTypeFlag())
buffer.put(filename.forceSize(NAME_LENGTH))
buffer.put(creationDate.toInt48())
buffer.put(modificationDate.toInt48())
buffer.put(this.hashCode().toBigEndian())
buffer.put(serialisedContents.array)
return buffer
}
override fun hashCode() = contents.serialize().getCRC32()
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
override fun toString() = "DiskEntry(name: ${getFilenameString(Charsets.UTF_8)}, index: $entryID, type: ${contents.getTypeFlag()}, crc: ${hashCode().toHex()})"
}
fun ByteArray.forceSize(size: Int): ByteArray {
return ByteArray(size, { if (it < this.size) this[it] else 0.toByte() })
}
interface DiskEntryContent {
fun serialize(): AppendableByteBuffer
fun getSizePure(): Long
fun getSizeEntry(): Long
}
/**
* Do not retrieve bytes directly from this! Use VDUtil.retrieveFile(DiskEntry)
* And besides, the bytes could be compressed.
*/
open class EntryFile(internal var bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : DiskEntryContent {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 6
/** Create new blank file */
constructor(size: Long): this(net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size))
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toInt48())
buffer.put(bytes)
return buffer
}
}
class EntryFileCompressed(internal var uncompressedSize: Long, bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : EntryFile(bytes) {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 12
/* No new blank file for the compressed */
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toInt48())
buffer.put(uncompressedSize.toInt48())
buffer.put(bytes)
return buffer
}
}
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
override fun getSizePure() = entries.size * 4L
override fun getSizeEntry() = getSizePure() + 2
private fun checkCapacity(toAdd: Int = 1) {
if (entries.size + toAdd > 65535)
throw IOException("Directory entries limit exceeded.")
}
fun add(entryID: EntryID) {
checkCapacity()
entries.add(entryID)
}
fun remove(entryID: EntryID) {
entries.remove(entryID)
}
fun contains(entryID: EntryID) = entries.contains(entryID)
fun forEach(consumer: (EntryID) -> Unit) = entries.forEach(consumer)
val entryCount: Int
get() = entries.size
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(entries.size.toShort().toBigEndian())
entries.forEach { indexNumber -> buffer.put(indexNumber.toBigEndian()) }
return buffer
}
companion object {
val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L
}
}
class EntrySymlink(val target: EntryID) : DiskEntryContent {
override fun getSizePure() = 4L
override fun getSizeEntry() = 4L
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(4)
return buffer.put(target.toBigEndian())
}
}
fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').toUpperCase()
fun Int.toBigEndian(): ByteArray {
return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() })
}
fun Long.toInt48(): ByteArray {
return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() })
}
fun Short.toBigEndian(): ByteArray {
return byteArrayOf(
this.div(256).toByte(),
this.toByte()
)
}
fun AppendableByteBuffer.getCRC32(): Int {
val crc = CRC32()
this.array.forEachInt32 { crc.update(it) }
return crc.value.toInt()
}
class AppendableByteBuffer(val size: Long) {
val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size)
private var offset = 0L
fun put(byteArray64: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64): AppendableByteBuffer {
// it's slow but works
// can't do system.arrayCopy directly
byteArray64.forEach { put(it) }
return this
}
fun put(byteArray: ByteArray): AppendableByteBuffer {
byteArray.forEach { put(it) }
return this
}
fun put(byte: Byte): AppendableByteBuffer {
array[offset] = byte
offset += 1
return this
}
fun forEach(consumer: (Byte) -> Unit) = array.forEach(consumer)
}

View File

@@ -0,0 +1,56 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
import java.util.*
/**
* Created by minjaesong on 2016-09-08.
*/
object ComputerPartsCodex {
val rams = HashMap<Int, Int>() // id, capacity in bytes (0 bytes - 8 GBytes)
val processors = HashMap<Int, Int>() // id, cycles
val harddisks = HashMap<Int, Int>() // id, capacity in bytes
val diskettes = HashMap<Int, Int>() // id, capacity in bytes
val opticaldiscs = HashMap<Int, Int>() // id, capacity in bytes
init {
// in kilobytes
rams.put(4864, 128.KiB())
rams.put(4865, 192.KiB())
rams.put(4866, 256.KiB())
rams.put(4867, 384.KiB())
rams.put(4868, 512.KiB())
rams.put(4869, 768.KiB())
rams.put(4870, 1024.KiB())
rams.put(4871, 2048.KiB())
processors.put(4872, 1000)
processors.put(4873, 2000)
processors.put(4874, 4000)
processors.put(4875, 8000) // this is totally OP
harddisks.put(4876, 1.MB())
harddisks.put(4877, 2.MB())
harddisks.put(4878, 5.MB())
harddisks.put(4879, 10.MB())
// Floppy disk: your primitive and only choice of removable storage
diskettes.put(4880, 360.kB()) // single-sided
diskettes.put(4881, 720.kB()) // double-sided
diskettes.put(4882, 1440.kB()) // 3.5" HD
diskettes.put(4883, 2880.kB()) // 3.5" ED
// CD-Rs
opticaldiscs.put(4884, 8.MB()) // arbitrary size
}
fun getRamSize(itemIndex: Int): Int = rams[itemIndex] ?: 0
fun getProcessorCycles(itemIndex: Int): Int = processors[itemIndex] ?: 0
fun getHDDSize(itemIndex: Int): Int = harddisks[itemIndex] ?: 0
fun getFDDSize(itemIndex: Int): Int = diskettes[itemIndex] ?: 0
fun getODDSize(itemIndex: Int): Int = opticaldiscs[itemIndex] ?: 0
private fun Int.MB() = this * 1000000 // 1 MB == 1 000 000 bytes, bitches!
private fun Int.kB() = this * 1000
private fun Int.KiB() = this.shl(10)
private fun Int.MiB() = this.shl(20)
}

View File

@@ -0,0 +1,24 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
import com.badlogic.gdx.graphics.Color
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.gameworld.GameWorld
/**
* Created by minjaesong on 2016-09-08.
*/
class FixtureBasicTerminal(world: GameWorld, phosphor: Color) : FixtureBase(world) {
/*val computer = TerrarumComputer(8)
val vt: Terminal = SimpleTextTerminal(phosphor, 80, 25, computer)
val ui = UITextTerminal(vt)
init {
computer.attachTerminal(vt)
collisionFlag = COLLISION_PLATFORM
actorValue[AVKey.UUID] = UUID.randomUUID().toString()
}*/
}

View File

@@ -0,0 +1,60 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.gameworld.GameWorld
/**
* Created by minjaesong on 2016-09-08.
*/
open class FixtureComputerBase(world: GameWorld) : FixtureBase(world) {
/** Connected terminal */
var terminal: FixtureBasicTerminal? = null
var computerInside: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer? = null
init {
// UUID of the "brain"
actorValue["computerid"] = "none"
collisionFlag = COLLISION_PLATFORM
}
////////////////////////////////////
// get the computer actually work //
////////////////////////////////////
fun attachTerminal(uuid: String) {
val fetchedTerminal = getTerminalByUUID(uuid)
computerInside = net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer(8)
computerInside!!.attachTerminal(fetchedTerminal!!)
actorValue["computerid"] = computerInside!!.UUID
}
fun detatchTerminal() {
terminal = null
}
private fun getTerminalByUUID(uuid: String): net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal? {
TODO("get terminal by UUID. Return null if not found")
}
////////////////
// game codes //
////////////////
override fun update(delta: Float) {
super.update(delta)
if (terminal != null) terminal!!.update(delta)
}
fun keyPressed(key: Int, c: Char) {
/*if (terminal != null) {
terminal!!.vt.keyPressed(key, c)
computerInside!!.keyPressed(key, c)
}*/
}
}

View File

@@ -0,0 +1,60 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.Second
import net.torvald.terrarum.ui.*
/**
* Created by minjaesong on 2016-09-08.
*/
class UITextTerminal(val terminal: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : UICanvas() {
override var width: Int = terminal.displayW// + some
override var height: Int = terminal.displayH// + frame
/**
* In milliseconds
*
* Timer itself is implemented in the handler.
*/
override var openCloseTime: Second = OPENCLOSE_GENERIC
override fun updateUI(delta: Float) {
terminal.update(delta)
}
override fun renderUI(batch: SpriteBatch, camera: Camera) {
//terminal.render(gc, terminalDisplay.graphics)
}
/**
* Do not modify handler.openCloseCounter here.
*/
override fun doOpening(delta: Float) {
}
/**
* Do not modify handler.openCloseCounter here.
*/
override fun doClosing(delta: Float) {
}
/**
* Do not modify handler.openCloseCounter here.
*/
override fun endOpening(delta: Float) {
}
/**
* Do not modify handler.openCloseCounter here.
*/
override fun endClosing(delta: Float) {
}
override fun dispose() {
}
}