1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
-- cqueue filesystem with luaposix
-- TODO: closing files
-- TODO: error handling. right now it *always* returns errno instead of
-- throwing it, which may be what we want. code depends on this
local unistd = require("posix.unistd")
local cqueues = require("cqueues")
local file = {}
function file.new(fd, mode)
if not mode then mode = "r" end
local f = setmetatable({}, { __index = file })
f.pollfd = fd
f.mode = mode
if string.match(mode, "r") then
f.rbuf = {}
end
if string.match(mode, "w") then
f.wbuf = {}
end
return f
end
function file.events(f)
return f.mode
end
-- input/output buffers
--
-- buffers are arrays of objects containing "data" (a string) and "index" (their position)
-- indexing allows us to avoid recreating string objects
local function buffer_length(buf)
local total = 0
for _, entry in ipairs(buf) do
total = total + #entry.data - entry.index + 1
end
return total
end
-- returns nil on empty length
local function buffer_get(buf, length)
local out = {}
if not length then length = buffer_length(buf) end
while length > 0 do
assert(#buf > 0)
local x = string.sub(buf[1].data, buf[1].index, buf[1].index + length - 1)
table.insert(out, x)
if #buf[1].data - buf[1].index + 1 <= length then
table.remove(buf, 1)
else
buf[1].index = buf[1].index + length
end
length = length - #x
end
if out[1] then return table.concat(out) else return nil end
end
local function buffer_char_index(buf, char)
local num = 1
for _, entry in ipairs(buf) do
if string.find(entry.data, char, entry.index, true) then
return num + string.find(entry.data, char, entry.index, true) - entry.index
else
num = num + #entry.data - entry.index + 1
end
end
return nil
end
do
local test_buf = { { data = "abcde", index = 2 }, { data = "\na", index = 1 } }
assert(buffer_length(test_buf) == 6)
assert(buffer_char_index(test_buf, "\n") == 5)
assert(buffer_get(test_buf, 5) == "bcde\n")
assert(buffer_length(test_buf) == 1)
end
local function try_read(buffer, fd, max)
cqueues.poll({ pollfd = fd, events = function () return "r" end })
local data, _, errno = unistd.read(fd, max)
if not data then return nil, errno end
if #data == 0 then return nil end
table.insert(buffer, { data = data, index = 1 })
return #data
end
function file.read(f, what)
assert(f.rbuf)
if (type(what) == "number" and what > 0) then
while buffer_length(f.rbuf) < what do
if not try_read(f.rbuf, f.pollfd, what - buffer_length(f.rbuf)) then
break
end
end
return buffer_get(f.rbuf, math.min(what, buffer_length(f.rbuf)))
elseif (type(what) == "number" and what < 0) then
while buffer_length(f.rbuf) == 0 do
if not try_read(f.rbuf, f.pollfd, 0 - what) then return nil end
end
return buffer_get(f.rbuf, math.min(0 - what, buffer_length(f.rbuf)))
elseif what == "*l" or what == "*L" then
while not buffer_char_index(f.rbuf, "\n") do
-- TODO: constantize this
local x = try_read(f.rbuf, f.pollfd, 1024)
if not x then
break
end
end
local line = buffer_get(f.rbuf, buffer_char_index(f.rbuf, "\n"))
if not line then
return nil
end
local ret = string.gsub(
line,
"\n",
(what == "*l" and "" or "\n")
)
return ret
else
-- TODO
end
end
function file.lines(f, what)
return function () return file.read(f, what or "*l") end
end
function file.write(f, data)
local index = 1
while index <= #data do
cqueues.poll({ pollfd = f.pollfd, events = function () return "w" end })
local nb, _, errno = unistd.write(f.pollfd, string.sub(data, index))
if nb then
index = index + nb
else
return nil, errno -- TODO?
end
end
return f
end
function file.close(f)
cqueues.cancel(f.pollfd)
end
return file
|