mostly memory consumption optimization of built-in stuff: moved serialization code to extra module so they only get loaded when necessary, drastically reduced memory consumption of component proxies, moved advanced shell stuff (redirects, pipes, variable expansion) to an extra program (besh.lua) to reduce memory footprint of shell module; shell uses $PS1 for the prompt now

This commit is contained in:
Florian Nücke 2014-02-22 17:53:22 +01:00
parent b5bd97807c
commit 95bca5ced5
10 changed files with 538 additions and 453 deletions

View File

@ -489,7 +489,7 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
// ----------------------------------------------------------------------- //
override def installedMemory = 128 * 1024
override def installedMemory = 96 * 1024
override def tier = 0

View File

@ -270,6 +270,17 @@ sandbox._G = sandbox
-- Start of non-standard stuff.
local libcomponent
local callback = {
__call = function(method, ...)
return libcomponent.invoke(method.address, method.name, ...)
end,
__tostring = function(method)
return libcomponent.doc(method.address, method.name) or "function"
end,
__metatable = "callback"
}
libcomponent = {
doc = function(address, method)
checkArg(1, address, "string")
@ -283,7 +294,16 @@ libcomponent = {
invoke = function(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
return invoke(false, address, method, ...)
local methods, reason = component.methods(address)
if not methods then
return nil, reason
end
for name, direct in pairs(methods) do
if name == method then
return invoke(direct, address, method, ...)
end
end
error("no such method", 1)
end,
list = function(filter)
checkArg(1, filter, "string", "nil")
@ -307,15 +327,8 @@ libcomponent = {
if not methods then
return nil, reason
end
for method, direct in pairs(methods) do
proxy[method] = setmetatable({}, {
__call = function(_, ...)
return invoke(direct, address, method, ...)
end,
__tostring = function()
return libcomponent.doc(address, method) or "function"
end
})
for method in pairs(methods) do
proxy[method] = setmetatable({address=address,name=method}, callback)
end
return proxy
end,

View File

@ -0,0 +1,351 @@
-- BEtter SHell, a wrapper for the normal shell that adds many POSIX
-- features, such as pipes, redirects and variable expansion.
local component = require("component")
local computer = require("computer")
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local shell = require("shell")
local term = require("term")
local text = require("text")
local unicode = require("unicode")
local function expandVars(token)
local name = nil
local special = false
local ignore = false
local ignoreChar =''
local escaped = false
local lastEnd = 1
local doubleQuote = false
local singleQuote = false
local endToken = {}
for i = 1, unicode.len(token) do
local char = unicode.sub(token, i, i)
if escaped then
if name then
table.insert(name, char)
end
escaped = false
elseif char == '\\' then
escaped = not escaped
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == '"' and not singleQuote then
doubleQuote = not doubleQuote
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == "'" and not doubleQuote then
singleQuote = not singleQuote
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == "$" and not doubleQuote and not singleQuote then
if name then
ignore = true
else
name = {}
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
end
elseif char == '{' and #name == 0 then
if ignore and ignoreChar == '' then
ignoreChar = '}'
else
special = true
end
elseif char == '(' and ignoreChar == '' then
ignoreChar = ')'
elseif char == '`' and special then
ignore = true
ignoreChar = '`'
elseif char == '}' and not ignore and not doubleQuote and not singleQuote then
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
lastEnd = i+1
elseif char == '"' and not singleQuote then
doubleQuote = not doubleQuote
elseif char == "'" and not doubleQuote then
singleQuote = not singleQuote
elseif name and (char:match("[%a%d_]") or special) then
if char:match("%d") and #name == 0 then
error "Identifiers can't start with a digit!"
end
table.insert(name, char)
elseif char == ignoreChar and ignore then
ignore = false
ignoreChar = ''
elseif name then -- We are done with gathering the name
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
lastEnd = i
end
end
if name then
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
else
table.insert(endToken, unicode.sub(token, lastEnd, -1))
end
return table.concat(endToken)
end
local function parseCommand(tokens)
if #tokens == 0 then
return
end
-- Variable expansion for all command parts.
for i = 1, #tokens do
tokens[i] = expandVars(tokens[i])
end
-- Resolve alias for command.
local program, args = shell.resolveAlias(tokens[1], table.pack(select(2, table.unpack(tokens))))
-- Find redirects.
local input, output, mode = nil, nil, "write"
tokens = args
args = {}
local function smt(call) -- state metatable factory
local function index(_, token)
if token == "<" or token == ">" or token == ">>" then
return "parse error near " .. token
end
call(token)
return "args" -- default, return to normal arg parsing
end
return {__index=index}
end
local sm = { -- state machine for redirect parsing
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
smt(function(token)
table.insert(args, token)
end)),
input = setmetatable({}, smt(function(token)
input = token
end)),
output = setmetatable({}, smt(function(token)
output = token
mode = "write"
end)),
append = setmetatable({}, smt(function(token)
output = token
mode = "append"
end))
}
-- Run state machine over tokens.
local state = "args"
for i = 1, #tokens do
local token = tokens[i]
state = sm[state][token]
if not sm[state] then
return nil, state
end
end
return program, args, input, output, mode
end
local function parseCommands(command)
local tokens, reason = text.tokenize(command)
if not tokens then
return nil, reason
end
local commands, command = {}, {}
for i = 1, #tokens do
if tokens[i] == "|" then
if #command == 0 then
return nil, "parse error near '|'"
end
table.insert(commands, command)
command = {}
else
table.insert(command, tokens[i])
end
end
if #command > 0 then
table.insert(commands, command)
end
for i = 1, #commands do
commands[i] = table.pack(parseCommand(commands[i]))
if commands[i][1] == nil then
return nil, commands[i][2]
end
end
return commands
end
-------------------------------------------------------------------------------
local memoryStream = {}
function memoryStream:close()
self.closed = true
end
function memoryStream:seek()
return nil, "bad file descriptor"
end
function memoryStream:read(n)
if self.closed then
if self.buffer == "" and self.redirect.read then
return self.redirect.read:read(n)
end
return nil -- eof
end
if self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
end
local result = string.sub(self.buffer, 1, n)
self.buffer = string.sub(self.buffer, n + 1)
return result
end
function memoryStream:write(value)
local ok
if self.redirect.write then
ok = self.redirect.write:write(value)
end
if not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
ok = true
end
if ok then
return true
end
return nil, "stream is closed"
end
function memoryStream.new()
local stream = {closed = false, buffer = "",
redirect = {}, result = {}, args = {}}
local metatable = {__index = memoryStream,
__gc = memoryStream.close,
__metatable = "memorystream"}
return setmetatable(stream, metatable)
end
-------------------------------------------------------------------------------
local function execute(command, env, ...)
checkArg(1, command, "string")
local commands, reason = parseCommands(command)
if not commands then
return false, reason
end
if #commands == 0 then
return true
end
-- Piping data between programs works like so:
-- program1 gets its output replaced with our custom stream.
-- program2 gets its input replaced with our custom stream.
-- repeat for all programs
-- custom stream triggers execution of 'next' program after write.
-- custom stream triggers yield before read if buffer is empty.
-- custom stream may have 'redirect' entries for fallback/duplication.
local threads, pipes, inputs, outputs = {}, {}, {}, {}
for i = 1, #commands do
local program, args, input, output, mode = table.unpack(commands[i])
local reason
threads[i], reason = shell.load(program, env, function()
if input then
local file, reason = io.open(shell.resolve(input))
if not file then
error(reason)
end
table.insert(inputs, file)
if pipes[i - 1] then
pipes[i - 1].stream.redirect.read = file
io.input(pipes[i - 1])
else
io.input(file)
end
elseif pipes[i - 1] then
io.input(pipes[i - 1])
end
if output then
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
if not file then
error(reason)
end
if mode == "append" then
io.write("\n")
end
table.insert(outputs, file)
if pipes[i] then
pipes[i].stream.redirect.write = file
io.output(pipes[i])
else
io.output(file)
end
elseif pipes[i] then
io.output(pipes[i])
end
end, command)
if not threads[i] then
return false, reason
end
if i < #commands then
pipes[i] = require("buffer").new("rw", memoryStream.new())
pipes[i]:setvbuf("no")
end
if i > 1 then
pipes[i - 1].stream.next = threads[i]
pipes[i - 1].stream.args = args
end
end
local args = select(2, table.unpack(commands[1]))
table.insert(args, 1, true)
for _, arg in ipairs(table.pack(...)) do
table.insert(args, arg)
end
args.n = #args
local result = nil
for i = 1, #threads do
-- Emulate CC behavior by making yields a filtered event.pull()
while args[1] and coroutine.status(threads[i]) ~= "dead" do
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
if coroutine.status(threads[i]) ~= "dead" then
if type(result[2]) == "string" then
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
else
args = {true, n=1}
end
end
end
if pipes[i] then
pipes[i]:close()
end
if i < #threads and not result[1] then
io.write(result[2])
end
end
for _, input in ipairs(inputs) do
input:close()
end
for _, output in ipairs(outputs) do
output:close()
end
if not args[1] then
return false, args[2]
end
if not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then
if result[2].code then
return true
else
return false, "terminated"
end
end
return table.unpack(result, 1, result.n)
end
local env = setmetatable({os=setmetatable({execute=execute}, {__index=os})}, {__index=_ENV})
shell.execute("/bin/sh", env, ...)

View File

@ -41,7 +41,7 @@ while term.isAvailable() do
code, reason = load(command, "=stdin", "t", env)
end
if code then
local result = table.pack(pcall(code))
local result = table.pack(xpcall(code, debug.traceback))
if not result[1] then
if type(result[2]) == "table" and result[2].reason == "terminated" then
os.exit(result[2].code)

View File

@ -25,7 +25,7 @@ while true do
end
while term.isAvailable() do
local foreground = component.gpu.setForeground(0xFF0000)
term.write("# ")
term.write(os.getenv("PS1") or "# ")
component.gpu.setForeground(foreground)
local command = term.read(history)
if not command then

View File

@ -8,6 +8,7 @@ local env = {
HOME="/home",
MANPATH="/usr/man",
PATH="/bin:/usr/bin:/home/bin:.",
PS1="# ",
PWD="/",
SHELL="/bin/sh",
TMP="/tmp"

View File

@ -0,0 +1,119 @@
local serialization = {}
-- Important: pretty formatting will allow presenting non-serializable values
-- but may generate output that cannot be unserialized back.
function serialization.serialize(value, pretty)
local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true,
["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true,
["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true,
["until"]=true, ["while"]=true}
local id = "^[%a_][%w_]*$"
local ts = {}
local function s(v, l)
local t = type(v)
if t == "nil" then
return "nil"
elseif t == "boolean" then
return v and "true" or "false"
elseif t == "number" then
if v ~= v then
return "0/0"
elseif v == math.huge then
return "math.huge"
elseif v == -math.huge then
return "-math.huge"
else
return tostring(v)
end
elseif t == "string" then
return string.format("%q", v)
elseif t == "table" and pretty and getmetatable(v) and getmetatable(v).__tostring then
return tostring(v)
elseif t == "table" then
if ts[v] then
if pretty then
return "recursion"
else
error("tables with cycles are not supported")
end
end
ts[v] = true
local i, r = 1, nil
local f
if pretty then
local ks = {}
for k in pairs(v) do table.insert(ks, k) end
table.sort(ks)
local n = 0
f = table.pack(function()
n = n + 1
local k = ks[n]
if k ~= nil then
return k, v[k]
else
return nil
end
end)
else
f = table.pack(pairs(v))
end
for k, v in table.unpack(f) do
if r then
r = r .. "," .. (pretty and ("\n" .. string.rep(" ", l)) or "")
else
r = "{"
end
local tk = type(k)
if tk == "number" and k == i then
i = i + 1
r = r .. s(v, l + 1)
else
if tk == "string" and not kw[k] and string.match(k, id) then
r = r .. k
else
r = r .. "[" .. s(k, l + 1) .. "]"
end
r = r .. "=" .. s(v, l + 1)
end
end
ts[v] = nil -- allow writing same table more than once
return (r or "{") .. "}"
else
if pretty then
return tostring(t)
else
error("unsupported type: " .. t)
end
end
end
local result = s(value, 1)
local limit = type(pretty) == "number" and pretty or 10
if pretty then
local truncate = 0
while limit > 0 and truncate do
truncate = string.find(result, "\n", truncate + 1, true)
limit = limit - 1
end
if truncate then
return result:sub(1, truncate) .. "..."
end
end
return result
end
function serialization.unserialize(data)
checkArg(1, data, "string")
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
if not result then
return nil, reason
end
local ok, output = pcall(result)
if not ok then
return nil, output
end
return output
end
return serialization

View File

@ -53,244 +53,6 @@ local function findFile(name, ext)
return false
end
function expandVars(token)
local name = nil
local special = false
local ignore = false
local ignoreChar =''
local escaped = false
local lastEnd = 1
local doubleQuote = false
local singleQuote = false
local endToken = {}
for i = 1, unicode.len(token) do
local char = unicode.sub(token, i, i)
if escaped then
if name then
table.insert(name, char)
end
escaped = false
elseif char == '\\' then
escaped = not escaped
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == '"' and not singleQuote then
doubleQuote = not doubleQuote
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == "'" and not doubleQuote then
singleQuote = not singleQuote
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
lastEnd = i+1
elseif char == "$" and not doubleQuote and not singleQuote then
if name then
ignore = true
else
name = {}
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
end
elseif char == '{' and #name == 0 then
if ignore and ignoreChar == '' then
ignoreChar = '}'
else
special = true
end
elseif char == '(' and ignoreChar == '' then
ignoreChar = ')'
elseif char == '`' and special then
ignore = true
ignoreChar = '`'
elseif char == '}' and not ignore and not doubleQuote and not singleQuote then
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
lastEnd = i+1
elseif char == '"' and not singleQuote then
doubleQuote = not doubleQuote
elseif char == "'" and not doubleQuote then
singleQuote = not singleQuote
elseif name and (char:match("[%a%d_]") or special) then
if char:match("%d") and #name == 0 then
error "Identifiers can't start with a digit!"
end
table.insert(name, char)
elseif char == ignoreChar and ignore then
ignore = false
ignoreChar = ''
elseif name then -- We are done with gathering the name
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
lastEnd = i
end
end
if name then
table.insert(endToken, os.getenv(table.concat(name)))
name = nil
else
table.insert(endToken, unicode.sub(token, lastEnd, -1))
end
return table.concat(endToken)
end
local function resolveAlias(tokens)
local program, lastProgram = tokens[1], nil
table.remove(tokens, 1)
while true do
local alias = text.tokenize(shell.getAlias(program) or program)
program = alias[1]
if program == lastProgram then
break
end
lastProgram = program
table.remove(alias, 1)
for i = 1, #tokens do
table.insert(alias, tokens[i])
end
tokens = alias
end
return program, tokens
end
local function parseCommand(tokens)
if #tokens == 0 then
return
end
-- Variable expansion for all command parts.
for i = 1, #tokens do
tokens[i] = expandVars(tokens[i])
end
-- Resolve alias for command.
local program, args = resolveAlias(tokens)
-- Find redirects.
local input, output, mode = nil, nil, "write"
tokens = args
args = {}
local function smt(call) -- state metatable factory
local function index(_, token)
if token == "<" or token == ">" or token == ">>" then
return "parse error near " .. token
end
call(token)
return "args" -- default, return to normal arg parsing
end
return {__index=index}
end
local sm = { -- state machine for redirect parsing
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
smt(function(token)
table.insert(args, token)
end)),
input = setmetatable({}, smt(function(token)
input = token
end)),
output = setmetatable({}, smt(function(token)
output = token
mode = "write"
end)),
append = setmetatable({}, smt(function(token)
output = token
mode = "append"
end))
}
-- Run state machine over tokens.
local state = "args"
for i = 1, #tokens do
local token = tokens[i]
state = sm[state][token]
if not sm[state] then
return nil, state
end
end
return program, args, input, output, mode
end
local function parseCommands(command)
local tokens, reason = text.tokenize(command)
if not tokens then
return nil, reason
end
local commands, command = {}, {}
for i = 1, #tokens do
if tokens[i] == "|" then
if #command == 0 then
return nil, "parse error near '|'"
end
table.insert(commands, command)
command = {}
else
table.insert(command, tokens[i])
end
end
if #command > 0 then
table.insert(commands, command)
end
for i = 1, #commands do
commands[i] = table.pack(parseCommand(commands[i]))
if commands[i][1] == nil then
return nil, commands[i][2]
end
end
return commands
end
-------------------------------------------------------------------------------
local memoryStream = {}
function memoryStream:close()
self.closed = true
end
function memoryStream:seek()
return nil, "bad file descriptor"
end
function memoryStream:read(n)
if self.closed then
if self.buffer == "" and self.redirect.read then
return self.redirect.read:read(n)
end
return nil -- eof
end
if self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
end
local result = string.sub(self.buffer, 1, n)
self.buffer = string.sub(self.buffer, n + 1)
return result
end
function memoryStream:write(value)
local ok
if self.redirect.write then
ok = self.redirect.write:write(value)
end
if not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
ok = true
end
if ok then
return true
end
return nil, "stream is closed"
end
function memoryStream.new()
local stream = {closed = false, buffer = "",
redirect = {}, result = {}, args = {}}
local metatable = {__index = memoryStream,
__gc = memoryStream.close,
__metatable = "memorystream"}
return setmetatable(stream, metatable)
end
-------------------------------------------------------------------------------
function shell.getAlias(alias)
@ -307,6 +69,24 @@ function shell.aliases()
return pairs(aliases)
end
function shell.resolveAlias(command, args)
checkArg(1, command, "string")
checkArg(2, args, "table", "nil")
local program, lastProgram = command, nil
while true do
local tokens = text.tokenize(shell.getAlias(program) or program)
program = tokens[1]
if program == lastProgram then
break
end
lastProgram = program
for i = #tokens, 2, -1 do
table.insert(args, 1, tokens[i])
end
end
return program, args
end
function shell.getWorkingDirectory()
return os.getenv("PWD")
end
@ -351,86 +131,28 @@ end
function shell.execute(command, env, ...)
checkArg(1, command, "string")
local commands, reason = parseCommands(command)
if not commands then
local parts, reason = text.tokenize(command)
if not parts then
return false, reason
end
if #commands == 0 then
if #parts == 0 then
return true
end
-- Piping data between programs works like so:
-- program1 gets its output replaced with our custom stream.
-- program2 gets its input replaced with our custom stream.
-- repeat for all programs
-- custom stream triggers execution of 'next' program after write.
-- custom stream triggers yield before read if buffer is empty.
-- custom stream may have 'redirect' entries for fallback/duplication.
local threads, pipes, inputs, outputs = {}, {}, {}, {}
for i = 1, #commands do
local program, args, input, output, mode = table.unpack(commands[i])
local reason
threads[i], reason = shell.load(program, env, function()
if input then
local file, reason = io.open(shell.resolve(input))
if not file then
error(reason)
end
table.insert(inputs, file)
if pipes[i - 1] then
pipes[i - 1].stream.redirect.read = file
io.input(pipes[i - 1])
else
io.input(file)
end
elseif pipes[i - 1] then
io.input(pipes[i - 1])
end
if output then
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
if not file then
error(reason)
end
if mode == "append" then
io.write("\n")
end
table.insert(outputs, file)
if pipes[i] then
pipes[i].stream.redirect.write = file
io.output(pipes[i])
else
io.output(file)
end
elseif pipes[i] then
io.output(pipes[i])
end
end, command)
if not threads[i] then
return false, reason
end
if i < #commands then
pipes[i] = require("buffer").new("rw", memoryStream.new())
pipes[i]:setvbuf("no")
end
if i > 1 then
pipes[i - 1].stream.next = threads[i]
pipes[i - 1].stream.args = args
end
end
local args = select(2, table.unpack(commands[1]))
local program, args = shell.resolveAlias(parts[1], table.pack(select(2, table.unpack(parts))))
table.insert(args, 1, true)
for _, arg in ipairs(table.pack(...)) do
table.insert(args, arg)
end
args.n = #args
local thread, reason = shell.load(program, env, nil, command)
if not thread then
return false, reason
end
local result = nil
for i = 1, #threads do
-- Emulate CC behavior by making yields a filtered event.pull()
while args[1] and coroutine.status(threads[i]) ~= "dead" do
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
if coroutine.status(threads[i]) ~= "dead" then
while args[1] and coroutine.status(thread) ~= "dead" do
result = table.pack(coroutine.resume(thread, table.unpack(args, 2, args.n)))
if coroutine.status(thread) ~= "dead" then
if type(result[2]) == "string" then
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
else
@ -438,19 +160,6 @@ function shell.execute(command, env, ...)
end
end
end
if pipes[i] then
pipes[i]:close()
end
if i < #threads and not result[1] then
io.write(result[2])
end
end
for _, input in ipairs(inputs) do
input:close()
end
for _, output in ipairs(outputs) do
output:close()
end
if not args[1] then
return false, args[2]
end

View File

@ -40,6 +40,8 @@ function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
return from > #value and "" or string.match(value, ".*%S", from)
end
-------------------------------------------------------------------------------
function text.tokenize(value)
checkArg(1, value, "string")
local tokens, token = {}, ""
@ -77,122 +79,12 @@ function text.tokenize(value)
return tokens
end
-------------------------------------------------------------------------------
-- Important: pretty formatting will allow presenting non-serializable values
-- but may generate output that cannot be unserialized back.
function text.serialize(value, pretty)
local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true,
["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true,
["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true,
["until"]=true, ["while"]=true}
local id = "^[%a_][%w_]*$"
local ts = {}
local function s(v, l)
local t = type(v)
if t == "nil" then
return "nil"
elseif t == "boolean" then
return v and "true" or "false"
elseif t == "number" then
if v ~= v then
return "0/0"
elseif v == math.huge then
return "math.huge"
elseif v == -math.huge then
return "-math.huge"
else
return tostring(v)
end
elseif t == "string" then
return string.format("%q", v)
elseif t == "table" and pretty and getmetatable(v) and getmetatable(v).__tostring then
return tostring(v)
elseif t == "table" then
if ts[v] then
if pretty then
return "recursion"
else
error("tables with cycles are not supported")
end
end
ts[v] = true
local i, r = 1, nil
local f
if pretty then
local ks = {}
for k in pairs(v) do table.insert(ks, k) end
table.sort(ks)
local n = 0
f = table.pack(function()
n = n + 1
local k = ks[n]
if k ~= nil then
return k, v[k]
else
return nil
end
end)
else
f = table.pack(pairs(v))
end
for k, v in table.unpack(f) do
if r then
r = r .. "," .. (pretty and ("\n" .. string.rep(" ", l)) or "")
else
r = "{"
end
local tk = type(k)
if tk == "number" and k == i then
i = i + 1
r = r .. s(v, l + 1)
else
if tk == "string" and not kw[k] and string.match(k, id) then
r = r .. k
else
r = r .. "[" .. s(k, l + 1) .. "]"
end
r = r .. "=" .. s(v, l + 1)
end
end
ts[v] = nil -- allow writing same table more than once
return (r or "{") .. "}"
else
if pretty then
return tostring(t)
else
error("unsupported type: " .. t)
end
end
end
local result = s(value, 1)
local limit = type(pretty) == "number" and pretty or 10
if pretty then
local truncate = 0
while limit > 0 and truncate do
truncate = string.find(result, "\n", truncate + 1, true)
limit = limit - 1
end
if truncate then
return result:sub(1, truncate) .. "..."
end
end
return result
function text.serialize(value, pretty) -- deprecated, use serialization module
return require("serialization").serialize(value, pretty)
end
function text.unserialize(data)
checkArg(1, data, "string")
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
if not result then
return nil, reason
end
local ok, output = pcall(result)
if not ok then
return nil, output
end
return output
function text.unserialize(data) -- deprecated, use serialization module
return require("serialization").unserialize(data)
end
-------------------------------------------------------------------------------

View File

@ -4,7 +4,7 @@
"modid": "OpenComputers",
"name": "OpenComputers",
"description": "This mod adds modular computers and robots that can be programmed in Lua.",
"version": "1.2.0",
"version": "1.2.1",
"mcversion": "1.6.4",
"url": "https://github.com/MightyPirates/OpenComputers/wiki",
"authors": ["Florian 'Sangar' Nücke", "Johannes 'Lord Joda' Lohrer", "Everyone who contributed to the mod on Github - thank you!"],