| local computer = require("computer") |
| local unicode = require("unicode") |
| |
| local 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" |
| } |
| 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 |
| |
| function buffer:close() |
| if self.mode.w or self.mode.a then |
| self:flush() |
| end |
| 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 function readChunk() |
| 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 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 |
| --[[ TODO ]] |
| error("not implemented") |
| 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 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:write(...) |
| 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 |
| |
| return buffer |