Skip to content

Commit

Permalink
Add inclusive argument to Read.read_until/read_line
Browse files Browse the repository at this point in the history
This adds an `inclusive` argument to the methods Read.read_until and
Read.read_line, giving the developer control over whether the terminal
byte/newline should be included in the target ByteArray, removing the
need for manually removing it when necessary.

Changelog: added
  • Loading branch information
yorickpeterse committed Jul 25, 2024
1 parent c6a18e2 commit d0a29da
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 32 deletions.
77 changes: 62 additions & 15 deletions std/src/std/io.inko
Original file line number Diff line number Diff line change
Expand Up @@ -481,31 +481,58 @@ trait pub BufferedRead: Read {
# Read bytes into `into` up to and including the byte specified in the `byte`
# argument.
#
# The `inclusive` argument specifies if the `byte` value should also be read
# into the `ByteArray`, or if it should be discarded. When set to `false`, the
# returned number of bytes _doesn't_ account for the terminal byte.
#
# Upon success, the return value is `Ok(n)` where `n` is the number of bytes
# read into `into`.
#
# # Examples
#
# Reading until and including a given byte:
#
# ```inko
# import std.fs.file (ReadOnlyFile)
# import std.io (BufferedReader)
# import std.io (Buffer, BufferedReader)
#
# let file = ReadOnlyFile.new('README.md').get
# let reader = BufferedReader.new(file)
# let reader = BufferedReader.new(Buffer.new('hello\nworld'))
# let bytes = ByteArray.new
#
# reader.read(into: bytes, size: 32)
# reader.read_until(byte: 0xA, into: bytes, inclusive: true) # => Result.Ok(6)
# bytes.to_string # => 'hello\n'
# ```
fn pub mut read_until(byte: Int, into: mut ByteArray) -> Result[Int, Error] {
#
# Excluding the byte from the buffer:
#
# ```inko
# import std.io (Buffer, BufferedReader)
#
# let reader = BufferedReader.new(Buffer.new('hello\nworld'))
# let bytes = ByteArray.new
#
# reader.read_until(byte: 0xA, into: bytes, inclusive: false) # => Result.Ok(5)
# bytes.to_string # => 'hello'
# ```
fn pub mut read_until(
byte: Int,
into: mut ByteArray,
inclusive: Bool,
) -> Result[Int, Error] {
let mut total = 0

loop {
match try read_byte {
case Some(val) if byte == val -> {
if inclusive {
total += 1
into.push(val)
}

break
}
case Some(val) -> {
total += 1
into.push(val)

if byte == val { break }
}
case _ -> break
}
Expand All @@ -517,23 +544,43 @@ trait pub BufferedRead: Read {
# Read bytes into `into` up to and including the newline byte (0xA aka
# `"\n"`).
#
# The `inclusive` argument specifies if the newline should also be read into
# the `ByteArray`, or if it should be discarded. When set to `false`, the
# returned number of bytes _doesn't_ account for the newline.
#
# Upon success, the return value is `Ok(n)` where `n` is the number of bytes
# read into `into`.
#
# # Examples
#
# Reading until and including the end of a line:
#
# ```inko
# import std.fs.file (ReadOnlyFile)
# import std.io (BufferedReader)
# import std.io (Buffer, BufferedReader)
#
# let file = ReadOnlyFile.new('README.md').get
# let reader = BufferedReader.new(file)
# let reader = BufferedReader.new(Buffer.new('hello\nworld'))
# let bytes = ByteArray.new
#
# reader.read_line(into: bytes, inclusive: true) # => Result.Ok(6)
# bytes.to_string # => 'hello\n'
# ```
#
# Excluding the newline from the buffer:
#
# ```inko
# import std.io (Buffer, BufferedReader)
#
# let reader = BufferedReader.new(Buffer.new('hello\nworld'))
# let bytes = ByteArray.new
#
# reader.read_line(into: bytes)
# reader.read_line(into: bytes, inclusive: false) # => Result.Ok(5)
# bytes.to_string # => 'hello'
# ```
fn pub mut read_line(into: mut ByteArray) -> Result[Int, Error] {
read_until(byte: 0xA, into: into)
fn pub mut read_line(
into: mut ByteArray,
inclusive: Bool,
) -> Result[Int, Error] {
read_until(byte: 0xA, into: into, inclusive: inclusive)
}

# Returns an iterator that yields the bytes in `self`.
Expand Down
2 changes: 1 addition & 1 deletion std/test/compiler/test_diagnostics.inko
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn parse_test(file: ReadOnlyFile) -> Result[Array[Diagnostic], String] {
let diags = []

loop {
match reader.read_line(buffer) {
match reader.read_line(buffer, inclusive: true) {
case Ok(0) -> break
case Ok(_) -> {}
case Error(e) -> throw 'failed to read a line from ${file.path}: ${e}'
Expand Down
86 changes: 70 additions & 16 deletions std/test/std/test_io.inko
Original file line number Diff line number Diff line change
Expand Up @@ -197,32 +197,86 @@ fn pub tests(t: mut Tests) {
t.equal(writer.buffer, 'foo\n'.to_byte_array)
})

t.test('BufferedRead.read_until', fn (t) {
let reader = BufferedReader.new(Reader.from_array([1, 0xA, 2, 0xA, 3]))
t.test('BufferedRead.read_until with inclusive: true', fn (t) {
let reader = BufferedReader.new(
Reader.from_array([0x1, 0xA, 0x2, 0xA, 0x3]),
)
let bytes = ByteArray.new

t.equal(reader.read_until(byte: 0xA, into: bytes), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([1, 0xA]))
t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: true),
Result.Ok(2),
)
t.equal(bytes, ByteArray.from_array([0x1, 0xA]))

t.equal(reader.read_until(byte: 0xA, into: bytes), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([1, 0xA, 2, 0xA]))
t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: true),
Result.Ok(2),
)
t.equal(bytes, ByteArray.from_array([0x1, 0xA, 0x2, 0xA]))

t.equal(reader.read_until(byte: 0xA, into: bytes), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([1, 0xA, 2, 0xA, 3]))
t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: true),
Result.Ok(1),
)
t.equal(bytes, ByteArray.from_array([0x1, 0xA, 0x2, 0xA, 0x3]))
})

t.test('BufferedRead.read_line', fn (t) {
let reader = BufferedReader.new(Reader.from_array([1, 0xA, 2, 0xA, 3]))
t.test('BufferedRead.read_until with inclusive: false', fn (t) {
let reader = BufferedReader.new(
Reader.from_array([0x1, 0xA, 0x2, 0xA, 0x3]),
)
let bytes = ByteArray.new

t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: false),
Result.Ok(1),
)
t.equal(bytes, ByteArray.from_array([0x1]))

t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: false),
Result.Ok(1),
)
t.equal(bytes, ByteArray.from_array([0x1, 0x2]))

t.equal(
reader.read_until(byte: 0xA, into: bytes, inclusive: false),
Result.Ok(1),
)
t.equal(bytes, ByteArray.from_array([0x1, 0x2, 0x3]))
})

t.test('BufferedRead.read_line with inclusive: true', fn (t) {
let reader = BufferedReader.new(
Reader.from_array([0x1, 0xA, 0x2, 0xA, 0x3]),
)
let bytes = ByteArray.new

t.equal(reader.read_line(bytes, inclusive: true), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([0x1, 0xA]))

t.equal(reader.read_line(bytes, inclusive: true), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([0x1, 0xA, 0x2, 0xA]))

t.equal(reader.read_line(bytes, inclusive: true), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([0x1, 0xA, 0x2, 0xA, 0x3]))
})

t.test('BufferedRead.read_line with inclusive: false', fn (t) {
let reader = BufferedReader.new(
Reader.from_array([0x1, 0xA, 0x2, 0xA, 0x3]),
)
let bytes = ByteArray.new

t.equal(reader.read_line(bytes), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([1, 0xA]))
t.equal(reader.read_line(bytes, inclusive: false), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([0x1]))

t.equal(reader.read_line(bytes), Result.Ok(2))
t.equal(bytes, ByteArray.from_array([1, 0xA, 2, 0xA]))
t.equal(reader.read_line(bytes, inclusive: false), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([0x1, 0x2]))

t.equal(reader.read_line(bytes), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([1, 0xA, 2, 0xA, 3]))
t.equal(reader.read_line(bytes, inclusive: false), Result.Ok(1))
t.equal(bytes, ByteArray.from_array([0x1, 0x2, 0x3]))
})

t.test('BufferedRead.read_line', fn (t) {
Expand Down

0 comments on commit d0a29da

Please sign in to comment.