-
Notifications
You must be signed in to change notification settings - Fork 14
/
cursor.lua
98 lines (79 loc) · 2.32 KB
/
cursor.lua
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
-- cursor - a wrapper for strings that makes them look like files
-- exports: seek read write
-- read only supports numeric amounts
local cursor = {}
-- like fseek
-- seeking past the end of the string is permitted
-- reads will return EOF, writes will fill in the intermediate space with nuls
-- seeking past the start of the string is a soft error
function cursor:seek(whence, offset)
self:flush()
whence = whence or "cur"
offset = offset or 0
if whence == "set" then
self.pos = offset
elseif whence == "cur" then
self.pos = self.pos + offset
elseif whence == "end" then
self.pos = #self.str + offset
else
error "bad argument #1 to seek"
end
if self.pos < 0 then
self.pos = 0
return nil,"attempt to seek prior to start of file"
end
return self.pos
end
-- read n bytes from the current position
-- reads longer than the string can satisfy return as much as it can
-- reads while the position is at the end return nil,"eof"
function cursor:read(n)
self:flush()
if self.pos >= #self.str then
return nil,"eof"
end
if n == "*a" then
n = #self.str
end
local buf = self.str:sub(self.pos+1, self.pos + n)
self.pos = math.min(self.pos + n, #self.str)
return buf
end
-- write the contents of the buffer at the current position, overwriting
-- any data already present
-- if the write pointer is past the end of the string, also fill in the
-- intermediate space with nuls
-- Internally, this just appends it to an internal buffer which is added to
-- the string when needed.
function cursor:write(buf)
table.insert(self.buf, buf)
return self
end
function cursor:flush()
if #self.buf == 0 then
return
end
-- Pad end with \0 if we're writing past end of file
if self.pos > #self.str then
self.str = self.str .. string.char(0):rep(self.pos - #self.str)
end
-- Concatenate queued writes
local buf = table.concat(self.buf)
-- Append or splice into the string as needed
self.str = self.str:sub(1, self.pos)
.. buf
.. self.str:sub(self.pos + #buf + 1, -1)
self.pos = self.pos + #buf
self.buf = {}
end
cursor.__index = cursor
setmetatable(cursor, {
__call = function(self, source)
assert(type(source) == "string", "invalid first argument to cursor()")
return setmetatable(
{ str = source, pos = 0, buf = {} },
cursor)
end;
})
return cursor