new API for file operations

File operations now always return a channel (or nil on error or
permission denied).

When start_reading() from a filename, you can repeatedly :recv() from
the channel it returns.
When :recv() returns nil, you're at the end of the file. Stop.

When you start_writing() to a filename, you can repeatedly :send() to
the channel it returns.
When you're done writing, :close() the channel. Writes to the file won't
be externally visible until you do.

To make this work I'm now always starting up the scheduler, so I need to
fix sieve.tlv.

Transparently running the scheduler is an abstraction, and whenever I
create an abstraction I always worry about how it might fail. There's
a hopefully-clear error when you read past end of a file.
This commit is contained in:
Kartik K. Agaram 2022-03-05 18:04:35 -08:00
parent 2d6b88204b
commit 52ae23784b
4 changed files with 83 additions and 40 deletions

View File

@ -265,18 +265,9 @@
>
>You can also override the default big picture screen entirely by creating a buffer called 'doc:main'.
- __teliva_timestamp:
>Sat Feb 26 21:49:00 2022
>Sat Feb 26 21:50:11 2022
main:
>function main()
> task.spawn(main_task)
> task.scheduler()
> print('out of scheduler')
> Window:getch()
>end
- __teliva_timestamp:
>Sat Feb 26 21:50:11 2022
main_task:
>function main_task()
> local c = task.Channel:new()
> task.spawn(counter, c)
> for i=1,10 do
@ -308,8 +299,8 @@
>end
- __teliva_timestamp:
>Sat Feb 26 21:55:46 2022
main_task:
>function main_task()
main:
>function main()
> local primes = task.Channel:new()
> task.spawn(sieve, primes)
> for i=1,10 do
@ -346,29 +337,17 @@
>end
- __teliva_timestamp:
>Sat Feb 26 22:09:47 2022
main_task:
>function main_task(window)
> local primes = task.Channel:new()
> task.spawn(sieve, primes)
> while true do
> window:addstr(primes:recv())
> window:addstr(' ')
> window:refresh()
> end
>end
- __teliva_timestamp:
>Sat Feb 26 22:08:52 2022
__teliva_note:
>infinite primes
main:
>function main()
> Window:nodelay(true)
> Window:clear()
> task.spawn(main_task, Window)
> task.scheduler()
> print('key pressed; done')
> Window:nodelay(false)
> Window:getch()
> local primes = task.Channel:new()
> task.spawn(sieve, primes)
> while true do
> Window:addstr(primes:recv())
> Window:addstr(' ')
> Window:refresh()
> end
>end
- __teliva_timestamp:
>Sat Feb 26 22:09:47 2022
@ -376,21 +355,26 @@
>clear screen when it fills up; pause on keypress
>
>In Teliva getch() implicitly refreshes the screen.
main_task:
>function main_task(window)
main:
>function main()
> Window:nodelay(true)
> Window:clear()
> local primes = task.Channel:new()
> task.spawn(sieve, primes)
> local h, w = window:getmaxyx()
> local h, w = Window:getmaxyx()
> while true do
> window:addstr(primes:recv())
> window:addstr(' ')
> local c = window:getch()
> Window:addstr(primes:recv())
> Window:addstr(' ')
> local c = Window:getch()
> if c then break end -- key pressed
> local y, x = window:getyx()
> local y, x = Window:getyx()
> if y > h-1 then
> window:clear()
> Window:clear()
> end
> end
> print('key pressed; done')
> Window:nodelay(false)
> Window:getch()
>end
- __teliva_timestamp:
>Sat Feb 26 22:27:25 2022

View File

@ -380,6 +380,17 @@ _M.RECV = RECV
_M.SEND = SEND
_M.NOP = NOP
-- Specific to Teliva
function spawn_main()
task.spawn(main)
task.scheduler()
assert(false, "Teliva's scheduler ran out of work; this shouldn't happen.\n"..
"Either a channel is blocked forever or you're reading past\n"..
"the end of a file (after recv() returned nil).\n")
curses.nodelay(true)
curses.getch()
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Tests

View File

@ -1794,7 +1794,7 @@ int handle_image(lua_State* L, char** argv, int n) {
/* initialize permissions */
load_permissions_from_user_configuration(L);
/* call main() */
lua_getglobal(L, "main");
lua_getglobal(L, "spawn_main");
status = docall(L, 0, 1);
if (status != 0) return report_in_developer_mode(L, status);
return 0;

View File

@ -385,3 +385,51 @@
> '123 ',
> 'test_check_screen')
>end
- __teliva_timestamp: original
start_reading:
>-- 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.
>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
>end
- __teliva_timestamp: original
start_writing:
>-- 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()
>function start_writing(fs, filename)
> local result = task.Channel:new()
> local initial_filename = os.tmpname()
> 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
>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