|
- -- shamelessly stolen from plan9k
-
- buffer = {}
-
- function buffer.new(mode, stream) -- string table -- table -- create a new buffer in mode *mode* backed by stream object *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 badFileDescriptor()
- 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
-
- 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 badFileDescriptor()
- 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
|