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:
Kartik K. Agaram 2022-03-16 17:03:38 -07:00
parent ab89be1ed3
commit b9c187d259
5 changed files with 57 additions and 99 deletions

View File

@ -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>

View File

@ -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

View File

@ -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)
return {
read = coroutine.wrap(function(format)
while true do
if format == nil then format = '*l' end
format = coroutine.yield(infile:read(format))
end
chanout:send(nil) -- eof
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
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
task.spawn(writing_task, outfile, result)
return result
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

View File

@ -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

View File

@ -3732,8 +3732,8 @@
>switch to new file API for writing
write_zettels:
>function write_zettels(outfile)
> outfile:send(json.encode(zettels))
> outfile:close()
> outfile.write(json.encode(zettels))
> outfile.close()
>end
- __teliva_timestamp:
>Thu Mar 10 04:21:28 2022