stop using tasks in start_reading/start_writing
We just need queues/streams for file I/O. No need to complect concurrency concerns with them.
This commit is contained in:
parent
ab89be1ed3
commit
b9c187d259
|
@ -3600,35 +3600,26 @@ idiomatic Teliva uses some slightly different primitives.
|
|||
<hr><h3><a name="pdf-start_reading"><code>start_reading (fs, filename)</code></a></h3>
|
||||
|
||||
<p>
|
||||
This function opens a file exclusively for reading, and returns a
|
||||
<code>channel</code> (NOT a file as in Lua) or <b>nil</b> on error.
|
||||
<a href='#pdf-channel:recv'><code>recv</code></a> from the channel to read a
|
||||
line at a time from the file.
|
||||
This function opens a file exclusively for reading, and returns an object (NOT
|
||||
a file handle as in Lua) or <b>nil</b> on error. If the returned object is
|
||||
stored in variable <code>f</code>, <code>f.read(format)</code> will read from
|
||||
the file. Legal values for <code>format</code> are identical to
|
||||
<a href='#pdf-file:read'><code>file:read(format)</code></a>.
|
||||
|
||||
<p>
|
||||
(The <code>fs</code> parameter is currently unused. It will be used to pass in
|
||||
fake file systems for tests.)
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-character_by_character"><code>character_by_character(chanin, chanout)</code></a></h3>
|
||||
|
||||
<p>
|
||||
This function converts a channel that can <a href='#pdf-channel:recv'><code>recv</code></a>
|
||||
a line at a time from a file into a channel that can <a href='#pdf-channel:recv'><code>recv</code></a>
|
||||
a character at a time. Don't try to mix by-line reads with by-character reads.
|
||||
Once a channel is passed into <code>character_by_character</code>, calling
|
||||
code should stop trying to use it.
|
||||
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-start_writing"><code>start_writing(fs, filename)</code></a></h3>
|
||||
|
||||
<p>
|
||||
This function opens a file exclusively for writing, and returns a
|
||||
<code>channel</code> (NOT a file as in Lua) or <b>nil</b> on error.
|
||||
<a href='#pdf-channel:send'><code>send</code></a> to the channel to write to
|
||||
the file. <code>close</code> on the channel will persist the changes and make
|
||||
them externally visible. All writes are hidden until <code>close</code>.
|
||||
This function opens a file exclusively for writing, and returns an object (NOT
|
||||
a file handle as in Lua) or <b>nil</b> on error. If the result is stored in
|
||||
variable <code>f</code>, <code>f.write(x)</code> will write <code>x</code> to
|
||||
the file. <code>f.close()</code> will persist the changes and make them
|
||||
externally visible. All changes will be hidden until <code>f.close()</code>.
|
||||
|
||||
<p>
|
||||
(The <code>fs</code> parameter is currently unused. It will be used to pass in
|
||||
|
@ -4408,17 +4399,19 @@ Returns a value representing the JSON string <code>str</code>.
|
|||
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-jsonf.decode"><code>jsonf.decode (chan)</code></a></h3>
|
||||
<hr><h3><a name="pdf-jsonf.decode"><code>jsonf.decode (f)</code></a></h3>
|
||||
|
||||
|
||||
<p>
|
||||
Returns a value representing the JSON string read from channel
|
||||
<code>chan</code>.
|
||||
Reads a value encoded in JSON from a file and returns it.
|
||||
<code>f</code> is the type of file object returned by
|
||||
<a href='#pdf-start_reading'><code>start_reading</code></a> (i.e. supporting
|
||||
<code>f.read(format)</code>).
|
||||
|
||||
For example, suppose file <code>foo</code> contains '[1,2,3,{"x":10}]'. Then:
|
||||
<pre>
|
||||
local channel = task.Channel:new()
|
||||
channel:send('[1,2,3,{"x":10}]')
|
||||
jsonf.decode(channel) -- Returns { 1, 2, 3, { x = 10 } }
|
||||
local infile = start_reading(nil, 'foo')
|
||||
jsonf.decode(infile) -- Returns { 1, 2, 3, { x = 10 } }
|
||||
</pre><p>
|
||||
|
||||
|
||||
|
|
2
life.tlv
2
life.tlv
|
@ -522,7 +522,7 @@
|
|||
> if infile == nil then return end
|
||||
> local line_index = lines -- quarter of the way down in pixels
|
||||
> while true do
|
||||
> local line = infile:recv()
|
||||
> local line = infile.read()
|
||||
> if line == nil then break end
|
||||
> if line:sub(1,1) ~= '!' then -- comment; plaintext files can't have whitespace before comments
|
||||
> local col_index = cols
|
||||
|
|
79
src/file.lua
79
src/file.lua
|
@ -1,42 +1,43 @@
|
|||
-- primitive for reading files from a file system (or, later, network)
|
||||
-- returns a channel or nil on error
|
||||
-- read lines from the channel using :recv()
|
||||
-- recv() on the channel will indicate end of file.
|
||||
-- returns an object or nil on error
|
||||
-- read lines from the object using .read() with args similar to file:read()
|
||||
-- read() will indicate end of file by returning nil.
|
||||
function start_reading(fs, filename)
|
||||
local result = task.Channel:new()
|
||||
local infile = io.open(filename)
|
||||
if infile == nil then return nil end
|
||||
task.spawn(reading_task, infile, result)
|
||||
return result
|
||||
end
|
||||
|
||||
function reading_task(infile, chanout)
|
||||
for line in infile:lines() do
|
||||
chanout:send(line)
|
||||
end
|
||||
chanout:send(nil) -- eof
|
||||
return {
|
||||
read = coroutine.wrap(function(format)
|
||||
while true do
|
||||
if format == nil then format = '*l' end
|
||||
format = coroutine.yield(infile:read(format))
|
||||
end
|
||||
end),
|
||||
}
|
||||
end
|
||||
|
||||
-- primitive for writing files to a file system (or, later, network)
|
||||
-- returns a channel or nil on error
|
||||
-- write to the channel using :send()
|
||||
-- indicate you're done writing by calling :close()
|
||||
-- file will not be externally visible until :close()
|
||||
-- returns an object or nil on error
|
||||
-- write to the object using .write()
|
||||
-- indicate you're done writing by calling .close()
|
||||
-- file will not be externally visible until .close()
|
||||
function start_writing(fs, filename)
|
||||
if filename == nil then
|
||||
error('start_writing requires two arguments: a file-system (nil for real disk) and a filename')
|
||||
end
|
||||
local result = task.Channel:new()
|
||||
local initial_filename = temporary_filename_in_same_volume(filename)
|
||||
local outfile = io.open(initial_filename, 'w')
|
||||
if outfile == nil then return nil end
|
||||
result.close = function()
|
||||
result:send(nil) -- end of file
|
||||
outfile:close()
|
||||
os.rename(initial_filename, filename)
|
||||
end
|
||||
task.spawn(writing_task, outfile, result)
|
||||
return result
|
||||
return {
|
||||
write = coroutine.wrap(function(x)
|
||||
while true do
|
||||
x = coroutine.yield(outfile:write(x))
|
||||
end
|
||||
end),
|
||||
close = function()
|
||||
outfile:close()
|
||||
os.rename(initial_filename, filename)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function temporary_filename_in_same_volume(filename)
|
||||
|
@ -57,31 +58,3 @@ function temporary_filename_in_same_volume(filename)
|
|||
i = i+1
|
||||
end
|
||||
end
|
||||
|
||||
function writing_task(outfile, chanin)
|
||||
while true do
|
||||
local line = chanin:recv()
|
||||
if line == nil then break end -- end of file
|
||||
outfile:write(line)
|
||||
end
|
||||
end
|
||||
|
||||
-- start_reading reads line by line by default
|
||||
-- this helper permits character-by-character reading
|
||||
function character_by_character(chanin, buffer_size)
|
||||
local chanout = task.Channel:new(buffer_size or 50)
|
||||
task.spawn(character_splitting_task, chanin, chanout)
|
||||
return chanout
|
||||
end
|
||||
|
||||
function character_splitting_task(chanin, chanout)
|
||||
while true do
|
||||
local line = chanin:recv()
|
||||
if line == nil then break end
|
||||
local linesz = line:len()
|
||||
for i=1,linesz do
|
||||
chanout:send(line:sub(i, i))
|
||||
end
|
||||
end
|
||||
chanout:send(nil) -- end of file
|
||||
end
|
||||
|
|
|
@ -67,7 +67,7 @@ local literal_map = {
|
|||
|
||||
local function skip_spaces(infile)
|
||||
while true do
|
||||
local c = infile:recv()
|
||||
local c = infile.read(1)
|
||||
if c == nil then break end
|
||||
if space_chars[c] == nil then return c end
|
||||
end
|
||||
|
@ -79,7 +79,7 @@ local function next_chars(infile, set, firstc)
|
|||
local res = {firstc}
|
||||
local nextc
|
||||
while true do
|
||||
nextc = infile:recv()
|
||||
nextc = infile.read(1)
|
||||
if nextc == nil then break end
|
||||
if set[nextc] then break end
|
||||
table.insert(res, nextc)
|
||||
|
@ -121,27 +121,27 @@ local function parse_string(infile, firstc)
|
|||
local res = {}
|
||||
|
||||
while true do
|
||||
local chr = infile:recv()
|
||||
local chr = infile.read(1)
|
||||
if chr == nil then break end
|
||||
local x = chr:byte()
|
||||
|
||||
if x < 32 then
|
||||
error("control character in string")
|
||||
elseif chr == '\\' then
|
||||
local c = infile:recv()
|
||||
local c = infile.read(1)
|
||||
if c == nil then break end
|
||||
if c == "u" then
|
||||
local hex = ''
|
||||
c = infile:recv()
|
||||
c = infile.read(1)
|
||||
if c == nil then break end
|
||||
hex = hex..c
|
||||
c = infile:recv()
|
||||
c = infile.read(1)
|
||||
if c == nil then break end
|
||||
hex = hex..c
|
||||
c = infile:recv()
|
||||
c = infile.read(1)
|
||||
if c == nil then break end
|
||||
hex = hex..c
|
||||
c = infile:recv()
|
||||
c = infile.read(1)
|
||||
if c == nil then break end
|
||||
hex = hex..c
|
||||
if not hex:match('^%x%x%x%x') then
|
||||
|
@ -155,7 +155,7 @@ local function parse_string(infile, firstc)
|
|||
table.insert(res, escape_char_map_inv[c])
|
||||
end
|
||||
elseif chr == '"' then
|
||||
return table.concat(res), infile:recv()
|
||||
return table.concat(res), infile.read(1)
|
||||
else
|
||||
table.insert(res, chr)
|
||||
end
|
||||
|
@ -170,7 +170,7 @@ local function parse_number(infile, firstc)
|
|||
local res = {firstc}
|
||||
local nextc
|
||||
while true do
|
||||
nextc = infile:recv()
|
||||
nextc = infile.read(1)
|
||||
if nextc == nil then break end
|
||||
if delim_chars[nextc] then break end
|
||||
table.insert(res, nextc)
|
||||
|
@ -295,14 +295,6 @@ end
|
|||
|
||||
|
||||
function jsonf.decode(infile)
|
||||
return decode2(character_by_character(infile))
|
||||
end
|
||||
|
||||
|
||||
function decode2(infile)
|
||||
if not ischannel(infile) then
|
||||
error("expected channel, got " .. type(f))
|
||||
end
|
||||
local firstc = skip_spaces(infile)
|
||||
local res, nextc = parse(infile, firstc)
|
||||
if nextc then
|
||||
|
|
Loading…
Reference in New Issue