From 201aa20c5af6cb0095c60359560eb9a3ee3ae248 Mon Sep 17 00:00:00 2001 From: XeonSquared Date: Mon, 16 Mar 2020 17:30:22 +1100 Subject: [PATCH] added the plan9k buffer library because it's cleaner than the old io library --- module/buffer.lua | 429 ++++++++++++++++++++++++++++++++++++++++++++ module/fs.lua | 27 +-- module/init.lua | 3 +- module/io.lua | 39 ++++ module/loadfile.lua | 4 +- module/vt-task.lua | 2 +- service/getty.lua | 2 +- 7 files changed, 477 insertions(+), 29 deletions(-) create mode 100644 module/buffer.lua create mode 100644 module/io.lua diff --git a/module/buffer.lua b/module/buffer.lua new file mode 100644 index 0000000..34d27a1 --- /dev/null +++ b/module/buffer.lua @@ -0,0 +1,429 @@ +-- shamelessly stolen from plan9k + +buffer = {} + +function buffer.new(mode, stream) + local result = { + mode = {}, + stream = stream, + bufferRead = "", + bufferWrite = "", + bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)), + bufferMode = "full", + readTimeout = math.huge + } + mode = mode or "r" + for i = 1, unicode.len(mode) do + result.mode[unicode.sub(mode, i, i)] = true + end + local metatable = { + __index = buffer, + __metatable = "file" + } + return setmetatable(result, metatable) +end + + +local function badFileDescriptor() + return nil, "bad file descriptor" +end + +function buffer:close() + if self.mode.w or self.mode.a then + self:flush() + end + self.closed = true + return self.stream:close() +end + +function buffer:flush() + local result, reason = self.stream:write(self.bufferWrite) + if result then + self.bufferWrite = "" + else + if reason then + return nil, reason + else + return nil, "bad file descriptor" + end + end + + return self +end + +function buffer:lines(...) + local args = table.pack(...) + return function() + local result = table.pack(self:read(table.unpack(args, 1, args.n))) + if not result[1] and result[2] then + error(result[2]) + end + return table.unpack(result, 1, result.n) + end +end + +function buffer:read(...) + local timeout = computer.uptime() + self.readTimeout + + local function readChunk() + if computer.uptime() > timeout then + error("timeout") + end + local result, reason = self.stream:read(self.bufferSize) + if result then + self.bufferRead = self.bufferRead .. result + return self + else -- error or eof + return nil, reason + end + end + + local function readBytesOrChars(n) + n = math.max(n, 0) + local len, sub + if self.mode.b then + len = rawlen + sub = string.sub + else + len = unicode.len + sub = unicode.sub + end + local buffer = "" + repeat + if len(self.bufferRead) == 0 then + local result, reason = readChunk() + if not result then + if reason then + return nil, reason + else -- eof + return #buffer > 0 and buffer or nil + end + end + end + local left = n - len(buffer) + buffer = buffer .. sub(self.bufferRead, 1, left) + self.bufferRead = sub(self.bufferRead, left + 1) + until len(buffer) == n + + --kernel.io.println("buffer read: "..tostring(buffer)) + return buffer + end + + local function readNumber() + local len, sub + if self.mode.b then + len = rawlen + sub = string.sub + else + len = unicode.len + sub = unicode.sub + end + local buffer = "" + local first = true + local decimal = false + local last = false + local hex = false + local pat = "^[0-9]+" + local minbuf = 3 -- "+0x" (sign + hexadecimal tag) + -- this function is used to read trailing numbers (1e2, 0x1p2, etc) + local function readnum(checksign) + local _buffer = "" + local sign = "" + while true do + if len(self.bufferRead) == 0 then + local result, reason = readChunk() + if not result then + if reason then + return nil, reason + else -- eof + return #_buffer > 0 and (sign .. _buffer) or nil + end + end + end + if checksign then + local _sign = sub(self.bufferRead, 1, 1) + if _sign == "+" or _sign == "-" then + -- "eat" the sign (Rio Lua behaviour) + sign = sub(self.bufferRead, 1, 1) + self.bufferRead = sub(self.bufferRead, 2) + end + checksign = false + else + local x,y = string.find(self.bufferRead, pat) + if not x then + break + else + _buffer = _buffer .. sub(self.bufferRead, 1, y) + self.bufferRead = sub(self.bufferRead, y + 1) + end + end + end + return #_buffer > 0 and (sign .. _buffer) or nil + end + while true do + if len(self.bufferRead) == 0 or len(self.bufferRead) < minbuf then + local result, reason = readChunk() + if not result then + if reason then + return nil, reason + else -- eof + return #buffer > 0 and tonumber(buffer) or nil + end + end + end + -- these ifs are here so we run the buffer check above + if first then + local sign = sub(self.bufferRead, 1, 1) + if sign == "+" or sign == "-" then + -- "eat" the sign (Rio Lua behaviour) + buffer = buffer .. sub(self.bufferRead, 1, 1) + self.bufferRead = sub(self.bufferRead, 2) + end + local hextag = sub(self.bufferRead, 1, 2) + if hextag == "0x" or hextag == "0X" then + pat = "^[0-9A-Fa-f]+" + -- "eat" the 0x, see https://gist.github.com/SoniEx2/570a363d81b743353151 + buffer = buffer .. sub(self.bufferRead, 1, 2) + self.bufferRead = sub(self.bufferRead, 3) + hex = true + end + minbuf = 0 + first = false + elseif decimal then + local sep = sub(self.bufferRead, 1, 1) + if sep == "." then + buffer = buffer .. sep + self.bufferRead = sub(self.bufferRead, 2) + local temp = readnum(false) -- no sign + if temp then + buffer = buffer .. temp + end + end + if not tonumber(buffer) then break end + decimal = false + last = true + minbuf = 1 + elseif last then + local tag = sub(self.bufferRead, 1, 1) + if hex and (tag == "p" or tag == "P") then + local temp = sub(self.bufferRead, 1, 1) + self.bufferRead = sub(self.bufferRead, 2) + local temp2 = readnum(true) -- this eats the next sign if any + if temp2 then + buffer = buffer .. temp .. temp2 + end + elseif tag == "e" or tag == "E" then + local temp = sub(self.bufferRead, 1, 1) + self.bufferRead = sub(self.bufferRead, 2) + local temp2 = readnum(true) -- this eats the next sign if any + if temp2 then + buffer = buffer .. temp .. temp2 + end + end + break + else + local x,y = string.find(self.bufferRead, pat) + if not x then + minbuf = 1 + decimal = true + else + buffer = buffer .. sub(self.bufferRead, 1, y) + self.bufferRead = sub(self.bufferRead, y + 1) + end + end + end + return tonumber(buffer) + end + + local function readLine(chop) + local start = 1 + while true do + local l = self.bufferRead:find("\n", start, true) + if l then + local result = self.bufferRead:sub(1, l + (chop and -1 or 0)) + self.bufferRead = self.bufferRead:sub(l + 1) + return result + else + start = #self.bufferRead + local result, reason = readChunk() + if not result then + if reason then + return nil, reason + else -- eof + local result = #self.bufferRead > 0 and self.bufferRead or nil + self.bufferRead = "" + return result + end + end + end + end + end + + local function readAll() + repeat + local result, reason = readChunk() + if not result and reason then + return nil, reason + end + until not result -- eof + local result = self.bufferRead + self.bufferRead = "" + return result + end + + local function read(n, format) + if type(format) == "number" then + return readBytesOrChars(format) + else + if type(format) ~= "string" or unicode.sub(format, 1, 1) ~= "*" then + error("bad argument #" .. n .. " (invalid option)") + end + format = unicode.sub(format, 2, 2) + if format == "n" then + return readNumber() + elseif format == "l" then + return readLine(true) + elseif format == "L" then + return readLine(false) + elseif format == "a" then + return readAll() + else + error("bad argument #" .. n .. " (invalid format)") + end + end + end + + if self.mode.w or self.mode.a then + self:flush() + end + + local results = {} + local formats = table.pack(...) + if formats.n == 0 then + return readLine(true) + end + for i = 1, formats.n do + local result, reason = read(i, formats[i]) + if result then + results[i] = result + elseif reason then + return nil, reason + end + end + return table.unpack(results, 1, formats.n) +end + +function buffer:seek(whence, offset) + whence = tostring(whence or "cur") + assert(whence == "set" or whence == "cur" or whence == "end", + "bad argument #1 (set, cur or end expected, got " .. whence .. ")") + offset = offset or 0 + checkArg(2, offset, "number") + assert(math.floor(offset) == offset, "bad argument #2 (not an integer)") + + if self.mode.w or self.mode.a then + self:flush() + elseif whence == "cur" then + offset = offset - #self.bufferRead + end + local result, reason = self.stream:seek(whence, offset) + if result then + self.bufferRead = "" + return result + else + return nil, reason + end +end + +function buffer:setvbuf(mode, size) + mode = mode or self.bufferMode + size = size or self.bufferSize + + assert(mode == "no" or mode == "full" or mode == "line", + "bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")") + assert(mode == "no" or type(size) == "number", + "bad argument #2 (number expected, got " .. type(size) .. ")") + + self.bufferMode = mode + self.bufferSize = size + + return self.bufferMode, self.bufferSize +end + +function buffer:getTimeout() + return self.readTimeout +end + +function buffer:setTimeout(value) + self.readTimeout = tonumber(value) +end + +function buffer:write(...) + if self.closed then + return nil, "bad file descriptor" + end + local args = table.pack(...) + for i = 1, args.n do + if type(args[i]) == "number" then + args[i] = tostring(args[i]) + end + checkArg(i, args[i], "string") + end + + for i = 1, args.n do + local arg = args[i] + local result, reason + + if self.bufferMode == "full" then + if self.bufferSize - #self.bufferWrite < #arg then + result, reason = self:flush() + if not result then + return nil, reason + end + end + if #arg > self.bufferSize then + result, reason = self.stream:write(arg) + else + self.bufferWrite = self.bufferWrite .. arg + result = self + end + + elseif self.bufferMode == "line" then + local l + repeat + local idx = arg:find("\n", (l or 0) + 1, true) + if idx then + l = idx + end + until not idx + if l or #arg > self.bufferSize then + result, reason = self:flush() + if not result then + return nil, reason + end + end + if l then + result, reason = self.stream:write(arg:sub(1, l)) + if not result then + return nil, reason + end + arg = arg:sub(l + 1) + end + if #arg > self.bufferSize then + result, reason = self.stream:write(arg) + else + self.bufferWrite = self.bufferWrite .. arg + result = self + end + + else -- self.bufferMode == "no" + result, reason = self.stream:write(arg) + end + + if not result then + return nil, reason + end + end + + return self +end diff --git a/module/fs.lua b/module/fs.lua index 197cd60..b044a57 100644 --- a/module/fs.lua +++ b/module/fs.lua @@ -32,37 +32,16 @@ for k,v in pairs({"makeDirectory","exists","isDirectory","list","lastModified"," end local function fread(self,length) - if length == "*a" then - length = math.huge - end - if type(length) == "number" then - local rstr, lstr = "", "" - repeat - lstr = fsmounts[self.fs].read(self.fid,math.min(2^16,length-rstr:len())) or "" - rstr = rstr .. lstr - until rstr:len() == length or lstr == "" - return rstr - elseif type(length) == "string" then - local buf = "" - if length == "*l" then - length = "\n" - end - repeat - local rb = fsmounts[self.fs].read(self.fid,1) or "" - buf = buf .. rb - until buf:match(length) or rb == "" - return buf:match("(.*)"..length) - end return fsmounts[self.fs].read(self.fid,length) end local function fwrite(self,data) - fsmounts[self.fs].write(self.fid,data) + return fsmounts[self.fs].write(self.fid,data) end local function fseek(self,dist) - fsmounts[self.fs].seek(self.fid,dist) + return fsmounts[self.fs].seek(self.fid,dist) end local function fclose(self) - fsmounts[self.fs].close(self.fid) + return fsmounts[self.fs].close(self.fid) end function fs.open(path,mode) -- opens file *path* with mode *mode* diff --git a/module/init.lua b/module/init.lua index 3412bef..9631452 100644 --- a/module/init.lua +++ b/module/init.lua @@ -1,8 +1,9 @@ --#include "module/syslog.lua" --#include "module/sched.lua" +--#include "module/buffer.lua" --#include "module/osutil.lua" --#include "module/fs.lua" ---#include "module/newio.lua" +--#include "module/io.lua" --#include "module/devfs.lua" --#include "module/devfs/syslog.lua" --#include "module/vt-task.lua" diff --git a/module/io.lua b/module/io.lua new file mode 100644 index 0000000..b3401f0 --- /dev/null +++ b/module/io.lua @@ -0,0 +1,39 @@ +io = {} + +function io.open(path,mode) + local f,e = fs.open(path, mode) + if not f then return false, e end + return buffer.new(mode,f) +end + +function io.input(fd) + if type(fd) == "string" then + fd=io.open(fd,"rb") + end + if fd then + os.setenv("STDIN",fd) + end + return os.getenv("STDIN") +end +function io.output(fd) + if type(fd) == "string" then + fd=io.open(fd,"wb") + end + if fd then + os.setenv("STDOUT",fd) + end + return os.getenv("STDOUT") +end + +function io.read(...) + return io.input():read() +end +function io.write(...) + io.output():write(...) +end + +function print(...) + for k,v in ipairs({...}) do + io.write(tostring(v).."\n") + end +end diff --git a/module/loadfile.lua b/module/loadfile.lua index 4e305e3..cb5acc1 100644 --- a/module/loadfile.lua +++ b/module/loadfile.lua @@ -1,5 +1,5 @@ function loadfile(p) -- reads file *p* and returns a function if possible - local f = fs.open(p,"rb") + local f = io.open(p,"rb") local c = f:read("*a") f:close() return load(c,p,"t") @@ -9,7 +9,7 @@ function runfile(p,...) -- runs file *p* with arbitrary arguments in the current end function os.spawnfile(p,n,...) -- spawns a new process from file *p* with name *n*, with arguments following *n*. local tA = {...} - return os.spawn(function() computer.pushSignal("process_finished", os.pid(), pcall(loadfile(p), table.unpack(tA))) end,n or p) + return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p) end _G.libs = {computer=computer,component=component} function require(f) -- searches for a library with name *f* and returns what the library returns, if possible diff --git a/module/vt-task.lua b/module/vt-task.lua index d465c1c..6f61c5c 100644 --- a/module/vt-task.lua +++ b/module/vt-task.lua @@ -31,7 +31,7 @@ function vtemu(gpua,scra) -- creates a process to handle the GPU and screen addr coroutine.yield() end local n = buf:find("\n") - r, buf = buf:sub(1,n-1), buf:sub(n+1) + r, buf = buf:sub(1,n), buf:sub(n+1) return r end return bread, write, function() io.write("\27[2J\27[H") end diff --git a/service/getty.lua b/service/getty.lua index 5a4325c..91c9ce4 100644 --- a/service/getty.lua +++ b/service/getty.lua @@ -32,7 +32,7 @@ end local function spawnShell(fin,fout) io.input(fin) - io.output(fout) + io.output(fout):setvbuf("no") print(_OSVERSION.." - "..tostring(math.floor(computer.totalMemory()/1024)).."K RAM") return os.spawnfile("/boot/exec/shell.lua") end