diff --git a/build.gradle b/build.gradle index 01f3e4d68..f88f66889 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ buildscript { apply plugin: 'forge' apply plugin: 'scala' -version = "2.0.0" +version = "2.0.1" group= "li.cil.oc" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "OpenComputers" diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index 4e0e6dd37..baec22ae5 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -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, diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua new file mode 100644 index 000000000..2fbb109dc --- /dev/null +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua @@ -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, ...) \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua index ca96e704e..5be963a2c 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua @@ -7,6 +7,7 @@ if #dirs == 0 then table.insert(dirs, ".") end +io.output():setvbuf("line") for i = 1, #dirs do local path = shell.resolve(dirs[i]) if #dirs > 1 then @@ -21,6 +22,7 @@ for i = 1, #dirs do else local function setColor(c) if component.gpu.getForeground() ~= c then + io.stdout:flush() component.gpu.setForeground(c) end end @@ -67,4 +69,5 @@ for i = 1, #dirs do io.write("\n") end end -end \ No newline at end of file +end +io.output():setvbuf("no") \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/lua.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/lua.lua index 60d95eba7..dbc6f368d 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/lua.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/lua.lua @@ -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) diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/man.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/man.lua index 8978409c8..88ac41518 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/man.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/man.lua @@ -10,9 +10,9 @@ end local topic = args[1] for path in string.gmatch(os.getenv("MANPATH"), "[^:]+") do - path = fs.concat(path, topic) + path = shell.resolve(fs.concat(path, topic), "man") if fs.exists(path) and not fs.isDirectory(path) then - os.execute("more " .. path) + os.execute(os.getenv("PAGER") .. " " .. path) os.exit() end end diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/more.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/more.lua index c105f14f9..a1f2d2b6c 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/more.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/more.lua @@ -2,11 +2,12 @@ local component = require("component") local keyboard = require("keyboard") local shell = require("shell") local term = require("term") +local text = require("text") local unicode = require("unicode") local args = shell.parse(...) if #args == 0 then - io.write("Usage: less ") + io.write("Usage: more ") return end @@ -29,13 +30,9 @@ while true do return end end - if unicode.len(line) > w then - io.write(unicode.sub(line, 1, w), "\n") - line = unicode.sub(line, w + 1) - else - io.write(line, "\n") - line = nil - end + local wrapped + wrapped, line = text.wrap(text.detab(line), w, w) + io.write(wrapped .. "\n") i = i + 1 end term.setCursor(1, h) @@ -53,4 +50,4 @@ while true do end end end -end +end \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/set.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/set.lua index 85d0340ef..0a049dcda 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/set.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/set.lua @@ -5,14 +5,19 @@ if #args < 1 then io.write(k, "='", string.gsub(v, "'", [['"'"']]), "'\n") end else - local count = 1 + local count = 0 for _, expr in ipairs(args) do local k, v = string.match(expr, "(.-)=(.*)") if v then os.setenv(k, v) else - os.setenv(tostring(count), k) + if count == 0 then + for i = 1, os.getenv('#') do + os.setenv(i, nil) + end + end count = count + 1 + os.setenv(count, expr) end end end diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua index b60c13183..f5547cfc9 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/lua/rom/boot/01_os.lua b/src/main/resources/assets/opencomputers/lua/rom/boot/01_os.lua index 96147a022..3124dd635 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/boot/01_os.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/boot/01_os.lua @@ -7,7 +7,9 @@ local unicode = require("unicode") local env = { HOME="/home", MANPATH="/usr/man", + PAGER="/bin/more", PATH="/bin:/usr/bin:/home/bin:.", + PS1="# ", PWD="/", SHELL="/bin/sh", TMP="/tmp" @@ -25,7 +27,9 @@ function os.exit(code) end function os.getenv(varname) - if varname ~= nil then + if varname == '#' then + return #env + elseif varname ~= nil then return env[varname] else return env @@ -33,9 +37,18 @@ function os.getenv(varname) end function os.setenv(varname, value) - checkArg(1, varname, "string") - env[varname] = value - return env[varname] + checkArg(1, varname, "string", "number") + if value == nil then + env[varname] = nil + else + local success, val = pcall(tostring, value) + if success then + env[varname] = val + return env[varname] + else + return nil, val + end + end end function os.remove(...) diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/serialization.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/serialization.lua new file mode 100644 index 000000000..8d22548a7 --- /dev/null +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/serialization.lua @@ -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 \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua index 901ca2e03..d21026ba5 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua @@ -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] diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua index bd0b9b579..2298b0b2c 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua @@ -102,21 +102,22 @@ function term.read(history) scrollY = nby - 1 + nbx = math.max(1, math.min(unicode.len(history[nby]) + 1, nbx)) local ncx = nbx + offset - scrollX if ncx > w then local sx = nbx - (w - offset) local dx = math.abs(scrollX - sx) scrollX = sx component.gpu.copy(1 + offset + dx, cy, w - offset - dx, 1, -dx, 0) - local str = unicode.sub(line(), nbx - (dx - 1), nbx) + local str = unicode.sub(history[nby], nbx - (dx - 1), nbx) str = text.padRight(str, dx) - component.gpu.set(1 + (w - dx), cy, str) + component.gpu.set(1 + math.max(offset, w - dx), cy, unicode.sub(str, 1 + math.max(0, dx - (w - offset)))) elseif ncx < 1 + offset then local sx = nbx - 1 local dx = math.abs(scrollX - sx) scrollX = sx component.gpu.copy(1 + offset, cy, w - offset - dx, 1, dx, 0) - local str = unicode.sub(line(), nbx, nbx + dx) + local str = unicode.sub(history[nby], nbx, nbx + dx) --str = text.padRight(str, dx) component.gpu.set(1 + offset, cy, str) end @@ -172,7 +173,7 @@ function term.read(history) local function up() local cbx, cby = getCursor() if cby > 1 then - setCursor(cbx, cby - 1) + setCursor(1, cby - 1) redraw() ende() end @@ -181,7 +182,7 @@ function term.read(history) local function down() local cbx, cby = getCursor() if cby < #history then - setCursor(cbx, cby + 1) + setCursor(1, cby + 1) redraw() ende() end @@ -337,8 +338,14 @@ function term.write(value, wrap) end local blink = term.getCursorBlink() term.setCursorBlink(false) - local function checkCursor() - if cursorX > w then + local line, nl = value + repeat + if wrap then + line, value, nl = text.wrap(value, w - (cursorX - 1), w) + end + component.gpu.set(cursorX, cursorY, line) + cursorX = cursorX + unicode.len(line) + if nl or cursorX > w then cursorX = 1 cursorY = cursorY + 1 end @@ -347,28 +354,7 @@ function term.write(value, wrap) component.gpu.fill(1, h, w, 1, " ") cursorY = h end - end - for line, nl in value:gmatch("([^\r\n]*)([\r\n]?)") do - while wrap and unicode.len(line) > w - (cursorX - 1) do - local partial = unicode.sub(line, 1, w - (cursorX - 1)) - local wordWrapped = partial:match("(.*[^a-zA-Z0-9._])") - if wordWrapped or unicode.len(line) > w then - partial = wordWrapped or partial - line = unicode.sub(line, unicode.len(partial) + 1) - component.gpu.set(cursorX, cursorY, partial) - end - cursorX = math.huge - checkCursor() - end - if unicode.len(line) > 0 then - component.gpu.set(cursorX, cursorY, line) - cursorX = cursorX + unicode.len(line) - end - if unicode.len(nl) == 1 then - cursorX = math.huge - checkCursor() - end - end + until not wrap or not value term.setCursorBlink(blink) end diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/text.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/text.lua index 001943021..5e17aa8b3 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/lib/text.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/text.lua @@ -5,7 +5,7 @@ local text = {} function text.detab(value, tabWidth) checkArg(1, value, "string") checkArg(2, tabWidth, "number", "nil") - tabWidth = tabWidth or 4 + tabWidth = tabWidth or 8 local function rep(match) local spaces = tabWidth - match:len() % tabWidth return match .. string.rep(" ", spaces) @@ -40,6 +40,26 @@ function text.trim(value) -- from http://lua-users.org/wiki/StringTrim return from > #value and "" or string.match(value, ".*%S", from) end +function text.wrap(value, width, maxWidth) + checkArg(1, value, "string") + checkArg(2, width, "number") + local line, nl = value:match("([^\r\n]*)([\r\n]?)") -- read until newline + if unicode.len(line) > width then -- do we even need to wrap? + local partial = unicode.sub(line, 1, width) + local wrapped = partial:match("(.*[^a-zA-Z0-9._])") + if wrapped or unicode.len(line) > maxWidth then + partial = wrapped or partial + return partial, unicode.sub(value, unicode.len(partial) + 1), true + else + return "", value, true -- write in new line. + end + end + local start = unicode.len(line) + unicode.len(nl) + 1 + return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0 +end + +------------------------------------------------------------------------------- + function text.tokenize(value) checkArg(1, value, "string") local tokens, token = {}, "" @@ -77,122 +97,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 ------------------------------------------------------------------------------- diff --git a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala index ab1fd1bd8..4faca00b9 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -491,7 +491,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