///|
let block_size : Int32 = 65536
///|
// Some regular pseudo files report zero or a page-sized nominal length. Probe
// before output begins; an unknown extent must be consumed as a stream.
async fn has_extent(file : @fs.File, length : Int64) -> Bool {
let probe = FixedArray::make(1, b'\x00')
if length == 0 {
file.read_at(probe, position=0L) == 0
} else {
file.read_at(probe, position=length - 1L) == 1
}
}
///|
async fn write_range(
file : @fs.File,
start : Int64,
end : Int64,
buffer : FixedArray[Byte],
writer : &@io.Writer,
) -> Unit {
let mut position = start
while position < end {
let length = (end - position).min(buffer.length().to_int64()).to_int()
let count = file.read_at(buffer, position~, len=length)
if count != length {
raise @io.ReaderClosed
}
writer.write(buffer.unsafe_reinterpret_as_bytes()[:count])
position += count.to_int64()
}
}
///|
/// KMP in the direction of traversal avoids rescanning long shared separator
/// prefixes and carries matches across backward block boundaries.
fn reverse_failure(separator : Bytes) -> FixedArray[Int32] {
let failure = FixedArray::make(separator.length(), 0)
let mut matched = 0
for index in 1.. 0 && byte != separator[separator.length() - matched - 1] {
matched = failure[matched - 1]
}
if byte == separator[separator.length() - matched - 1] {
matched += 1
}
failure[index] = matched
}
failure
}
///|
async fn reverse_file(
file : @fs.File,
separator : Bytes,
before : Bool,
writer~ : &@io.Writer = @stdio.stdout,
size~ : Int32 = block_size,
) -> Unit {
let length = file.size()
let writer = @io.BufferedWriter::new(writer, size=block_size)
let scan = FixedArray::make(size, b'\x00')
let output = FixedArray::make(size, b'\x00')
let failure = reverse_failure(separator)
let mut past_end = length
let mut block_end = length
let mut matched = 0
let mut first = true
while block_end > 0 {
let block_start = (block_end - size.to_int64()).max(0L)
let count = (block_end - block_start).to_int()
if file.read_at(scan, position=block_start, len=count) != count {
raise @io.ReaderClosed
}
for index = count - 1; index >= 0; index = index - 1 {
let byte = scan[index]
while matched > 0 && byte != separator[separator.length() - matched - 1] {
matched = failure[matched - 1]
}
if byte == separator[separator.length() - matched - 1] {
matched += 1
}
if matched == separator.length() {
let start = block_start + index.to_int64()
let boundary = if before { start } else { start + matched.to_int64() }
if before || !first || boundary != past_end {
if boundary >= block_start && past_end <= block_end {
writer.write(
scan.unsafe_reinterpret_as_bytes()[(boundary - block_start).to_int():(past_end -
block_start).to_int()],
)
} else {
write_range(file, boundary, past_end, output, writer)
}
}
past_end = boundary
matched = 0
first = false
}
}
block_end = block_start
}
write_range(file, 0L, past_end, output, writer)
writer.flush()
}
///|
async fn reverse_regex(
file : @fs.File,
pattern : @bytere.Pattern,
before : Bool,
writer : &@io.Writer,
) -> Unit {
// The matcher takes a contiguous byte-view String. Keep its O(input) cost
// explicit; ranges are emitted from the immutable spool without an output copy.
let text = @bytere.text(file.read_all().binary())
let writer = @io.BufferedWriter::new(writer, size=block_size)
let output = FixedArray::make(block_size, b'\x00')
let mut search_end = text.length()
let mut past_end = search_end
let mut first = true
while search_end > 0 {
guard pattern.rfind_span(text, search_end) is Some((start, end)) else {
break
}
let boundary = if before { start } else { end }
if before || !first || boundary != past_end {
write_range(
file,
boundary.to_int64(),
past_end.to_int64(),
output,
writer,
)
}
past_end = boundary
search_end = start
first = false
}
write_range(file, 0L, past_end.to_int64(), output, writer)
writer.flush()
}
///|
async fn reverse_spooled(
reader : &@io.Reader,
separator : Bytes,
before : Bool,
regex~ : @bytere.Pattern? = None,
writer~ : &@io.Writer = @stdio.stdout,
) -> Unit {
let root = @fs.tmpdir(prefix="cli-tac-")
defer (@fs.rmdir(root, recursive=true) catch { _ => () })
let file = @fs.open(
root + "/input",
mode=ReadWrite,
create_mode=CreateNew,
permission=0o600,
)
defer file.close()
let buffer = FixedArray::make(block_size, b'\x00')
while reader.read(buffer) is count && count > 0 {
file.write(buffer.unsafe_reinterpret_as_bytes()[:count])
}
match regex {
None => reverse_file(file, separator, before, writer~)
Some(pattern) => reverse_regex(file, pattern, before, writer)
}
}