local async = require("diffview.async")
local lazy = require("diffview.lazy")
local oop = require("diffview.oop")
local control = lazy.require("diffview.control")
local utils = lazy.require("diffview.utils")
local await = async.await
local M = {}
local Stream = oop.create_class("Stream")
M.Stream = Stream
Stream.EOF = oop.Symbol("Stream.EOF");
function Stream:init(src)
self.src = self:create_src(src)
self.head = 1
self.drained = false
end
function Stream:create_src(src)
if type(src) == "table" then
if utils.islist(src) then
local itr = ipairs(src)
return function()
local _, v = itr(src, self.head - 1)
return v
end
else
error("Unimplemented!")
end
else
return src
end
end
function Stream:next()
if self.drained then
error("Attempted to consume a drained stream!")
end
local idx = self.head
local v, cont = self.src()
if v == Stream.EOF or (v == nil and not cont) then
self.drained = true
return Stream.EOF, nil
end
self.head = self.head + 1
return v, idx
end
function Stream:skip(n)
if not n then
self:next()
return
end
for _ = 1, n do self:next() end
return self
end
function Stream:iter()
return function()
local v, i = self:next()
if v == Stream.EOF then return nil end
return i, v
end
end
function Stream:collect()
local ret = {}
for i, v in self:iter() do ret[i] = v end
return ret
end
function Stream:slice(first, last)
if first == nil then first = 1 end
if last == nil then last = math.huge end
return Stream(function()
if self.head > last then return nil, false end
if first > self.head then
self:skip(first - self.head)
end
return (self:next())
end)
end
function Stream:map(f)
return Stream(function()
local v = self:next()
while v ~= Stream.EOF do
v = f(v)
if v ~= nil then break end
v = self:next()
end
if v == Stream.EOF then
return nil, false
end
return v
end)
end
function Stream:filter(f)
return self:map(function(item)
if not f(item) then
return nil
end
return item
end)
end
function Stream:reduce(f, init)
local acc = init
if not acc then acc = self:next() end
for _, v in self:iter() do acc = f(acc, v) end
return acc
end
local AsyncStream = oop.create_class("AsyncStream", Stream)
M.AsyncStream = AsyncStream
AsyncStream.next = async.sync_wrap(
function(self, callback)
if self.drained then
error("Attempted to consume a drained stream!")
end
local idx = self.head
local v, cont = await(self.src())
if v == Stream.EOF or (v == nil and not cont) then
self.drained = true
callback(Stream.EOF, nil)
return
end
self.head = self.head + 1
callback(v, idx)
end
)
AsyncStream.await = async.sync_wrap(
function(self, callback)
callback(self:collect())
end
)
local StreamState = oop.enum({
OPEN = 1,
CLOSING = 2,
CLOSED = 3,
})
local AsyncListStream = oop.create_class("AsyncListStream", AsyncStream)
M.AsyncListStream = AsyncListStream
function AsyncListStream:init(opt)
opt = opt or {}
self.data = {}
self.state = {
on_close = { listeners = { opt.on_close } },
on_post_close = { listeners = { opt.on_post_close } },
}
self.flow_state = StreamState.OPEN
self.sem = control.Semaphore(1)
local src = async.wrap(function(callback)
if self.data[self.head] == nil then
self.resume = callback
return
end
callback(self.data[self.head])
end)
self:super(src)
end
function AsyncListStream:push(...)
if self:is_closed() then return end
local args = { ... }
local permit = await(self.sem:acquire())
for i = 1, select("#", ...) do
if args[i] ~= nil then
if args[i] == Stream.EOF then
if self.flow_state ~= StreamState.CLOSING then
self.flow_state = StreamState.CLOSING
permit:forget()
self:invoke_listeners("on_close")
permit = await(self.sem:acquire())
self.data[#self.data+1] = args[i]
self.flow_state = StreamState.CLOSED
self:invoke_listeners("on_post_close")
break
end
else
self.data[#self.data+1] = args[i]
end
end
end
permit:forget()
if self.resume then
local resume = self.resume
self.resume = nil
resume(self.data[self.head])
end
end
function AsyncListStream:close(...)
if self:is_closed() then return end
self.state.on_close.args = utils.tbl_pack(...)
self:push(Stream.EOF)
end
function AsyncListStream:is_closed()
return self.flow_state == StreamState.CLOSED
end
function AsyncListStream:on_close(callback)
table.insert(self.state.on_close.listeners, callback)
end
function AsyncListStream:on_post_close(callback)
table.insert(self.state.on_post_close.listeners, callback)
end
function AsyncListStream:invoke_listeners(kind)
local event_state = self.state[kind]
for _, listener in ipairs(event_state.listeners) do
if event_state.args then
listener(utils.tbl_unpack(event_state.args))
else
listener()
end
end
event_state.args = nil
end
return M