From b9c187d2594953267357ef37e352d425c7fbeafe Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Wed, 16 Mar 2022 17:03:38 -0700 Subject: [PATCH] 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. --- doc/manual.html | 43 +++++++++++---------------- life.tlv | 2 +- src/file.lua | 79 ++++++++++++++++--------------------------------- src/jsonf.lua | 28 +++++++----------- zet.tlv | 4 +-- 5 files changed, 57 insertions(+), 99 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 5721704..f5520ce 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -3600,35 +3600,26 @@ idiomatic Teliva uses some slightly different primitives.

start_reading (fs, filename)

-This function opens a file exclusively for reading, and returns a -channel (NOT a file as in Lua) or nil on error. -recv 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 nil on error. If the returned object is +stored in variable f, f.read(format) will read from +the file. Legal values for format are identical to +file:read(format).

(The fs parameter is currently unused. It will be used to pass in fake file systems for tests.) -

-


character_by_character(chanin, chanout)

- -

-This function converts a channel that can recv -a line at a time from a file into a channel that can recv -a character at a time. Don't try to mix by-line reads with by-character reads. -Once a channel is passed into character_by_character, calling -code should stop trying to use it. -


start_writing(fs, filename)

-This function opens a file exclusively for writing, and returns a -channel (NOT a file as in Lua) or nil on error. -send to the channel to write to -the file. close on the channel will persist the changes and make -them externally visible. All writes are hidden until close. +This function opens a file exclusively for writing, and returns an object (NOT +a file handle as in Lua) or nil on error. If the result is stored in +variable f, f.write(x) will write x to +the file. f.close() will persist the changes and make them +externally visible. All changes will be hidden until f.close().

(The fs parameter is currently unused. It will be used to pass in @@ -4408,17 +4399,19 @@ Returns a value representing the JSON string str.

-


jsonf.decode (chan)

+

jsonf.decode (f)

-Returns a value representing the JSON string read from channel -chan. +Reads a value encoded in JSON from a file and returns it. +f is the type of file object returned by +start_reading (i.e. supporting +f.read(format)). +For example, suppose file foo contains '[1,2,3,{"x":10}]'. Then:

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

diff --git a/life.tlv b/life.tlv index 27aad4f..6c62f31 100644 --- a/life.tlv +++ b/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 diff --git a/src/file.lua b/src/file.lua index 81fe68d..2ac840f 100644 --- a/src/file.lua +++ b/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 diff --git a/src/jsonf.lua b/src/jsonf.lua index 25e0c6e..aec0e0a 100644 --- a/src/jsonf.lua +++ b/src/jsonf.lua @@ -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 diff --git a/zet.tlv b/zet.tlv index 0a638c4..ab0ad79 100644 --- a/zet.tlv +++ b/zet.tlv @@ -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