mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-19 12:17:17 -04:00
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:
parent
b5bd97807c
commit
95bca5ced5
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
351
src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua
Normal file
351
src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua
Normal 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, ...)
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -8,6 +8,7 @@ local env = {
|
||||
HOME="/home",
|
||||
MANPATH="/usr/man",
|
||||
PATH="/bin:/usr/bin:/home/bin:.",
|
||||
PS1="# ",
|
||||
PWD="/",
|
||||
SHELL="/bin/sh",
|
||||
TMP="/tmp"
|
||||
|
@ -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
|
@ -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,105 +131,34 @@ 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
|
||||
if type(result[2]) == "string" then
|
||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
args = {true, n=1}
|
||||
end
|
||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||
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
|
||||
args = {true, n=1}
|
||||
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]
|
||||
|
@ -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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
@ -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!"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user