Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
OpenComputers/src/main/resources/assets/opencomputers/lua/machine.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1518 lines (1427 sloc)
44.1 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local hookInterval = 10000 | |
| local function calcHookInterval() | |
| local bogomipsDivider = 0.05 | |
| local bogomipsDeadline = computer.realTime() + bogomipsDivider | |
| local ipsCount = 0 | |
| local bogomipsBusy = true | |
| local function calcBogoMips() | |
| ipsCount = ipsCount + hookInterval | |
| if computer.realTime() > bogomipsDeadline then | |
| bogomipsBusy = false | |
| end | |
| end | |
| -- The following is a bit of nonsensical-seeming code attempting | |
| -- to cover Lua's VM sufficiently for the IPS calculation. | |
| local bogomipsTmpA = {{["b"]=3, ["d"]=9}} | |
| local function c(k) | |
| if k <= 2 then | |
| bogomipsTmpA[1].d = k / 2.0 | |
| end | |
| end | |
| debug.sethook(calcBogoMips, "", hookInterval) | |
| while bogomipsBusy do | |
| local st = "" | |
| for k=2,4 do | |
| st = st .. "a" .. k | |
| c(k) | |
| if k >= 3 then | |
| bogomipsTmpA[1].b = bogomipsTmpA[1].b * (k ^ k) | |
| end | |
| end | |
| end | |
| debug.sethook() | |
| return ipsCount / bogomipsDivider | |
| end | |
| local ipsCount = calcHookInterval() | |
| -- Since our IPS might still be too generous (hookInterval needs to run at most | |
| -- every 0.05 seconds), we divide it further by 10 relative to that. | |
| hookInterval = (ipsCount * 0.005) | |
| if hookInterval < 1000 then hookInterval = 1000 end | |
| local deadline = math.huge | |
| local hitDeadline = false | |
| local tooLongWithoutYielding = setmetatable({}, { __tostring = function() return "too long without yielding" end}) | |
| local function checkDeadline() | |
| if computer.realTime() > deadline then | |
| debug.sethook(coroutine.running(), checkDeadline, "", 1) | |
| if not hitDeadline then | |
| deadline = deadline + 0.5 | |
| end | |
| hitDeadline = true | |
| error(tooLongWithoutYielding) | |
| end | |
| end | |
| local function pcallTimeoutCheck(...) | |
| local ok, timeout = ... | |
| if rawequal(timeout, tooLongWithoutYielding) then | |
| return ok, tostring(tooLongWithoutYielding) | |
| end | |
| return ... | |
| 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 pretty much a straight port of Lua's pattern matching code from | |
| the standard PUC-Rio C implementation. We want to have this in plain Lua | |
| for the sandbox, so that timeouts also apply while matching stuff, which | |
| can take a looong time for certain "evil" patterns. | |
| It passes the pattern matching unit tests from Lua 5.2's test suite, so | |
| that should be good enough. ]] | |
| do | |
| local CAP_UNFINISHED = -1 | |
| local CAP_POSITION = -2 | |
| local L_ESC = '%' | |
| local SPECIALS = "^$*+?.([%-" | |
| local SHORT_STRING = 500 -- use native implementations for short strings | |
| local string_find, string_lower, string_match, string_gmatch, string_gsub = | |
| string.find, string.lower, string.match, string.gmatch, string.gsub | |
| local match -- forward declaration | |
| local strptr | |
| local strptr_mt = {__index={ | |
| step = function(self, count) | |
| self.pos = self.pos + (count or 1) | |
| return self | |
| end, | |
| head = function(self, len) | |
| return string.sub(self.data, self.pos, self.pos + (len or self:len()) - 1) | |
| end, | |
| len = function(self) | |
| return #self.data - (self.pos - 1) | |
| end, | |
| char = function(self, offset) | |
| local pos = self.pos + (offset or 0) | |
| if pos == #self.data + 1 then | |
| return "\0" | |
| end | |
| return string.sub(self.data, pos, pos) | |
| end, | |
| copy = function(self, offset) | |
| return strptr(self.data, self.pos + (offset or 0)) | |
| end | |
| }, | |
| __add = function(a, b) | |
| if type(b) == "table" then | |
| return a.pos + b.pos | |
| else | |
| return a:copy(b) | |
| end | |
| end, | |
| __sub = function(a, b) | |
| if type(b) == "table" then | |
| return a.pos - b.pos | |
| else | |
| return a:copy(-b) | |
| end | |
| end, | |
| __eq = function(a, b) | |
| return a.data == b.data and a.pos == b.pos | |
| end, | |
| __lt = function(a, b) | |
| assert(a.data == b.data) | |
| return a.pos < b.pos | |
| end, | |
| __le = function(a, b) | |
| assert(a.data == b.data) | |
| return a.pos <= b.pos | |
| end | |
| } | |
| function strptr(s, pos) | |
| return setmetatable({ | |
| data = s, | |
| pos = pos or 1 | |
| }, strptr_mt) | |
| end | |
| local function islower(b) return b >= 'a' and b <= 'z' end | |
| local function isupper(b) return b >= 'A' and b <= 'Z' end | |
| local function isalpha(b) return islower(b) or isupper(b) end | |
| local function iscntrl(b) return b <= '\007' or (b >= '\010' and b <= '\017') or (b >= '\020' and b <= '\027') or (b >= '\030' and b <= '\037' and b ~= ' ') or b == '\177' end | |
| local function isdigit(b) return b >= '0' and b <= '9' end | |
| local function ispunct(b) return (b >= '{' and b <= '~') or (b == '`') or (b >= '[' and b <= '_') or (b == '@') or (b >= ':' and b <= '?') or (b >= '(' and b <= '/') or (b >= '!' and b <= '\'') end | |
| local function isspace(b) return b == '\t' or b == '\n' or b == '\v' or b == '\f' or b == '\r' or b == ' ' end | |
| local function isalnum(b) return isalpha(b) or isdigit(b) end | |
| local function isxdigit(b) return isdigit(b) or (b >= 'a' and b <= 'f') or (b >= 'A' and b <= 'F') end | |
| local function isgraph(b) return not iscntrl(b) and not isspace(b) end | |
| -- translate a relative string position: negative means back from end | |
| local function posrelat(pos, len) | |
| if pos >= 0 then return pos | |
| elseif -pos > len then return 0 | |
| else return len + pos + 1 | |
| end | |
| end | |
| local function check_capture(ms, l) | |
| l = l - '1' | |
| if l < 0 or l >= ms.level or ms.capture[l].len == CAP_UNFINISHED then | |
| error("invalid capture index %" .. (l + 1)) | |
| end | |
| return l | |
| end | |
| local function capture_to_close(ms) | |
| local level = ms.level | |
| while level > 0 do | |
| level = level - 1 | |
| if ms.capture[level].len == CAP_UNFINISHED then | |
| return level | |
| end | |
| end | |
| return error("invalid pattern capture") | |
| end | |
| local function classend(ms, p) | |
| local p0 = p:char() p = p:copy(1) | |
| if p0 == L_ESC then | |
| if p == ms.p_end then | |
| error("malformed pattern (ends with %)") | |
| end | |
| return p:step(1) | |
| elseif p0 == '[' then | |
| if p:char() == '^' then | |
| p:step() | |
| end | |
| repeat -- look for a `]' | |
| if p == ms.p_end then | |
| error("malformed pattern (missing ])") | |
| end | |
| p:step() | |
| if p:char(-1) == L_ESC then | |
| if p < ms.p_end then | |
| p:step() -- skip escapes (e.g. `%]') | |
| end | |
| end | |
| until p:char() == ']' | |
| return p:step() | |
| else | |
| return p | |
| end | |
| end | |
| local function match_class(c, cl) | |
| local res | |
| local cll = string_lower(cl) | |
| if cll == 'a' then res = isalpha(c) | |
| elseif cll == 'c' then res = iscntrl(c) | |
| elseif cll == 'd' then res = isdigit(c) | |
| elseif cll == 'g' then res = isgraph(c) | |
| elseif cll == 'l' then res = islower(c) | |
| elseif cll == 'p' then res = ispunct(c) | |
| elseif cll == 's' then res = isspace(c) | |
| elseif cll == 'u' then res = isupper(c) | |
| elseif cll == 'w' then res = isalnum(c) | |
| elseif cll == 'x' then res = isxdigit(c) | |
| elseif cll == 'z' then res = c == '\0' -- deprecated option | |
| else return cl == c | |
| end | |
| if islower(cl) then return res | |
| else return not res | |
| end | |
| end | |
| local function matchbracketclass(c, p, ec) | |
| local sig = true | |
| p = p:copy(1) | |
| if p:char() == '^' then | |
| sig = false | |
| p:step() -- skip the `^' | |
| end | |
| while p < ec do | |
| if p:char() == L_ESC then | |
| p:step() | |
| if match_class(c, p:char()) then | |
| return sig | |
| end | |
| elseif p:char(1) == '-' and p + 2 < ec then | |
| p:step(2) | |
| if p:char(-2) <= c and c <= p:char() then | |
| return sig | |
| end | |
| elseif p:char() == c then | |
| return sig | |
| end | |
| p:step() | |
| end | |
| return not sig | |
| end | |
| local function singlematch(ms, s, p, ep) | |
| if s >= ms.src_end then | |
| return false | |
| end | |
| local p0 = p:char() | |
| if p0 == '.' then return true -- matches any char | |
| elseif p0 == L_ESC then return match_class(s:char(), p:char(1)) | |
| elseif p0 == '[' then return matchbracketclass(s:char(), p, ep:copy(-1)) | |
| else return p:char() == s:char() | |
| end | |
| end | |
| local function matchbalance(ms, s, p) | |
| if p >= ms.p_end - 1 then | |
| error("malformed pattern (missing arguments to %b)") | |
| end | |
| if s:char() ~= p:char() then return nil end | |
| local b = p:char() | |
| local e = p:char(1) | |
| local cont = 1 | |
| s = s:copy() | |
| while s:step() < ms.src_end do | |
| if s:char() == e then | |
| cont = cont - 1 | |
| if cont == 0 then return s:step() end | |
| elseif s:char() == b then | |
| cont = cont + 1 | |
| end | |
| end | |
| return nil -- string ends out of balance | |
| end | |
| local function max_expand(ms, s, p, ep) | |
| local i = 0 -- counts maximum expand for item | |
| while singlematch(ms, s:copy(i), p, ep) do | |
| i = i + 1 | |
| end | |
| -- keeps trying to match with the maximum repetitions | |
| while i >= 0 do | |
| local res = match(ms, s:copy(i), ep:copy(1)) | |
| if res then return res end | |
| i = i - 1 -- else didn't match; reduce 1 repetition to try again | |
| end | |
| return nil | |
| end | |
| local function min_expand(ms, s, p, ep) | |
| s = s:copy() | |
| while true do | |
| local res = match(ms, s, ep:copy(1)) | |
| if res ~= nil then | |
| return res | |
| elseif singlematch(ms, s, p, ep) then | |
| s:step() -- try with one more repetition | |
| else return nil | |
| end | |
| end | |
| end | |
| local function start_capture(ms, s, p, what) | |
| local level = ms.level | |
| ms.capture[level] = ms.capture[level] or {} | |
| ms.capture[level].init = s:copy() | |
| ms.capture[level].len = what | |
| ms.level = level + 1 | |
| local res = match(ms, s, p) | |
| if res == nil then -- match failed? | |
| ms.level = ms.level - 1 -- undo capture | |
| end | |
| return res | |
| end | |
| local function end_capture(ms, s, p) | |
| local l = capture_to_close(ms) | |
| ms.capture[l].len = s - ms.capture[l].init -- close capture | |
| local res = match(ms, s, p) | |
| if res == nil then -- match failed? | |
| ms.capture[l].len = CAP_UNFINISHED -- undo capture | |
| end | |
| return res | |
| end | |
| local function match_capture(ms, s, l) | |
| l = check_capture(ms, l) | |
| local len = ms.capture[l].len | |
| if ms.src_end - s >= len and | |
| ms.capture[l].init:head(len) == s:head(len) | |
| then | |
| return s:copy(len) | |
| else return nil | |
| end | |
| end | |
| function match(ms, s, p) | |
| s = s:copy() | |
| p = p:copy() | |
| ::init:: -- using goto's to optimize tail recursion | |
| if p ~= ms.p_end then | |
| local p0 = p:char() | |
| if p0 == '(' then -- start capture | |
| if p:char(1) == ')' then -- position capture? | |
| s = start_capture(ms, s, p:copy(2), CAP_POSITION) | |
| else | |
| s = start_capture(ms, s, p:copy(1), CAP_UNFINISHED) | |
| end | |
| goto brk | |
| elseif p0 == ')' then -- end capture | |
| s = end_capture(ms, s, p:copy(1)) | |
| goto brk | |
| elseif p0 == '$' then | |
| if p + 1 ~= ms.p_end then -- is the `$' the last char in pattern? | |
| goto dflt -- no; go to default | |
| end | |
| s = (s == ms.src_end) and s or nil -- check end of string | |
| goto brk | |
| elseif p0 == L_ESC then -- escaped sequences not in the format class[*+?-]? | |
| local p1 = p:char(1) | |
| if p1 == 'b' then -- balanced string? | |
| s = matchbalance(ms, s, p:copy(2)) | |
| if s ~= nil then | |
| p:step(4) | |
| goto init -- return match(ms, s, p + 4) | |
| end | |
| -- else fail (s == nil) | |
| elseif p1 == 'f' then -- frontier? | |
| p:step(2) | |
| if p:char() ~= '[' then | |
| error("missing [ after %f in pattern") | |
| end | |
| local ep = classend(ms, p) -- points to what is next | |
| local previous = (s == ms.src_init) and '\0' or s:char(-1) | |
| if not matchbracketclass(previous, p, ep:copy(-1)) and | |
| matchbracketclass(s:char(), p, ep:copy(-1)) | |
| then | |
| p = ep | |
| goto init -- return match(ms, s, ep) | |
| end | |
| s = nil -- match failed | |
| elseif isdigit(p:char(1)) then -- capture results (%0-%9)? | |
| s = match_capture(ms, s, p:char(1)) | |
| if s ~= nil then | |
| p:step(2) | |
| goto init -- return match(ms, s, p + 2) | |
| end | |
| else | |
| goto dflt | |
| end | |
| goto brk | |
| end | |
| ::dflt:: do | |
| local ep = classend(ms, p) -- points to what is next | |
| local ep0 = ep:char() | |
| if not singlematch(ms, s, p, ep) then | |
| if ep0 == '*' or ep0 == '?' or ep0 == '-' then -- accept empty? | |
| p = ep:copy(1) | |
| goto init -- return match(ms, s, ep + 1) | |
| else -- '+' or no suffix | |
| s = nil -- fail | |
| end | |
| else -- matched once | |
| if ep0 == '?' then -- optional | |
| local res = match(ms, s:copy(1), ep:copy(1)) | |
| if res ~= nil then | |
| s = res | |
| else | |
| p = ep:copy(1) | |
| goto init -- else return match(ms, s, ep + 1) | |
| end | |
| elseif ep0 == '+' then -- 1 or more repetitions | |
| s = max_expand(ms, s:copy(1), p, ep) -- 1 match already done | |
| elseif ep0 == '*' then -- 0 or more repetitions | |
| s = max_expand(ms, s, p, ep) | |
| elseif ep0 == '-' then -- 0 or more repetitions (minimum) | |
| s = min_expand(ms, s, p, ep) | |
| else | |
| s:step() | |
| p = ep | |
| goto init -- else return match(ms, s+1, ep); | |
| end | |
| end | |
| end | |
| ::brk:: | |
| end | |
| return s | |
| end | |
| local function push_onecapture(ms, i, s, e) | |
| if i >= ms.level then | |
| if i == 0 then -- ms->level == 0, too | |
| return s:head(e - s) -- add whole match | |
| else | |
| error("invalid capture index") | |
| end | |
| else | |
| local l = ms.capture[i].len; | |
| if l == CAP_UNFINISHED then error("unfinished capture") end | |
| if l == CAP_POSITION then | |
| return ms.capture[i].init - ms.src_init + 1 | |
| else | |
| return ms.capture[i].init:head(l) | |
| end | |
| end | |
| end | |
| local function push_captures(ms, s, e) | |
| local nlevels = (ms.level == 0 and s) and 1 or ms.level | |
| local captures = {} | |
| for i = 0, nlevels - 1 do | |
| table.insert(captures, push_onecapture(ms, i, s, e)) | |
| end | |
| return table.unpack(captures) | |
| end | |
| -- check whether pattern has no special characters | |
| local function nospecials(p) | |
| for i = 1, #p do | |
| for j = 1, #SPECIALS do | |
| if p:sub(i, i) == SPECIALS:sub(j, j) then | |
| return false | |
| end | |
| end | |
| end | |
| return true | |
| end | |
| local function str_find_aux(str, pattern, init, plain, find) | |
| checkArg(1, str, "string") | |
| checkArg(2, pattern, "string") | |
| checkArg(3, init, "number", "nil") | |
| if #str < SHORT_STRING then | |
| return (find and string_find or string_match)(str, pattern, init, plain) | |
| end | |
| local s = strptr(str) | |
| local p = strptr(pattern) | |
| local init = posrelat(init or 1, #str) | |
| if init < 1 then init = 1 | |
| elseif init > #str + 1 then -- start after string's end? | |
| return nil -- cannot find anything | |
| end | |
| -- explicit request or no special characters? | |
| if find and (plain or nospecials(pattern)) then | |
| -- do a plain search | |
| local s2 = string_find(str, pattern, init, true) | |
| if s2 then | |
| return s2-s.pos + 1, s2 - s.pos + p:len() | |
| end | |
| else | |
| local s1 = s:copy(init - 1) | |
| local anchor = p:char() == '^' | |
| if anchor then p:step() end | |
| local ms = { | |
| src_init = s, | |
| src_end = s:copy(s:len()), | |
| p_end = p:copy(p:len()), | |
| capture = {} | |
| } | |
| repeat | |
| ms.level = 0 | |
| local res = match(ms, s1, p) | |
| if res ~= nil then | |
| if find then | |
| return s1.pos - s.pos + 1, res.pos - s.pos, push_captures(ms, nil, nil) | |
| else | |
| return push_captures(ms, s1, res) | |
| end | |
| end | |
| until s1:step() > ms.src_end or anchor | |
| end | |
| return nil -- not found | |
| end | |
| local function str_find(s, pattern, init, plain) | |
| return str_find_aux(s, pattern, init, plain, true) | |
| end | |
| local function str_match(s, pattern, init) | |
| return str_find_aux(s, pattern, init, false, false) | |
| end | |
| local function str_gmatch(s, pattern) | |
| checkArg(1, s, "string") | |
| checkArg(2, pattern, "string") | |
| if #s < SHORT_STRING then | |
| return string_gmatch(s, pattern, repl, n) | |
| end | |
| local s = strptr(s) | |
| local p = strptr(pattern) | |
| local start = 0 | |
| return function() | |
| ms = { | |
| src_init = s, | |
| src_end = s:copy(s:len()), | |
| p_end = p:copy(p:len()), | |
| capture = {} | |
| } | |
| for offset = start, ms.src_end.pos - 1 do | |
| local src = s:copy(offset) | |
| ms.level = 0 | |
| local e = match(ms, src, p) | |
| if e ~= nil then | |
| local newstart = e - s | |
| if e == src then newstart = newstart + 1 end -- empty match? go at least one position | |
| start = newstart | |
| return push_captures(ms, src, e) | |
| end | |
| end | |
| return nil -- not found | |
| end | |
| end | |
| local function add_s(ms, b, s, e, r) | |
| local news = tostring(r) | |
| local i = 1 | |
| while i <= #news do | |
| if news:sub(i, i) ~= L_ESC then | |
| b = b .. news:sub(i, i) | |
| else | |
| i = i + 1 -- skip ESC | |
| if not isdigit(news:sub(i, i)) then | |
| b = b .. news:sub(i, i) | |
| elseif news:sub(i, i) == '0' then | |
| b = b .. s:head(e - s) | |
| else | |
| b = b .. push_onecapture(ms, news:sub(i, i) - '1', s, e) -- add capture to accumulated result | |
| end | |
| end | |
| i = i + 1 | |
| end | |
| return b | |
| end | |
| local function add_value(ms, b, s, e, r, tr) | |
| local res | |
| if tr == "function" then | |
| res = r(push_captures(ms, s, e)) | |
| elseif tr == "table" then | |
| res = r[push_onecapture(ms, 0, s, e)] | |
| else -- LUA_TNUMBER or LUA_TSTRING | |
| return add_s(ms, b, s, e, r) | |
| end | |
| if not res then -- nil or false? | |
| res = s:head(e - s) -- keep original text | |
| elseif type(res) ~= "string" and type(res) ~= "number" then | |
| error("invalid replacement value (a "..type(res)..")") | |
| end | |
| return b .. res -- add result to accumulator | |
| end | |
| local function str_gsub(s, pattern, repl, n) | |
| checkArg(1, s, "string") | |
| checkArg(2, pattern, "string", "number") | |
| checkArg(3, repl, "number", "string", "function", "table") | |
| checkArg(4, n, "number", "nil") | |
| if #s < SHORT_STRING then | |
| return string_gsub(s, pattern, repl, n) | |
| end | |
| pattern = tostring(pattern) | |
| local src = strptr(s); | |
| local p = strptr(pattern) | |
| local tr = type(repl) | |
| local max_s = n or (#s + 1) | |
| local anchor = p:char() == '^' | |
| if anchor then | |
| p:step() -- skip anchor character | |
| end | |
| n = 0 | |
| local b = "" | |
| local ms = { | |
| src_init = src:copy(), | |
| src_end = src:copy(src:len()), | |
| p_end = p:copy(p:len()), | |
| capture = {} | |
| } | |
| while n < max_s do | |
| ms.level = 0 | |
| local e = match(ms, src, p) | |
| if e then | |
| n = n + 1 | |
| b = add_value(ms, b, src, e, repl, tr) | |
| end | |
| if e and e > src then -- non empty match? | |
| src = e -- skip it | |
| elseif src < ms.src_end then | |
| b = b .. src:char() | |
| src:step() | |
| else break | |
| end | |
| if anchor then break end | |
| end | |
| b = b .. src:head() | |
| return b, n -- number of substitutions | |
| end | |
| string.find = str_find | |
| string.match = str_match | |
| string.gmatch = str_gmatch | |
| string.gsub = str_gsub | |
| end | |
| ------------------------------------------------------------------------------- | |
| local function spcall(...) | |
| local result = table.pack(pcall(...)) | |
| if not result[1] then | |
| error(tostring(result[2]), 0) | |
| else | |
| return table.unpack(result, 2, result.n) | |
| end | |
| end | |
| local sgcco | |
| local function sgcf(self, gc) | |
| while true do | |
| self, gc = coroutine.yield(pcall(gc, self)) | |
| end | |
| end | |
| local function sgc(self) | |
| local oldDeadline, oldHitDeadline = deadline, hitDeadline | |
| local mt = debug.getmetatable(self) | |
| mt = rawget(mt, "mt") | |
| local gc = rawget(mt, "__gc") | |
| if type(gc) ~= "function" then | |
| return | |
| end | |
| if not sgcco then | |
| sgcco = coroutine.create(sgcf) | |
| end | |
| debug.sethook(sgcco, checkDeadline, "", hookInterval) | |
| deadline, hitDeadline = math.min(oldDeadline, computer.realTime() + 0.5), true | |
| local _, result, reason = coroutine.resume(sgcco, self, gc) | |
| debug.sethook(sgcco) | |
| if coroutine.status(sgcco) == "dead" then | |
| sgcco = nil | |
| end | |
| deadline, hitDeadline = oldDeadline, oldHitDeadline | |
| if not result then | |
| error(reason, 0) | |
| 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, libprocess | |
| sandbox = { | |
| assert = assert, | |
| dofile = nil, -- in boot/*_base.lua | |
| error = error, | |
| _G = nil, -- see below | |
| getmetatable = function(t) | |
| if type(t) == "string" then -- don't allow messing with the string mt | |
| return nil | |
| end | |
| local result = getmetatable(t) | |
| -- check if we have a wrapped __gc using mt | |
| if type(result) == "table" and system.allowGC() and rawget(result, "__gc") == sgc then | |
| result = rawget(result, "mt") | |
| end | |
| return result | |
| end, | |
| ipairs = ipairs, | |
| load = function(ld, source, mode, env) | |
| if not system.allowBytecode() then | |
| mode = "t" | |
| end | |
| return load(ld, source, mode, env or sandbox) | |
| end, | |
| loadfile = nil, -- in boot/*_base.lua | |
| next = next, | |
| pairs = pairs, | |
| pcall = function(...) | |
| return pcallTimeoutCheck(pcall(...)) | |
| end, | |
| print = nil, -- in boot/*_base.lua | |
| rawequal = rawequal, | |
| rawget = rawget, | |
| rawlen = rawlen, | |
| rawset = rawset, | |
| select = select, | |
| setmetatable = function(t, mt) | |
| if type(mt) ~= "table" then | |
| return setmetatable(t, mt) | |
| end | |
| if rawget(mt, "__gc") ~= nil then -- If __gc is set to ANYTHING not `nil`, we're gonna have issues | |
| -- Garbage collector callbacks apparently can't be sandboxed after | |
| -- all, because hooks are disabled while they're running. So we just | |
| -- disable them altogether by default. | |
| if system.allowGC() then | |
| -- For all user __gc functions we enforce a much tighter deadline. | |
| -- This is because these functions may be called from the main | |
| -- thread under certain circumstanced (such as when saving the world), | |
| -- which can lead to noticeable lag if the __gc function behaves badly. | |
| local sbmt = {} -- sandboxed metatable. only for __gc stuff, so it's | |
| -- kinda ok to have a shallow copy instead... meh. | |
| for k, v in next, mt do | |
| sbmt[k] = v | |
| end | |
| sbmt.__gc = sgc | |
| sbmt.mt = mt | |
| mt = sbmt | |
| else | |
| -- Don't allow marking for finalization, but use the raw metatable. | |
| local gc = rawget(mt, "__gc") | |
| rawset(mt, "__gc", nil) -- remove __gc | |
| local ret = table.pack(pcall(setmetatable, t, mt)) | |
| rawset(mt, "__gc", gc) -- restore __gc | |
| if not ret[1] then error(ret[2], 0) end | |
| return table.unpack(ret, 2, ret.n) | |
| end | |
| end | |
| return setmetatable(t, mt) | |
| end, | |
| tonumber = tonumber, | |
| tostring = tostring, | |
| type = type, | |
| _VERSION = _VERSION:match("Luaj") and "Luaj" or _VERSION:match("5.4") and "Lua 5.4" or _VERSION:match("5.3") and "Lua 5.3" or "Lua 5.2", | |
| xpcall = function(f, msgh, ...) | |
| local handled = false | |
| checkArg(2, msgh, "function") | |
| local result = table.pack(xpcall(f, function(...) | |
| if rawequal((...), tooLongWithoutYielding) then | |
| return tooLongWithoutYielding | |
| elseif handled then | |
| return ... | |
| else | |
| handled = true | |
| return msgh(...) | |
| end | |
| end, ...)) | |
| if rawequal(result[2], tooLongWithoutYielding) then | |
| result = table.pack(result[1], select(2, pcallTimeoutCheck(pcall(msgh, tostring(tooLongWithoutYielding))))) | |
| end | |
| return table.unpack(result, 1, result.n) | |
| end, | |
| 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, "", hookInterval) | |
| 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, | |
| -- Lua 5.3. | |
| isyieldable = coroutine.isyieldable | |
| }, | |
| 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, | |
| -- Lua 5.3. | |
| pack = string.pack, | |
| unpack = string.unpack, | |
| packsize = string.packsize | |
| }, | |
| table = { | |
| concat = table.concat, | |
| insert = table.insert, | |
| pack = table.pack, | |
| remove = table.remove, | |
| sort = table.sort, | |
| unpack = table.unpack, | |
| -- Lua 5.3. | |
| move = table.move | |
| }, | |
| 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 or function(a, b) -- Deprecated in Lua 5.3 | |
| return a^b | |
| end, | |
| rad = math.rad, | |
| random = function(...) | |
| return spcall(math.random, ...) | |
| end, | |
| randomseed = function(seed) | |
| spcall(math.randomseed, seed) | |
| end, | |
| sin = math.sin, | |
| sinh = math.sinh, | |
| sqrt = math.sqrt, | |
| tan = math.tan, | |
| tanh = math.tanh, | |
| -- Lua 5.3. | |
| maxinteger = math.maxinteger, | |
| mininteger = math.mininteger, | |
| tointeger = math.tointeger, | |
| type = math.type, | |
| ult = math.ult | |
| }, | |
| -- Deprecated in Lua 5.3. | |
| bit32 = bit32 and { | |
| 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) | |
| return spcall(os.date, format, time) | |
| end, | |
| difftime = function(t2, t1) | |
| return t2 - t1 | |
| end, | |
| execute = nil, -- in boot/*_os.lua | |
| exit = nil, -- in boot/*_os.lua | |
| remove = nil, -- in boot/*_os.lua | |
| rename = nil, -- in boot/*_os.lua | |
| time = function(table) | |
| checkArg(1, table, "table", "nil") | |
| return os.time(table) | |
| end, | |
| tmpname = nil, -- in boot/*_os.lua | |
| }, | |
| debug = { | |
| getinfo = function(...) | |
| local result = debug.getinfo(...) | |
| if result then | |
| -- Only make primitive information available in the sandbox. | |
| return { | |
| source = result.source, | |
| short_src = result.short_src, | |
| linedefined = result.linedefined, | |
| lastlinedefined = result.lastlinedefined, | |
| what = result.what, | |
| currentline = result.currentline, | |
| nups = result.nups, | |
| nparams = result.nparams, | |
| isvararg = result.isvararg, | |
| name = result.name, | |
| namewhat = result.namewhat, | |
| istailcall = result.istailcall | |
| } | |
| end | |
| end, | |
| traceback = debug.traceback, | |
| -- using () to wrap the return of debug methods because in Lua doing this | |
| -- causes only the first return value to be selected | |
| -- e.g. (1, 2) is only (1), the 2 is not returned | |
| -- this is critically important here because the 2nd return value from these | |
| -- debug methods is the value itself, which opens a door to exploit the sandbox | |
| getlocal = function(...) return (debug.getlocal(...)) end, | |
| getupvalue = function(...) return (debug.getupvalue(...)) end, | |
| }, | |
| -- Lua 5.3. | |
| utf8 = utf8 and { | |
| char = utf8.char, | |
| charpattern = utf8.charpattern, | |
| codes = utf8.codes, | |
| codepoint = utf8.codepoint, | |
| len = utf8.len, | |
| offset = utf8.offset | |
| }, | |
| checkArg = checkArg | |
| } | |
| sandbox._G = sandbox | |
| ------------------------------------------------------------------------------- | |
| -- Start of non-standard stuff. | |
| -- JNLua derps when the metatable of userdata is changed, so we have to | |
| -- wrap and isolate it, to make sure it can't be touched by user code. | |
| -- These functions provide the logic for wrapping and unwrapping (when | |
| -- pushed to user code and when pushed back to the host, respectively). | |
| local wrapUserdata, wrapSingleUserdata, unwrapUserdata, wrappedUserdataMeta | |
| wrappedUserdataMeta = { | |
| -- Weak keys, clean up once a proxy is no longer referenced anywhere. | |
| __mode="k", | |
| -- We need custom persist logic here to avoid ERIS trying to save the | |
| -- userdata referenced in this table directly. It will be repopulated | |
| -- in the load methods of the persisted userdata wrappers (see below). | |
| [persistKey and persistKey() or "LuaJ"] = function() | |
| return function() | |
| -- When using special persistence we have to manually reassign the | |
| -- metatable of the persisted value. | |
| return setmetatable({}, wrappedUserdataMeta) | |
| end | |
| end | |
| } | |
| local wrappedUserdata = setmetatable({}, wrappedUserdataMeta) | |
| local function processResult(result) | |
| result = wrapUserdata(result) -- needed for metamethods. | |
| 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 invoke(target, direct, ...) | |
| local result | |
| if direct then | |
| local args = table.pack(...) -- for unwrapping | |
| args = unwrapUserdata(args) | |
| result = table.pack(target.invoke(table.unpack(args, 1, args.n))) | |
| args = nil -- clear upvalue, avoids trying to persist it | |
| if result.n == 0 then -- limit for direct calls reached | |
| result = nil | |
| end | |
| -- no need to wrap here, will be wrapped in processResult | |
| end | |
| if not result then | |
| local args = table.pack(...) -- for access in closure | |
| result = select(1, coroutine.yield(function() | |
| args = unwrapUserdata(args) | |
| local result = table.pack(target.invoke(table.unpack(args, 1, args.n))) | |
| args = nil -- clear upvalue, avoids trying to persist it | |
| result = wrapUserdata(result) | |
| return result | |
| end)) | |
| end | |
| return processResult(result) | |
| end | |
| local function udinvoke(f, data, ...) | |
| local args = table.pack(...) | |
| args = unwrapUserdata(args) | |
| local result = table.pack(f(data, table.unpack(args))) | |
| args = nil -- clear upvalue, avoids trying to persist it | |
| return processResult(result) | |
| end | |
| -- Metatable for additional functionality on userdata. | |
| local userdataWrapper = { | |
| __index = function(self, ...) | |
| return udinvoke(userdata.apply, wrappedUserdata[self], ...) | |
| end, | |
| __newindex = function(self, ...) | |
| return udinvoke(userdata.unapply, wrappedUserdata[self], ...) | |
| end, | |
| __call = function(self, ...) | |
| return udinvoke(userdata.call, wrappedUserdata[self], ...) | |
| end, | |
| __gc = function(self) | |
| local data = wrappedUserdata[self] | |
| wrappedUserdata[self] = nil | |
| userdata.dispose(data) | |
| end, | |
| -- This is the persistence protocol for userdata. Userdata is considered | |
| -- to be 'owned' by Lua, and is saved to an NBT tag. We also get the name | |
| -- of the actual class when saving, so we can create a new instance via | |
| -- reflection when loading again (and then immediately wrap it again). | |
| -- Collect wrapped callback methods. | |
| [persistKey and persistKey() or "LuaJ"] = function(self) | |
| local className, nbt = userdata.save(wrappedUserdata[self]) | |
| -- The returned closure is what actually gets persisted, including the | |
| -- upvalues, that being the classname and a byte array representing the | |
| -- nbt data of the userdata value. | |
| return function() | |
| return wrapSingleUserdata(userdata.load(className, nbt)) | |
| end | |
| end, | |
| -- Do not allow changing the metatable to avoid the gc callback being | |
| -- unset, leading to potential resource leakage on the host side. | |
| __metatable = "userdata", | |
| __tostring = function(self) | |
| local data = wrappedUserdata[self] | |
| return tostring(select(2, pcall(tostring, data))) | |
| end | |
| } | |
| local userdataCallback = { | |
| __call = function(self, ...) | |
| local methods = spcall(userdata.methods, wrappedUserdata[self.proxy]) | |
| for name, direct in pairs(methods) do | |
| if name == self.name then | |
| return invoke(userdata, direct, self.proxy, name, ...) | |
| end | |
| end | |
| error("no such method", 1) | |
| end, | |
| __tostring = function(self) | |
| return userdata.doc(wrappedUserdata[self.proxy], self.name) or "function" | |
| end | |
| } | |
| function wrapSingleUserdata(data) | |
| -- Reuse proxies for lower memory consumption and more logical behavior | |
| -- without the need of metamethods like __eq, as well as proper reference | |
| -- behavior after saving and loading again. | |
| for k, v in pairs(wrappedUserdata) do | |
| -- We need a custom 'equals' check for userdata because metamethods on | |
| -- userdata introduced by JNLua tend to crash the game for some reason. | |
| if v == data then | |
| return k | |
| end | |
| end | |
| local proxy = {type = "userdata"} | |
| local methods = spcall(userdata.methods, data) | |
| for method in pairs(methods) do | |
| proxy[method] = setmetatable({name=method, proxy=proxy}, userdataCallback) | |
| end | |
| wrappedUserdata[proxy] = data | |
| return setmetatable(proxy, userdataWrapper) | |
| end | |
| function wrapUserdata(values) | |
| local processed = {} | |
| local function wrapRecursively(value) | |
| if type(value) == "table" then | |
| if not processed[value] then | |
| processed[value] = true | |
| for k, v in pairs(value) do | |
| value[k] = wrapRecursively(v) | |
| end | |
| end | |
| elseif type(value) == "userdata" then | |
| return wrapSingleUserdata(value) | |
| end | |
| return value | |
| end | |
| return wrapRecursively(values) | |
| end | |
| function unwrapUserdata(values) | |
| local processed = {} | |
| local function unwrapRecursively(value) | |
| if wrappedUserdata[value] then | |
| return wrappedUserdata[value] | |
| end | |
| if type(value) == "table" then | |
| if not processed[value] then | |
| processed[value] = true | |
| for k, v in pairs(value) do | |
| value[k] = unwrapRecursively(v) | |
| end | |
| end | |
| end | |
| return value | |
| end | |
| return unwrapRecursively(values) | |
| end | |
| ------------------------------------------------------------------------------- | |
| local libcomponent | |
| -- Caching proxy objects for lower memory use. | |
| local proxyCache = setmetatable({}, {__mode="v"}) | |
| -- Short-term caching of callback directness for improved performance. | |
| local directCache = setmetatable({}, {__mode="k"}) | |
| local function isDirect(address, method) | |
| local cacheKey = address..":"..method | |
| local cachedValue = directCache[cacheKey] | |
| if cachedValue ~= nil then | |
| return cachedValue | |
| end | |
| local methods, reason = spcall(component.methods, address) | |
| if not methods then | |
| return false | |
| end | |
| for name, info in pairs(methods) do | |
| if name == method then | |
| directCache[cacheKey] = info.direct | |
| return info.direct | |
| end | |
| end | |
| error("no such method", 1) | |
| end | |
| local componentProxy = { | |
| __index = function(self, key) | |
| if self.fields[key] and self.fields[key].getter then | |
| return libcomponent.invoke(self.address, key) | |
| else | |
| rawget(self, key) | |
| end | |
| end, | |
| __newindex = function(self, key, value) | |
| if self.fields[key] and self.fields[key].setter then | |
| return libcomponent.invoke(self.address, key, value) | |
| elseif self.fields[key] and self.fields[key].getter then | |
| error("field is read-only") | |
| else | |
| rawset(self, key, value) | |
| end | |
| end, | |
| __pairs = function(self) | |
| local keyProxy, keyField, value | |
| return function() | |
| if not keyField then | |
| repeat | |
| keyProxy, value = next(self, keyProxy) | |
| until not keyProxy or keyProxy ~= "fields" | |
| end | |
| if not keyProxy then | |
| keyField, value = next(self.fields, keyField) | |
| end | |
| return keyProxy or keyField, value | |
| end | |
| end | |
| } | |
| local componentCallback = { | |
| __call = function(self, ...) | |
| return libcomponent.invoke(self.address, self.name, ...) | |
| end, | |
| __tostring = function(self) | |
| return libcomponent.doc(self.address, self.name) or "function" | |
| end | |
| } | |
| libcomponent = { | |
| doc = function(address, method) | |
| checkArg(1, address, "string") | |
| checkArg(2, method, "string") | |
| local result, reason = spcall(component.doc, address, method) | |
| if not result and reason then | |
| error(reason, 2) | |
| end | |
| return result | |
| end, | |
| invoke = function(address, method, ...) | |
| checkArg(1, address, "string") | |
| checkArg(2, method, "string") | |
| return invoke(component, isDirect(address, method), address, method, ...) | |
| end, | |
| list = function(filter, exact) | |
| checkArg(1, filter, "string", "nil") | |
| local list = spcall(component.list, filter, not not exact) | |
| local key = nil | |
| return setmetatable(list, {__call=function() | |
| key = next(list, key) | |
| if key then | |
| return key, list[key] | |
| end | |
| end}) | |
| end, | |
| methods = function(address) | |
| local result, reason = spcall(component.methods, address) | |
| -- Transform to pre 1.4 format to avoid breaking scripts. | |
| if type(result) == "table" then | |
| for k, v in pairs(result) do | |
| if not v.getter and not v.setter then | |
| result[k] = v.direct | |
| else | |
| result[k] = nil | |
| end | |
| end | |
| return result | |
| end | |
| return result, reason | |
| end, | |
| fields = function(address) | |
| local result, reason = spcall(component.methods, address) | |
| if type(result) == "table" then | |
| for k, v in pairs(result) do | |
| if not v.getter and not v.setter then | |
| result[k] = nil | |
| end | |
| end | |
| return result | |
| end | |
| return result, reason | |
| end, | |
| proxy = function(address) | |
| local type, reason = spcall(component.type, address) | |
| if not type then | |
| return nil, reason | |
| end | |
| local slot, reason = spcall(component.slot, address) | |
| if not slot then | |
| return nil, reason | |
| end | |
| if proxyCache[address] then | |
| return proxyCache[address] | |
| end | |
| local proxy = {address = address, type = type, slot = slot, fields = {}} | |
| local methods, reason = spcall(component.methods, address) | |
| if not methods then | |
| return nil, reason | |
| end | |
| for method, info in pairs(methods) do | |
| if not info.getter and not info.setter then | |
| proxy[method] = setmetatable({address=address,name=method}, componentCallback) | |
| else | |
| proxy.fields[method] = info | |
| end | |
| end | |
| setmetatable(proxy, componentProxy) | |
| proxyCache[address] = proxy | |
| return proxy | |
| end, | |
| type = function(address) | |
| return spcall(component.type, address) | |
| end, | |
| slot = function(address) | |
| return spcall(component.slot, address) | |
| end | |
| } | |
| sandbox.component = libcomponent | |
| local libcomputer = { | |
| isRobot = computer.isRobot, | |
| address = computer.address, | |
| tmpAddress = computer.tmpAddress, | |
| freeMemory = computer.freeMemory, | |
| totalMemory = computer.totalMemory, | |
| uptime = computer.uptime, | |
| energy = computer.energy, | |
| maxEnergy = computer.maxEnergy, | |
| getBootAddress = computer.getBootAddress, | |
| setBootAddress = function(...) | |
| return spcall(computer.setBootAddress, ...) | |
| end, | |
| users = computer.users, | |
| addUser = function(...) | |
| return spcall(computer.addUser, ...) | |
| end, | |
| removeUser = function(...) | |
| return spcall(computer.removeUser, ...) | |
| end, | |
| shutdown = function(reboot) | |
| coroutine.yield(not not reboot) | |
| end, | |
| pushSignal = function(...) | |
| return spcall(computer.pushSignal, ...) | |
| 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, | |
| beep = function(...) | |
| return libcomponent.invoke(computer.address(), "beep", ...) | |
| end, | |
| getDeviceInfo = function() | |
| return libcomponent.invoke(computer.address(), "getDeviceInfo") | |
| end, | |
| getProgramLocations = function() | |
| return libcomponent.invoke(computer.address(), "getProgramLocations") | |
| end, | |
| getArchitectures = function(...) | |
| return spcall(computer.getArchitectures, ...) | |
| end, | |
| getArchitecture = function(...) | |
| return spcall(computer.getArchitecture, ...) | |
| end, | |
| setArchitecture = function(...) | |
| local result, reason = spcall(computer.setArchitecture, ...) | |
| if not result then | |
| if reason then | |
| return result, reason | |
| end | |
| else | |
| coroutine.yield(true) -- reboot | |
| end | |
| end | |
| } | |
| sandbox.computer = libcomputer | |
| local libunicode = { | |
| char = function(...) | |
| return spcall(unicode.char, ...) | |
| end, | |
| len = function(s) | |
| return spcall(unicode.len, s) | |
| end, | |
| lower = function(s) | |
| return spcall(unicode.lower, s) | |
| end, | |
| reverse = function(s) | |
| return spcall(unicode.reverse, s) | |
| end, | |
| sub = function(s, i, j) | |
| if j then | |
| return spcall(unicode.sub, s, i, j) | |
| end | |
| return spcall(unicode.sub, s, i) | |
| end, | |
| upper = function(s) | |
| return spcall(unicode.upper, s) | |
| end, | |
| isWide = function(s) | |
| return spcall(unicode.isWide, s) | |
| end, | |
| charWidth = function(s) | |
| return spcall(unicode.charWidth, s) | |
| end, | |
| wlen = function(s) | |
| return spcall(unicode.wlen, s) | |
| end, | |
| wtrunc = function(s, n) | |
| return spcall(unicode.wtrunc, s, n) | |
| end | |
| } | |
| sandbox.unicode = libunicode | |
| ------------------------------------------------------------------------------- | |
| local function bootstrap() | |
| local eeprom = libcomponent.list("eeprom")() | |
| if eeprom then | |
| local code = libcomponent.invoke(eeprom, "get") | |
| if code and #code > 0 then | |
| local bios, reason = load(code, "=bios", "t", sandbox) | |
| if bios then | |
| return coroutine.create(bios), {n=0} | |
| end | |
| error("failed loading bios: " .. reason, 0) | |
| end | |
| end | |
| error("no bios found; install a configured EEPROM", 0) | |
| end | |
| ------------------------------------------------------------------------------- | |
| local function main() | |
| -- Yield once to get a memory baseline. | |
| coroutine.yield() | |
| -- After memory footprint to avoid init.lua bumping the baseline. | |
| local co, args = bootstrap() | |
| local forceGC = 10 | |
| while true do | |
| deadline = computer.realTime() + system.timeout() | |
| hitDeadline = false | |
| -- NOTE: since this is run in an executor thread and we enforce timeouts | |
| -- in user-defined garbage collector callbacks this should be safe. | |
| if persistKey then -- otherwise we're in LuaJ | |
| forceGC = forceGC - 1 | |
| if forceGC < 1 then | |
| collectgarbage("collect") | |
| forceGC = 10 | |
| end | |
| end | |
| debug.sethook(co, checkDeadline, "", hookInterval) | |
| local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n))) | |
| args = nil -- clear upvalue, avoids trying to persist it | |
| if not result[1] then | |
| error(tostring(result[2]), 0) | |
| elseif coroutine.status(co) == "dead" then | |
| error("computer halted", 0) | |
| else | |
| args = table.pack(coroutine.yield(result[2])) -- system yielded value | |
| args = wrapUserdata(args) | |
| 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 pcallTimeoutCheck(pcall(main)) |