blob: ff0af0d2b745f9b404d8e916d54b8c1aa7a17a7f [file] [log] [blame] [raw]
local deadline = 0
local function checkDeadline()
if computer.realTime() > deadline then
debug.sethook(coroutine.running(), checkDeadline, "", 1)
error("too long without yielding", 0)
end
end
-------------------------------------------------------------------------------
local function invoke(direct, ...)
local result
if direct then
result = table.pack(component.invoke(...))
if result.n == 0 then -- limit for direct calls reached
result = nil
end
end
if not result then
local args = table.pack(...) -- for access in closure
result = select(1, coroutine.yield(function()
return table.pack(component.invoke(table.unpack(args, 1, args.n)))
end))
end
if not result[1] then -- error that should be re-thrown.
error(result[2], 0)
else -- success or already processed error.
return table.unpack(result, 2, result.n)
end
end
-------------------------------------------------------------------------------
local function checkArg(n, have, ...)
have = type(have)
local function check(want, ...)
if not want then
return false
else
return have == want or check(...)
end
end
if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)",
n, table.concat({...}, " or "), have)
error(msg, 3)
end
end
-------------------------------------------------------------------------------
--[[ This is the global environment we make available to userland programs. ]]
-- You'll notice that we do a lot of wrapping of native functions and adding
-- parameter checks in those wrappers. This is to avoid errors from the host
-- side that would push error objects - which are userdata and cannot be
-- persisted.
local sandbox
sandbox = {
assert = assert,
dofile = nil, -- in lib/base.lua
error = error,
_G = nil, -- see below
getmetatable = function(t)
if type(t) == "string" then return nil end
return getmetatable(t)
end,
ipairs = ipairs,
load = function(ld, source, mode, env)
assert((mode or "t") == "t", "unsupported mode")
return load(ld, source, "t", env or sandbox)
end,
loadfile = nil, -- in lib/base.lua
next = next,
pairs = pairs,
pcall = pcall,
print = nil, -- in lib/base.lua
rawequal = rawequal,
rawget = rawget,
rawlen = rawlen,
rawset = rawset,
select = select,
setmetatable = setmetatable,
tonumber = tonumber,
tostring = tostring,
type = type,
_VERSION = "Lua 5.2",
xpcall = xpcall,
coroutine = {
create = coroutine.create,
resume = function(co, ...) -- custom resume part for bubbling sysyields
checkArg(1, co, "thread")
local args = table.pack(...)
while true do -- for consecutive sysyields
debug.sethook(co, checkDeadline, "", 10000)
local result = table.pack(
coroutine.resume(co, table.unpack(args, 1, args.n)))
debug.sethook(co) -- avoid gc issues
checkDeadline()
if result[1] then -- success: (true, sysval?, ...?)
if coroutine.status(co) == "dead" then -- return: (true, ...)
return true, table.unpack(result, 2, result.n)
elseif result[2] ~= nil then -- yield: (true, sysval)
args = table.pack(coroutine.yield(result[2]))
else -- yield: (true, nil, ...)
return true, table.unpack(result, 3, result.n)
end
else -- error: result = (false, string)
return false, result[2]
end
end
end,
running = coroutine.running,
status = coroutine.status,
wrap = function(f) -- for bubbling coroutine.resume
local co = coroutine.create(f)
return function(...)
local result = table.pack(sandbox.coroutine.resume(co, ...))
if result[1] then
return table.unpack(result, 2, result.n)
else
error(result[2], 0)
end
end
end,
yield = function(...) -- custom yield part for bubbling sysyields
return coroutine.yield(nil, ...)
end
},
string = {
byte = string.byte,
char = string.char,
dump = string.dump,
find = string.find,
format = string.format,
gmatch = string.gmatch,
gsub = string.gsub,
len = string.len,
lower = string.lower,
match = string.match,
rep = string.rep,
reverse = string.reverse,
sub = string.sub,
upper = string.upper
},
table = {
concat = table.concat,
insert = table.insert,
pack = table.pack,
remove = table.remove,
sort = table.sort,
unpack = table.unpack
},
math = {
abs = math.abs,
acos = math.acos,
asin = math.asin,
atan = math.atan,
atan2 = math.atan2,
ceil = math.ceil,
cos = math.cos,
cosh = math.cosh,
deg = math.deg,
exp = math.exp,
floor = math.floor,
fmod = math.fmod,
frexp = math.frexp,
huge = math.huge,
ldexp = math.ldexp,
log = math.log,
max = math.max,
min = math.min,
modf = math.modf,
pi = math.pi,
pow = math.pow,
rad = math.rad,
random = math.random,
randomseed = function(seed)
checkArg(1, seed, "number")
math.randomseed(seed)
end,
sin = math.sin,
sinh = math.sinh,
sqrt = math.sqrt,
tan = math.tan,
tanh = math.tanh
},
bit32 = {
arshift = bit32.arshift,
band = bit32.band,
bnot = bit32.bnot,
bor = bit32.bor,
btest = bit32.btest,
bxor = bit32.bxor,
extract = bit32.extract,
replace = bit32.replace,
lrotate = bit32.lrotate,
lshift = bit32.lshift,
rrotate = bit32.rrotate,
rshift = bit32.rshift
},
io = nil, -- in lib/io.lua
os = {
clock = os.clock,
date = function(format, time)
checkArg(1, format, "string", "nil")
checkArg(2, time, "number", "nil")
return os.date(format, time)
end,
difftime = function(t2, t1)
return t2 - t1
end,
execute = nil, -- in lib/os.lua
exit = nil, -- in lib/os.lua
remove = nil, -- in lib/os.lua
rename = nil, -- in lib/os.lua
time = os.time,
tmpname = nil, -- in lib/os.lua
},
-------------------------------------------------------------------------------
-- Start of non-standard stuff.
computer = {
isRobot = computer.isRobot,
address = computer.address,
romAddress = computer.romAddress,
tmpAddress = computer.tmpAddress,
freeMemory = computer.freeMemory,
totalMemory = computer.totalMemory,
uptime = computer.uptime,
energy = computer.energy,
maxEnergy = computer.maxEnergy,
users = computer.users,
addUser = function(name)
checkArg(1, name, "string")
return computer.addUser(name)
end,
removeUser = function(name)
checkArg(1, name, "string")
return computer.removeUser(name)
end,
shutdown = function(reboot)
coroutine.yield(reboot ~= nil and reboot ~= false)
end,
pushSignal = function(name, ...)
checkArg(1, name, "string")
local args = table.pack(...)
for i = 1, args.n do
checkArg(i + 1, args[i], "nil", "boolean", "string", "number")
end
return computer.pushSignal(name, ...)
end,
pullSignal = function(timeout)
local deadline = computer.uptime() +
(type(timeout) == "number" and timeout or math.huge)
repeat
local signal = table.pack(coroutine.yield(deadline - computer.uptime()))
if signal.n > 0 then
return table.unpack(signal, 1, signal.n)
end
until computer.uptime() >= deadline
end
},
component = {
invoke = function(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
return invoke(false, address, method, ...)
end,
list = function(filter)
checkArg(1, filter, "string", "nil")
local list = component.list(filter)
local key = nil
return function()
key = next(list, key)
if key then
return key, list[key]
end
end
end,
proxy = function(address)
checkArg(1, address, "string")
local type, reason = component.type(address)
if not type then
return nil, reason
end
local proxy = {address = address, type = type}
local methods, reason = component.methods(address)
if not methods then
return nil, reason
end
for method, direct in pairs(methods) do
proxy[method] = function(...)
return invoke(direct, address, method, ...)
end
end
return proxy
end,
type = function(address)
checkArg(1, address, "string")
return component.type(address)
end
},
unicode = {
char = unicode.char,
len = unicode.len,
lower = unicode.lower,
reverse = unicode.reverse,
sub = unicode.sub,
upper = unicode.upper
},
checkArg = checkArg
}
sandbox._G = sandbox
-------------------------------------------------------------------------------
local function main()
local args
local function bootstrap()
-- Minimalistic hard-coded pure async proxy for our ROM.
local rom = {}
function rom.invoke(method, ...)
return invoke(true, computer.romAddress(), method, ...)
end
function rom.open(file) return rom.invoke("open", file) end
function rom.read(handle) return rom.invoke("read", handle, math.huge) end
function rom.close(handle) return rom.invoke("close", handle) end
function rom.libs(file) return ipairs(rom.invoke("list", "lib")) end
function rom.isDirectory(path) return rom.invoke("isDirectory", path) end
-- Custom low-level dofile implementation reading from our ROM.
local function dofile(file)
local handle, reason = rom.open(file)
if not handle then
error(reason)
end
if handle then
local buffer = ""
repeat
local data, reason = rom.read(handle)
if not data and reason then
error(reason)
end
buffer = buffer .. (data or "")
until not data
rom.close(handle)
local program, reason = load(buffer, "=" .. file, "t", sandbox)
if program then
local result = table.pack(pcall(program))
if result[1] then
return table.unpack(result, 2, result.n)
else
error(result[2])
end
else
error(reason)
end
end
end
local init = {}
for _, lib in rom.libs() do
local path = "lib/" .. lib
if not rom.isDirectory(path) then
local install = dofile(path)
if type(install) == "function" then
table.insert(init, install)
end
end
end
for _, install in ipairs(init) do
install()
end
-- Yield once to get a memory baseline.
coroutine.yield()
return coroutine.create(function() dofile("/init.lua") end)
end
local co, args = bootstrap(), {n=0}
while true do
deadline = computer.realTime() + timeout -- timeout global is set by host
debug.sethook(co, checkDeadline, "", 10000)
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
if not result[1] then
error(tostring(result[2]), 0)
elseif coroutine.status(co) == "dead" then
error("computer stopped unexpectedly", 0)
else
args = table.pack(coroutine.yield(result[2])) -- system yielded value
end
end
end
-- JNLua converts the coroutine to a string immediately, so we can't get the
-- traceback later. Because of that we have to do the error handling here.
return pcall(main)