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