require 'org.luaj.vm2.lib.DebugLib'
require 'org.luaj.vm2.lib.jse.LuajavaLib'
local debug = debug
local function duplicate(table, base)
local t = base or { }
for i, v in pairs(table) do
t[i] = v
end
return t
end
local function clone(table)
local t = { }
local function clones(table)
if type(table) ~= 'table' then
return table
end
if not t[table] then
t[table] = { }
for i, v in pairs(table) do
t[table][i] = clones(v)
end
end
return t[table]
end
return clones(table)
end
local empty = { }
local userfun = { }
local error
local print
local function rvpack(...)
local arg = {...}
arg.n = select('#', ...)
arg.s = 1
return arg
end
local function rvunpack(t, s, n)
return unpack(t, s or t.s, n or t.n)
end
local function rvshift(t, d)
t.s = t.s + d
return t
end
local function protectenv(table, force)
local t = { }
local getfenv = getfenv
local setfenv = setfenv
local select = select
local function sub(table)
if not t[table] then
t[table] = true
for i, v in pairs(table) do
if force or userfun[v] then
table[i] = setfenv(function(...)
local p, err = getfenv(v)
local f = setfenv(v, getfenv(0))
local r = rvpack(f(...))
if p then
setfenv(v, p)
end
return rvunpack(r)
end, empty)
elseif type(v) == 'table' then
sub(v)
end
end
end
end
sub(table)
end
local parallels = { }
local currentParallel = nil
local currentProcess = nil
local processInfo = setmetatable({}, { __mode = 'k' })
local handles = setmetatable({}, { __mode = 'k' })
local returnValue
local timers = { }
function pullEventRaw()
local event = rvpack(coroutine.yield())
if event[1] == 'terminate' then
local info = processInfo[parallels[1].currentProcess]
info.status = 'dead'
info.parent.returnValue = rvpack('error', 'terminated')
else
if event[1] == 'timer' and timers[event[2]] then
timers[event[2]]()
timers[event[2]] = nil
end
return event
end
end
local events = { }
events.back = events
local ignoreEvents = false
local waiting = false
local listener = { }
local function queueEvent(event)
local e = { value = event }
events.back.next = e
events.back = e
end
local function pullEvent(wait)
if currentParallel.events.next then
currentParallel.events = currentParallel.events.next
return assert(currentParallel.events.value)
end
return nil
end
--TODO: work with parallel
local function sleep(time)
local timer = os.startTimer(time)
local info = processInfo[currentProcess]
if info.status ~= 'dead' then
info.status = 'wait'
info.wait = function() end
timers[timer] = function()
if info.status ~= 'dead' then
info.status = 'normal'
end
end
end
end
local function flushEvent()
--sleep(0)
coroutine.yield('sleep', 0)
end
loadfile = function(fn)
local file = fs.open(fn, "r")
if file then
local func, err = loadstring(file.readAll(), fn)
file.close()
return func, err
end
return nil, "File not found"
end
dofile = function(fn)
local fun, e = loadfile(fn)
if fun then
return fun()
else
error(e)
end
end
function write( sText )
local w,h = term.getSize()
local x,y = term.getCursorPos()
local nLinesPrinted = 0
local function newLine()
if y + 1 <= h then
term.setCursorPos(1, y + 1)
else
term.scroll(1)
term.setCursorPos(1, h)
end
x, y = term.getCursorPos()
nLinesPrinted = nLinesPrinted + 1
end
-- Print the line with proper word wrapping
while string.len(sText) > 0 do
local whitespace = string.match( sText, "^[ \t]+" )
if whitespace then
-- Print whitespace
term.write( whitespace )
x,y = term.getCursorPos()
sText = string.sub( sText, string.len(whitespace) + 1 )
end
local newline = string.match( sText, "^\n" )
if newline then
-- Print newlines
newLine()
sText = string.sub( sText, 2 )
end
local text = string.match( sText, "^[^ \t\n]+" )
if text then
sText = string.sub( sText, string.len(text) + 1 )
if string.len(text) > w then
-- Print a multiline word
while string.len( text ) > 0 do
if x > w then
newLine()
end
term.write( text )
text = string.sub( text, (w-x) + 2 )
x,y = term.getCursorPos()
end
else
-- Print a word normally
if x + string.len(text) > w then
newLine()
end
term.write( text )
x,y = term.getCursorPos()
end
end
end
return nLinesPrinted
end
function print( ... )
local nLinesPrinted = 0
for n,v in ipairs( { ... } ) do
nLinesPrinted = nLinesPrinted + write( tostring( v ) )
end
nLinesPrinted = nLinesPrinted + write( "\n" )
return nLinesPrinted
end
function read( _sReplaceChar, _tHistory )
setfenv(1, getfenv(0))
term.setCursorBlink( true )
local sLine = ""
local nHistoryPos = nil
local nPos = 0
if _sReplaceChar then
_sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
end
local w, h = term.getSize()
local sx, sy = term.getCursorPos()
local function redraw()
local nScroll = 0
if sx + nPos >= w then
nScroll = (sx + nPos) - w
end
term.setCursorPos( sx, sy )
term.write( string.rep(" ", w - sx + 1) )
term.setCursorPos( sx, sy )
if _sReplaceChar then
term.write( string.rep(_sReplaceChar, string.len(sLine) - nScroll) )
else
term.write( string.sub( sLine, nScroll + 1 ) )
end
term.setCursorPos( sx + nPos - nScroll, sy )
end
while true do
local sEvent, param = os.pullEvent()
if sEvent == "char" then
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
nPos = nPos + 1
redraw()
elseif sEvent == "key" then
if param == 28 then
-- Enter
break
elseif param == 203 then
-- Left
if nPos > 0 then
nPos = nPos - 1
redraw()
end
elseif param == 205 then
-- Right
if nPos < string.len(sLine) then
nPos = nPos + 1
redraw()
end
elseif param == 200 or param == 208 then
-- Up or down
if _tHistory then
if param == 200 then
-- Up
if nHistoryPos == nil then
if #_tHistory > 0 then
nHistoryPos = #_tHistory
end
elseif nHistoryPos > 1 then
nHistoryPos = nHistoryPos - 1
end
else
-- Down
if nHistoryPos == #_tHistory then
nHistoryPos = nil
elseif nHistoryPos ~= nil then
nHistoryPos = nHistoryPos + 1
end
end
if nHistoryPos then
sLine = _tHistory[nHistoryPos]
nPos = string.len( sLine )
else
sLine = ""
nPos = 0
end
redraw()
end
elseif param == 14 then
-- Backspace
if nPos > 0 then
sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
nPos = nPos - 1
redraw()
end
end
end
end
term.setCursorBlink( false )
term.setCursorPos( w + 1, sy )
print()
return sLine
end
local function split(str, sub)
local t = { }
local p = 1
local q = string.find(str, sub, p, true)
local l = string.len(sub)
while q do
table.insert(t, string.sub(str, p, q - 1))
p = q + l
q = string.find(str, sub, p, true)
end
end
local tenv = {
os = {
version = function()
return 'CCOS 0.2'
end,
computerID = os.computerID,
run = function (env, path, ...)
local file, err = loadfile(path)
if file then
local ret = rvpack(coroutine.yield('exec', loadfile(path), rvpack(...), nil, setmetatable(env, { __index = _G })))
if ret[1] == 'error' then
ret[1] = false
print(ret[2])
end
return rvunpack(ret)
else
return false, err
end
end,
loadAPI = function(path)
return false, 'Premission denied'
end,
unloadAPI = function(name)
return false, 'Premission denied'
end,
pullEvent = function()
return coroutine.yield('pull')
end,
pullEventRaw = function()
return coroutine.yield('pull')
end,
queueEvent = os.queueEvent,
clock = os.clock,
startTimer = os.startTimer,
sleep = function(time, hold)
coroutine.yield('sleep', time)
if not hold then
coroutine.yield('discard')
end
end,
time = os.time,
shutdown = function()
coroutine.yield('shutdown')
end,
reboot = os.reboot,
setAlarm = os.setAlarm,
},
coroutine = coroutine,
redstone = redstone,
term = term,
math = math,
table = table,
fs = fs,
rs = rs,
string = string,
sleep = function(time)
coroutine.yield('sleep', time)
end,
disk = disk,
getmetatable = getmetatable,
unpack = unpack,
__inext = __inext,
_VERSION = _VERSION,
ipairs = ipairs,
load = load,
print = print,
read = read,
write = write,
pcall = pcall,
assert = assert,
tonumber = tonumber,
rawequal = rawequal,
loadstring = loadstring,
loadfile = loadfile,
dofile = dofile,
pairs = pairs,
collectgarbage = collectgarbage,
xpcall = xpcall,
error = error,
setfenv = setfenv,
require = function(name)
if package.loaded[name] then
return package.loaded[name]
end
for i, v in ipairs(package.loaders) do
local f = v(name)
if type(f) == 'function' then
package.loaded[name] = f(name)
if not package.loaded[name] == nil then
package.loaded[name] = true
end
return package.loaded[name]
end
end
error('cannot find module ' .. name)
end,
module = function(name, ...)
local function do_at(fun, table, name, ...)
if select('#', ...) ~= 0 then
return do_at(fun, table[name], ...)
else
return fun(table, name)
end
end
local l = split(name, '.')
local t
if package.loaded[name] then
t = package.loaded[name]
else
t = do_at(function(t, n) return t[n] end, _G, unpack(l))
if not t then
package.loaded[name] = t
do_at(function(g, n) g[n] = t end, _G, unpack(l))
end
end
t._NAME = name
t._M = t
t._PACKAGE = string.sub(name, 1, string.len(name) - string.len(l[#l]) - 1)
package.loaded[name] = t
setfenv(0, t)
for i, v in ipairs({...}) do
v(t)
end
return t
end,
getfenv = getfenv,
rawget = rawget,
type = type,
rawset = rawset,
setmetatable = setmetatable,
select = select,
tostring = tostring,
next = next,
http = http,
parallel = {
create = function(f, ...)
if select('#', ...) ~= 0 then
return coroutine.yield('parallel', f), parallel.create(...)
else
return coroutine.yield('parallel', f)
end
end,
waitForAny = function(...)
coroutine.yield('wait', 1, parallel.create(...))
end,
waitForAll = function(...)
coroutine.yield('wait', select('#', ...), parallel.create(...))
end,
},
package = {
cpath = '/',
loaded = { },
loaders = {
function(name)
return package.preload[name]
end,
function(name)
name = string.gusb(string.gsub(name, '%.', '/'), '%%', '%%%%')
local s = split(package.path, ';')
for i, v in ipairs(s) do
local fun = loadfile(string.gsub(s[i], '%?', name))
if fun then
return fun
end
end
end,
function(name)
return nil
end,
function(name)
return nil
end,
},
path = '?;?.lua',
preload = { },
seeall = function(module)
setmetatable(module, { __index = _G })
end
},
}
userfun[tenv.os.run] = true
userfun[tenv.require] = true
userfun[tenv.module] = true
userfun[tenv.package.seeall] = true
userfun[tenv.read] = true
userfun[tenv.parallel.create] = true
userfun[tenv.parallel.waitForAny] = true
userfun[tenv.parallel.waitForAll] = true
protectenv(tenv.package.loaders, true)
--assert(userfun[nil] ~= true)
local syscall = { }
local function cycle()
while parallels[1].currentProcess do
local running = false
for i, v in ipairs(parallels) do
currentParallel = v
currentProcess = v.currentProcess
while currentProcess and processInfo[currentProcess].status == 'dead' do
if listener[currentProcess] then
for i, v in ipairs(listener[currentProcess]) do
v()
end
end
listener[currentProcess] = nil
currentProcess = processInfo[currentProcess].parent
end
v.currentProcess = currentProcess
if not currentProcess then
if listener[v] then
for i, f in ipairs(listener[v]) do
f()
end
listener[v] = nil
end
elseif processInfo[currentProcess].status ~= 'wait' or processInfo[currentProcess].wait() then
running = true
returnValue = processInfo[currentProcess].returnValue
local ret = rvpack(coroutine.resume(currentProcess, rvunpack(returnValue)))
if not ret[1] then
processInfo[currentProcess].status = 'dead'
ret[1] = 'error'
local p = processInfo[currentProcess].parent
if p then
processInfo[p].returnValue = ret
else
print(rvunpack(ret))
end
elseif coroutine.status(currentProcess) == 'dead' then
processInfo[currentProcess].status = 'dead'
ret[1] = true
local p = processInfo[currentProcess].parent
if p then
processInfo[p].returnValue = ret
end
else
ret[2] = ret[2] or 'pull'
syscall[ret[2]](rvunpack(ret, 3))
end
end
end
if not running then
queueEvent(pullEventRaw())
end
end
end
local function createProcess(file, argv, envp, eenv)
envp = envp or duplicate(processInfo[currentProcess].environment)
argv = argv or { }
argv[0] = path
argv.n = argv.n or #argv
argv.s = argv.s or 1
local penv = eenv or clone(tenv)
penv._G = penv
local co = coroutine.create(setfenv(file, penv))
debug.setfenv(co, penv)
debug.sethook(co, flushEvent, '', 2000)
processInfo[co] = {
environment = envp,
arguments = argv,
parent = currentProcess,
parellel = currentParallel,
returnValue = argv
}
return co
end
local function execf(file, argv, envp, eenv)
local co = createProcess(file, argv, envp, eenv)
currentParallel.currentProcess = co
end
local function exec(path, argv, envp, eenv)
if type(path) == 'function' then
return execf(path, argv, envp, eenv)
end
local file, err = loadfile(path)
if file then
return execf(file, argv, envp, eenv)
end
processInfo[currentProcess].returnValue = rvpack(false, err)
end
local function pull()
local e = pullEvent()
local info = processInfo[currentProcess]
if e then
info.returnValue = e
elseif info.status ~= 'dead' then
info.status = 'wait'
e = currentParallel.events
info.wait = function()
if e.next then
info.returnValue = assert(pullEvent())
info.status = 'normal'
return true
end
end
end
end
local function discard()
currentParallel.events = events.back
end
local function shutdown()
os.shutdown()
while true do
coroutine.yield()
end
end
local function parallel(func)
local co = createProcess(func)
local parallel = {
currentProcess = co,
events = currentParallel.events
}
local info = processInfo[co]
info.parent = nil
info.parallel = parallel
table.insert(parallels, parallel)
local v = { }
handles[v] = parallel
processInfo[currentProcess].returnValue = rvpack(v)
end
function wait(n, ...)
for i, v in ipairs({...}) do
if handles[v] then
v = handles[v]
end
if not listener[v] then
listener[v] = { }
end
if processInfo[v] and processInfo[v].status == 'dead' then
n = n - 1
end
if v and v.currentProcess == nil then
n = n - 1
end
table.insert(listener[v], function()
n = n - 1
if n <= 0 and info.status ~= 'dead' then
info.status = 'normal'
end
end)
end
local info = processInfo[currentProcess]
info.wait = function() end
if info.status ~= 'dead' then
info.status = 'wait'
end
end
syscall.exec = execf
syscall.pull = pull
syscall.sleep = sleep
syscall.discard = discard
syscall.shutdown = shutdown
syscall.parallel = parallel
syscall.wait = wait
local tAPIsLoading = {}
function os.loadAPI( _sPath )
local sName = fs.getName( _sPath )
if tAPIsLoading[sName] == true then
return false, "API "..sName.." is already being loaded"
end
tAPIsLoading[sName] = true
local tEnv = {}
setmetatable(tEnv, { __index = tenv })
local fnAPI, err = loadfile( _sPath )
if fnAPI then
setfenv(fnAPI, tEnv)
fnAPI()
else
return false, err
end
--_G[sName] = duplicate(tEnv)
tenv[sName] = duplicate(tEnv, tenv[sName])
tAPIsLoading[sName] = nil
return true
end
function os.unloadAPI( _sName )
if _sName ~= "_G" and type(_G[_sName] == "table") then
tenv[sName] = nil
end
end
-- Install the lua parts of the HTTP api (if enabled)
if http then
http.get = function( _url )
local requestID = http.request( _url )
while true do
local event, param1, param2 = os.pullEvent()
if event == "http_success" and param1 == _url then
return param2
elseif event == "http_failure" and param1 == _url then
return nil
end
end
end
end
-- Load APIs
local tApis = fs.list( "rom/apis" )
for n,sFile in ipairs( tApis ) do
if not fs.isDir( sFile ) and sFile ~= 'parallel' then
os.loadAPI( fs.combine( "rom/apis", sFile ) )
end
end
--tenv = clone(tenv)
protectenv(tenv)
currentProcess = createProcess(function()
_G.parallel.create(rednet.run)
os.run( {}, "rom/programs/shell" )
end, nil, { })
local parallel = {
currentProcess = currentProcess,
events = events
}
local info = processInfo[currentProcess]
info.parent = nil
info.parallel = parallel
table.insert(parallels, parallel)
print(pcall(cycle))
local x = returnValue
print(rvunpack(returnValue))
--[[
-- Run the shell
local ok, err = pcall( function()
parallel.waitForAny(
function()
rednet.run()
end,
function()
os.run( {}, "rom/programs/shell" )
end
)
end )
if not ok then
print( err )
end
]]
-- If the shell didn't shutdown the computer,
-- it probably errored, so let the user read it.
pcall( function()
term.setCursorBlink( false )
print( "Press any key to continue" )
repeat
local event = pullEventRaw()
until event[1] == "key"
end )
os.shutdown() -- Just in case