# `nupp.io`
`nupp.io` is bytes: storage for them, and the readers and writers that move
them. Reach for it when a program needs a growable buffer, an immutable
snapshot of one, or a parser that works the same over a buffer, a file and an
HTTP response body.
```nupp:playground
local bytes = nupp.io.newBuffer("hello")
local reader = bytes:newReader()
assert(reader:read(5) == "hello")
```
A buffer holds its bytes in the target's physical storage: a native array on
LuaJIT, or a rooted allocation in a Wasm application. `Reader` and `Writer` are interfaces rather than the
concrete things that satisfy them, which is what lets code written against the
contract work over any of them without knowing which it has.
The rest of what `io` means is a module of its own, because each has a luacase
name and so can have one. See `nupp.io.path` for filesystem names,
`nupp.io.uri` for resource identifiers, `nupp.io.files` for the filesystem
itself, `nupp.io.process` for running a child process, `nupp.io.net` for
network connections, `nupp.io.tls` for encrypting one, and `nupp.io.http`
for the asynchronous HTTP client.
Keeping them apart is what lets a program that wants a byte buffer carry a byte
buffer rather than every provider behind the name.
The last four move their bytes through the `Reader` and `Writer` contracts on
this page, so a parser written against byte I/O reads a file, a child's output,
a connection, or a response body without knowing which it has.
Every value here is an owner. `close` is safe to call repeatedly and answers
whether the thing it was writing into was still open, so a caller that cares can
tell a clean finish from a destination that went away first. `isReleased`
reports that state without changing it.
## Buffers
`newBuffer()` creates an empty growable buffer. A string supplies initial
bytes; an integer reserves capacity without changing the length.
```nupp
local bytes = nupp.io.newBuffer("hello")
bytes:setString("!", bytes:length())
assert(bytes:getString() == "hello!")
```
Buffer offsets are zero-based, as they are wherever a count is an offset into
storage rather than a position in a Lua string. See [Byte
positions](../../../learn/runtime/data/standard-library/index.html#byte-positions) for the rule and
where the other convention applies.
`getString(offset, count)` copies a range. `setString` overwrites from an
offset and grows the buffer as needed, filling any gap with zero bytes. `clear`
sets the length to zero without discarding capacity, and `resize` truncates or
zero-fills:
```nupp
local bytes = nupp.io.newBuffer("hello")
bytes:ensureCapacity(4096)
assert(bytes:capacity() >= 4096)
bytes:resize(3)
assert(bytes:getString() == "hel")
```
Capacity is the allocation, not a recorded number. Growing at least doubles it,
so appending through a writer costs amortized constant time per byte and
`capacity()` reports bytes that are actually held. `ensureCapacity` reserves at
least the minimum asked for.
`close()` releases the buffer's storage, and an operation on a released buffer
raises rather than answering a reason.
`readSpan()` borrows the buffer's initialized bytes without separating its
pointer from its length. Native producers write through `reserveWrite(offset,
count)`: the returned lease supplies a checked writable span, and
`commit(written)` changes the logical length only after the producer succeeds.
Dropping the lease aborts it and leaves the length unchanged.
## Byte views
`view(offset, count)` returns an immutable snapshot, not a mutable alias into
the buffer. It remains valid if the source buffer changes or closes:
```nupp
local buffer = nupp.io.newBuffer("header-body")
local header = buffer:view(0, 6)
buffer:clear()
assert(header:getString() == "header")
header:close()
```
A view can make a smaller view, open its own snapshot reader, report its byte
length, copy to a string, borrow a checked span with `readSpan()`, and be
closed. Its offsets are zero-based like a buffer's. The data and UTF-8 modules
accept views, which is what keeps those APIs from being coupled to a mutable
buffer.
## Readers
A `nupp.io.Reader` is a forward-only byte source. `newStringReader(text)` reads
a string; `buffer:newReader()` reads a snapshot of the buffer's current
contents. A reader owns its close obligation and closes automatically at the
end of its scope unless `close()` consumes it earlier.
```nupp
local reader = nupp.io.newStringReader("abcdef")
assert(reader:read(2) == "ab")
local destination = nupp.io.newBuffer()
assert(reader:readInto(destination, 0, 3) == 3)
assert(destination:getString() == "cde")
assert(reader:read(8) == "f")
assert(reader:read(8) == "") -- EOF
```
`read(count)` requires a positive count and returns at most that many bytes. It
returns an empty string at EOF and nil with a reason after close.
`readSpan(writable)` fills a positive-sized checked writable span.
`readInto(buffer, offset, count)` returns zero at EOF and reads 64 KiB when
given no count; an explicit count must be positive. A failed or EOF read does
not extend the destination. `transferTo(writer)` copies the entire remaining
source and returns the byte count.
## Writers
`buffer:newWriter()` clears the buffer and returns a forward-only writer
targeting it. A writer likewise closes automatically; `flush()` publishes
pending work without consuming that ownership.
```nupp
local destination = nupp.io.newBuffer()
local writer = destination:newWriter()
assert(writer:write("prefix:"))
local payload = nupp.io.newBuffer("body")
assert(writer:writeSpan(payload:readSpan()) == 4)
assert(writer:flush())
assert(destination:getString() == "prefix:body")
```
`write` answers a boolean and `writeSpan` answers the byte count. Both answer a
reason when the writer is closed or the destination was released. The span is
the common zero-copy input contract for buffers, views, files, processes, and
HTTP transfers; there is no unchecked raw-pointer writer alongside it. `flush`
does nothing for memory and is part of the contract so that a later file or
socket writer implements the same interface.
## Typed scalars
A reader and a writer move bytes. `newScalarReader` and `newScalarWriter` add
the missing piece, a sized integer or float landing on those bytes without an
`ffi.cast` at every call site.
```nupp
local writer = nupp.io.newScalarWriter()
writer:writeUint32(1447383632 as uint32):writeFloat64(1.5)
local stored = writer:buffer()
assert(stored ~= nil)
local reader = nupp.io.newScalarReader(stored as nupp.io.Buffer)
assert(reader:readUint32() == (1447383632 as uint32))
assert(reader:readFloat64() == 1.5)
assert(reader:atEnd())
```
Every write answers the writer, so calls chain. A short read raises rather than
answering a reason, which is the one place this pair departs from the reader
and writer contracts above: a scalar either landed whole or the source was not
what the format said, and there is no partial value to hand back.
::: deepdive Host byte order
Both directions read and write host-endian, the only order LuaJIT ships, so
that is the default. A format fixed to one byte order casts and swaps
explicitly, and there are no LE and BE variants here until something needs
them. Adding a second set without a caller would double the surface and leave
half of it untested.
:::
### Scalar sources
`newScalarReader` takes whatever holds the bytes. A string, a `ByteView` or a
`Buffer` is read from a copy taken there and then, so `remaining()` knows the
count:
```nupp
local reader = nupp.io.newScalarReader("\1\2\3\4")
assert(reader:remaining() == 4)
```
A `Reader` is consumed as it goes, which is what lets a file or an HTTP body be
read a field at a time. It cannot say how much is left, so `remaining()`
answers nil and `atEnd()` is the question to ask instead. The scalar adapter
takes that reader: closing or dropping the adapter closes or drops the reader
exactly once.
### Scalar destinations
`newScalarWriter` appends to a `Buffer`, keeping what that buffer already
holds, or writes through a `Writer`. Given nothing it starts a buffer of its
own, which `buffer()` hands back:
```nupp
local destination = nupp.io.newBuffer("header:")
nupp.io.newScalarWriter(destination):writeUint8(33 as uint32)
assert(destination:getString() == "header:!")
```
A writer pointed at somebody else's `Writer` answers nil from `buffer()`, since
the bytes are already gone. That writer is taken, so closing or dropping the
scalar adapter closes or drops it exactly once. A buffer supplied by the caller
is only borrowed. The buffer created by the no-argument form belongs to the
scalar writer; `buffer()` borrows it for as long as the scalar writer remains
live.
## Byte queues
A `nupp.text.buffer` is a byte queue: `put` appends to the back and `get` consumes
from the front. `newScalarReader` accepts one directly, so bytes assembled
there are read without copying everything in it first, and the queue's own
`get` stays usable over the same bytes.
```nupp
local buffer = nupp.text.buffer
local queue = buffer.new()
queue:put("header:")
queue:put("!")
local reader = nupp.io.newScalarReader(queue)
assert(reader:readBytes(7) == "header:")
assert(queue:tostring() == "!")
assert(reader:readUint8() == (33 as uint8))
```
Reading drains the queue, and `remaining()` reports what it still holds.
`newQueueReader` is the same bridge one layer down: it answers an ordinary
`Reader` over the queue, for `transferTo`, `readInto`, and everything else that
contract already covers.
```nupp
local buffer = nupp.text.buffer
local destination = nupp.io.newBuffer()
local queue = buffer.new()
queue:put("payload")
assert(nupp.io.newQueueReader(queue):transferTo(destination:newWriter()) == 7)
assert(destination:getString() == "payload")
```
Neither takes the queue over. Nothing here frees or closes it, and one read to
empty leaves an empty queue rather than a released one.
::: seealso
- `nupp.io.files` for the same contracts over a file on disk
- `nupp.io.path` for filesystem names rather than file contents
- [ownership.md](../../../learn/runtime/ownership/borrowing/index.html) for the complete contract every
value here is written against
- [Standard library](../../../learn/runtime/data/standard-library/index.html) for the errors,
ownership and byte-position conventions every facility shares
:::
## Submodules
| Module | Description |
| --- | --- |
| `nupp.io.files` | nupp.io.files reads what the filesystem knows about a name: whether it resolves, what it refers to, what a directory... |
| `nupp.io.http` | nupp.io.http sends HTTP requests through the selected host implementation without blocking the caller's frame. |
| `nupp.io.net` | nupp.io.net is the network as bytes: a listener, the connections it accepts, and connections this process opens itself. |
| `nupp.io.path` | nupp.io.path answers an immutable path value whose joining, normalizing and splitting follow the platform's own rules. |
| `nupp.io.process` | Starts a child process and drains its streams without deadlocking. |
| `nupp.io.storage` | Persistent key/value storage owned by the current host. |
| `nupp.io.tls` | nupp.io.tls takes ownership of a connection nupp.io.net already opened and turns it into an encrypted connection. |
| `nupp.io.uri` | nupp.io.uri parses one absolute URI into an immutable value and answers its components from the parse rather than... |
## Constructors
### `newBuffer` _constructor_
```nupp
function newBuffer(initial: integer | string?): affine(Buffer, destroyOwner)
```
Creates a buffer, optionally over starting bytes or a starting capacity.
The buffer is an owner, so it closes at its lexical boundary unless it is
transferred out of one. A number is a capacity to allocate rather than
content, and the buffer is still empty afterwards.
```nupp
const io = nupp.io
do
local buffer = io.newBuffer("hello")
assert(buffer:length() == 5)
local sized = io.newBuffer(4096)
assert(sized:length() == 0 and sized:capacity() == 4096)
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `initial` | `integer | string?` | the starting bytes, a starting capacity, or nothing |
#### Returns
| Type | Description |
| --- | --- |
| `affine(Buffer, destroyOwner)` | the buffer, owned by the caller |
#### Raises
- when initial is neither bytes nor a capacity
### `newLines` _constructor_
```nupp
function newLines(takes source: Reader, limit: integer?): affine(Lines)
```
Takes a reader apart into lines.
The source is taken rather than borrowed, and closing the result closes it:
a line reader holds the bytes it read past a terminator, so a second reader
over the same source would be reading a stream with holes in it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes source` | `Reader` | the reader to take |
| `limit` | `integer?` | the most bytes one line may hold, or 65536 |
#### Returns
| Type | Description |
| --- | --- |
| `affine(Lines)` | the line reader, owned by the caller |
#### Raises
- when limit is not a positive integer
### `newQueueReader` _constructor_
```nupp
function newQueueReader(source: ByteQueue): Reader
```
Opens a reader over a byte queue, which it does not own.
Closing this reader leaves the queue behind it open, because the queue was
never transferred. A process stream and an HTTP response body are both byte
queues without naming this module.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `source` | `ByteQueue` | the queue to read from |
#### Returns
| Type | Description |
| --- | --- |
| `Reader` | the reader, owned by the caller |
#### Raises
- when source is not a byte queue
### `newScalarReader` _constructor_
```nupp
const newScalarReader: function(source: string | ByteQueue): affine(ScalarReader, destroyOwner)
& function(borrows source: ByteView | Buffer): affine(ScalarReader, destroyOwner)
& function(takes source: Reader): affine(ScalarReader, destroyOwner)
```
Opens a cursor for sized integer and float reads.
A string, snapshot or buffer is read from a copy of its bytes taken here, and
reports its `remaining` count. A reader or a queue is consumed in place: the reader
answers no count, the queue answers what it still holds.
What the source is decides what happens to it. A snapshot or a buffer is
borrowed and outlives the cursor; a reader is taken, and closing the cursor
closes it; a string or a queue carries no obligation either way.
```nupp
do
local cursor = io.newScalarReader(header)
local magic = cursor:readUint32()
local count = cursor:readUint16()
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `source` | `string | ByteQueue` | the bytes, snapshot, buffer, reader or queue to read |
#### Returns
| Type | Description |
| --- | --- |
| `affine(ScalarReader, destroyOwner)` | the new reader, owned by the caller |
#### Raises
- when source is none of those
### `newScalarWriter` _constructor_
```nupp
const newScalarWriter: function(): affine(ScalarWriter, destroyOwner)
& function(borrows destination: Buffer): affine(ScalarWriter, destroyOwner) borrows (destination)
& function(takes destination: Writer): affine(ScalarWriter, destroyOwner)
```
Opens a cursor for sized integer and float writes.
A buffer is appended to, keeping what it already holds. A writer receives the bytes
as they are written. Omitting the destination starts a fresh buffer, which
`ScalarWriter:buffer` hands back.
```nupp
do
local cursor = io.newScalarWriter()
cursor:writeUint32(0x4d495355):writeUint16(2)
local bytes = cursor:buffer()
assert(bytes ~= nil and bytes:getString() ~= "")
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows destination` | `Buffer` | the buffer or writer to append to, or nothing for a fresh buffer |
#### Returns
| Type | Description |
| --- | --- |
| `affine(ScalarWriter, destroyOwner) borrows (destination)` | the new writer, owned by the caller |
#### Raises
- when destination is neither a buffer nor a writer
### `newStringReader` _constructor_
```nupp
function newStringReader(text: string): Reader
```
Opens a reader over bytes held as a string.
The reader is a `Reader` like any other, so a parser written against the
contract reads a string here and a file or a response body elsewhere without
changing:
```nupp
local reader = io.newStringReader("hello")
assert(reader:read(5) == "hello")
assert(reader:read(1) == "")
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `text` | `string` | the bytes to read |
#### Returns
| Type | Description |
| --- | --- |
| `Reader` | the reader, owned by the caller |
#### Raises
- when text is not a string
## Types
### `Buffer` _interface_
```nupp
sealed interface Buffer
drop: nosuspend function(takes self: Buffer): nil
length: function(borrows self: Buffer): integer
capacity: function(borrows self: Buffer): integer
clear: function(exclusive self: Buffer): nil
ensureCapacity: function(exclusive self: Buffer, minimum: integer): nil
resize: function(exclusive self: Buffer, length: integer): nil
getString: function(borrows self: Buffer, offset: integer?, count: integer?): string
setString: function(exclusive self: Buffer, bytes: string, offset: integer?): nil
readSpan: function(borrows self: Buffer): span.ByteSpan borrows (self)
reserveWrite: function(
exclusive self: Buffer,
offset: integer,
count: integer
): affine(BufferWriteLease, destroyOwner) borrows (self)
view: function(self: Buffer, offset: integer?, count: integer?): affine(ByteView, destroyOwner)
newReader: function(self: Buffer): Reader
newWriter: function(exclusive self: Buffer): affine(Writer) borrows (self)
isReleased: function(borrows self: Buffer): boolean
close: function(takes self: Buffer): (boolean, string?)
end
```
Growable byte storage whose pointer and logical length never escape separately.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: Buffer): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `length`
```nupp
length: function(borrows self: Buffer): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `capacity`
```nupp
capacity: function(borrows self: Buffer): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `clear`
```nupp
clear: function(exclusive self: Buffer): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `ensureCapacity`
```nupp
ensureCapacity: function(exclusive self: Buffer, minimum: integer): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
| `minimum` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `resize`
```nupp
resize: function(exclusive self: Buffer, length: integer): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
| `length` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `getString`
```nupp
getString: function(borrows self: Buffer, offset: integer?, count: integer?): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Buffer` | |
| `offset` | `integer?` | |
| `count` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
##### `setString`
```nupp
setString: function(exclusive self: Buffer, bytes: string, offset: integer?): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
| `bytes` | `string` | |
| `offset` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `readSpan`
```nupp
readSpan: function(borrows self: Buffer): span.ByteSpan borrows (self)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `span.ByteSpan borrows (self)` | |
##### `reserveWrite`
```nupp
reserveWrite: function(
exclusive self: Buffer,
offset: integer,
count: integer
): affine(BufferWriteLease, destroyOwner) borrows (self)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
| `offset` | `integer` | |
| `count` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `affine(BufferWriteLease, destroyOwner) borrows (self)` | |
##### `view`
```nupp
view: function(self: Buffer, offset: integer?, count: integer?): affine(ByteView, destroyOwner)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | |
| `offset` | `integer?` | |
| `count` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `affine(ByteView, destroyOwner)` | |
##### `newReader`
```nupp
newReader: function(self: Buffer): Reader
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `Reader` | |
##### `newWriter`
```nupp
newWriter: function(exclusive self: Buffer): affine(Writer) borrows (self)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `affine(Writer) borrows (self)` | |
##### `isReleased`
```nupp
isReleased: function(borrows self: Buffer): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `close`
```nupp
close: function(takes self: Buffer): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `Buffer` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
### `BufferWriteLease` _interface_
```nupp
sealed interface BufferWriteLease
drop: nosuspend function(takes self: BufferWriteLease): nil
span: function(exclusive self: BufferWriteLease): span.Writable borrows (self)
commit: function(takes self: BufferWriteLease, written: integer): nil
end
```
An exclusive prepared region of a buffer. Its span may be written, then the
lease is committed with the number of bytes that actually landed. Dropping a
lease aborts it without changing the buffer's logical length.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: BufferWriteLease): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `BufferWriteLease` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `span`
```nupp
span: function(exclusive self: BufferWriteLease): span.Writable borrows (self)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `BufferWriteLease` | |
###### Returns
| Type | Description |
| --- | --- |
| `span.Writable\ borrows (self)` | |
##### `commit`
```nupp
commit: function(takes self: BufferWriteLease, written: integer): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `BufferWriteLease` | |
| `written` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `ByteQueue` _interface_
```nupp
interface ByteQueue
metamethod __len: function(self): integer
get: function(self: ByteQueue, ...: integer?): string
end
```
A queue of bytes something else is filling, which a reader may be opened over
without owning it.
A process stream and an HTTP response body are both one of these without either
knowing about this module.
#### Methods
##### `__len`
```nupp
__len: function(self): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `get`
```nupp
get: function(self: ByteQueue, ...: integer?): string
```
Consumes bytes from the front and returns them, or all of them when given no
count.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteQueue` | this queue |
| `...` | `integer?` | how many bytes each returned string takes |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the consumed bytes |
### `ByteView` _interface_
```nupp
sealed interface ByteView
drop: nosuspend function(takes self: ByteView): nil
length: function(borrows self: ByteView): integer
getString: function(borrows self: ByteView): string
readSpan: function(borrows self: ByteView): span.ByteSpan borrows (self)
newReader: function(self: ByteView): Reader
view: function(self: ByteView, offset: integer?, count: integer?): affine(ByteView, destroyOwner)
isReleased: function(self: ByteView): boolean
close: function(takes self: ByteView): (boolean, string?)
end
```
A snapshot of bytes that cannot be written through.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: ByteView): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `length`
```nupp
length: function(borrows self: ByteView): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `getString`
```nupp
getString: function(borrows self: ByteView): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
##### `readSpan`
```nupp
readSpan: function(borrows self: ByteView): span.ByteSpan borrows (self)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `span.ByteSpan borrows (self)` | |
##### `newReader`
```nupp
newReader: function(self: ByteView): Reader
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `Reader` | |
##### `view`
```nupp
view: function(self: ByteView, offset: integer?, count: integer?): affine(ByteView, destroyOwner)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | |
| `offset` | `integer?` | |
| `count` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `affine(ByteView, destroyOwner)` | |
##### `isReleased`
```nupp
isReleased: function(self: ByteView): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `close`
```nupp
close: function(takes self: ByteView): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ByteView` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
### `Lines` _interface_
```nupp
sealed interface Lines is nupp.Closeable
drop: nosuspend function(takes self: Lines): nil
read: function(self: Lines, ...: integer?): (string?, string?)
isReleased: function(self: Lines): boolean
close: nosuspend function(takes self: Lines): nil
end
```
A reader taken apart into lines.
Reading a line means reading past its end, so something has to hold what came
after it. That is what this owns, and it is why a line reader takes its
source rather than borrowing one: two things reading the same bytes would
each see half of them.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: Lines): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `Lines` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `read`
```nupp
read: function(self: Lines, ...: integer?): (string?, string?)
```
Reads the next line, without its terminator.
Both `\n` and `\r\n` end a line. Bytes left after the last terminator are
a line, because a stream that ended without one still said something.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Lines` | this line reader |
| `...` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the line, or nil at the end |
| `string?` | why it could not read, when unsuccessful |
##### `isReleased`
```nupp
isReleased: function(self: Lines): boolean
```
Whether this reader has been released.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Lines` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `close`
```nupp
close: nosuspend function(takes self: Lines): nil
```
Releases this reader and the source it took.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `Lines` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `Reader` _interface_
```nupp
affine interface Reader is nupp.Closeable
read: function(self: Reader, count: integer): (string?, string?)
readSpan: function(self: Reader, exclusive destination: span.Writable): (integer?, string?)
readInto: function(
self: Reader,
exclusive destination: Buffer,
offset: integer?,
count: integer?
): (integer?, string?)
transferTo: function(self: Reader, exclusive destination: Writer): (integer?, string?)
end
```
A forward-only byte source.
An interface rather than the concrete things that satisfy it. A buffer's
reader, a file's reader and an HTTP response body are all one of these, so
code written against the contract works over any of them without knowing
which it has.
#### Methods
##### `read`
```nupp
read: function(self: Reader, count: integer): (string?, string?)
```
Reads up to `count` bytes.
An empty answer is the end.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `count` | `integer` | the most bytes to read |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the bytes, or nil when the reader is closed |
| `string?` | why it could not read, when unsuccessful |
###### Raises
- when count is not a positive integer
##### `readSpan`
```nupp
readSpan: function(self: Reader, exclusive destination: span.Writable): (integer?, string?)
```
Reads directly into a checked writable span. A zero answer is the end.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `exclusive destination` | `span.Writable\` | the positive-sized range to fill |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes were read, or nil when the reader is closed |
| `string?` | why it could not read, when unsuccessful |
##### `readInto`
```nupp
readInto: function(
self: Reader,
exclusive destination: Buffer,
offset: integer?,
count: integer?
): (integer?, string?)
```
Reads into a buffer.
A zero answer is the end.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `exclusive destination` | `Buffer` | the buffer to write into |
| `offset` | `integer?` | where in the destination to start, or the beginning |
| `count` | `integer?` | the most bytes to read |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes were read, or nil when the reader is closed |
| `string?` | why it could not read, when unsuccessful |
##### `transferTo`
```nupp
transferTo: function(self: Reader, exclusive destination: Writer): (integer?, string?)
```
Writes everything left to a writer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `exclusive destination` | `Writer` | the writer to fill |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes moved, or nil on failure |
| `string?` | why it could not, when unsuccessful |
### `ScalarReader` _interface_
```nupp
interface ScalarReader
drop: nosuspend function(takes self: ScalarReader): nil
remaining: function(self: ScalarReader): integer?
atEnd: function(self: ScalarReader): boolean
skip: function(self: ScalarReader, count: integer): ScalarReader
readBytes: function(self: ScalarReader, count: integer): string
readUint8: function(self: ScalarReader): uint32
readInt8: function(self: ScalarReader): int32
readUint16: function(self: ScalarReader): uint32
readInt16: function(self: ScalarReader): int32
readUint32: function(self: ScalarReader): uint32
readInt32: function(self: ScalarReader): int32
readUint64: function(self: ScalarReader): uint64
readInt64: function(self: ScalarReader): int64
readFloat32: function(self: ScalarReader): float
readFloat64: function(self: ScalarReader): number
close: function(takes self: ScalarReader): (boolean, string?)
end
```
Reads fixed-width numbers from a source of bytes.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: ScalarReader): nil
```
Releases it as an ownership terminal.
Repeated calls are safe. Declared rather than written inline: a cleanup
contract is `nosuspend`, and an inline method cannot carry one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ScalarReader` | this owner, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `remaining`
```nupp
remaining: function(self: ScalarReader): integer?
```
How many bytes are left, when that is knowable.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the remaining byte count, or nil over an open-ended source |
###### Raises
- when the reader is closed
##### `atEnd`
```nupp
atEnd: function(self: ScalarReader): boolean
```
Whether the source has nothing left.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is at the end |
###### Raises
- when the reader is closed, or its source failed
##### `skip`
```nupp
skip: function(self: ScalarReader, count: integer): ScalarReader
```
Discards bytes.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
| `count` | `integer` | how many to discard |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarReader` | this reader |
###### Raises
- when there are not that many
##### `readBytes`
```nupp
readBytes: function(self: ScalarReader, count: integer): string
```
Reads bytes as they are.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
| `count` | `integer` | how many to read |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the bytes |
###### Raises
- when there are not that many
##### `readUint8`
```nupp
readUint8: function(self: ScalarReader): uint32
```
Reads one unsigned 8-bit integer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `uint32` | the value, 0 through 255 |
###### Raises
- when the source has no bytes left
##### `readInt8`
```nupp
readInt8: function(self: ScalarReader): int32
```
Reads one signed 8-bit integer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `int32` | the value, -128 through 127 |
###### Raises
- when the source has no bytes left
##### `readUint16`
```nupp
readUint16: function(self: ScalarReader): uint32
```
Reads one unsigned 16-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `uint32` | the value, 0 through 65535 |
###### Raises
- when fewer than 2 bytes are left
##### `readInt16`
```nupp
readInt16: function(self: ScalarReader): int32
```
Reads one signed 16-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `int32` | the value, -32768 through 32767 |
###### Raises
- when fewer than 2 bytes are left
##### `readUint32`
```nupp
readUint32: function(self: ScalarReader): uint32
```
Reads one unsigned 32-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `uint32` | the value, 0 through 4294967295 |
###### Raises
- when fewer than 4 bytes are left
##### `readInt32`
```nupp
readInt32: function(self: ScalarReader): int32
```
Reads one signed 32-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `int32` | the value |
###### Raises
- when fewer than 4 bytes are left
##### `readUint64`
```nupp
readUint64: function(self: ScalarReader): uint64
```
Reads one unsigned 64-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `uint64` | the value, as `uint64` cdata |
###### Raises
- when fewer than 8 bytes are left
##### `readInt64`
```nupp
readInt64: function(self: ScalarReader): int64
```
Reads one signed 64-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `int64` | the value, as `int64` cdata |
###### Raises
- when fewer than 8 bytes are left
##### `readFloat32`
```nupp
readFloat32: function(self: ScalarReader): float
```
Reads one 32-bit float in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `float` | the value, widened to a Lua number |
###### Raises
- when fewer than 4 bytes are left
##### `readFloat64`
```nupp
readFloat64: function(self: ScalarReader): number
```
Reads one 64-bit float in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarReader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `number` | the value |
###### Raises
- when fewer than 8 bytes are left
##### `close`
```nupp
close: function(takes self: ScalarReader): (boolean, string?)
```
Closes the reader, and the reader it was built over when there was one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ScalarReader` | this reader, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the underlying source closed cleanly |
| `string?` | why it did not, when it did not |
### `ScalarWriter` _interface_
```nupp
interface ScalarWriter
drop: nosuspend function(takes self: ScalarWriter): nil
writeBytes: function(self: ScalarWriter, bytes: string): ScalarWriter
writeUint8: function(self: ScalarWriter, value: uint32): ScalarWriter
writeInt8: function(self: ScalarWriter, value: int32): ScalarWriter
writeUint16: function(self: ScalarWriter, value: uint32): ScalarWriter
writeInt16: function(self: ScalarWriter, value: int32): ScalarWriter
writeUint32: function(self: ScalarWriter, value: uint32): ScalarWriter
writeInt32: function(self: ScalarWriter, value: int32): ScalarWriter
writeUint64: function(self: ScalarWriter, value: uint64): ScalarWriter
writeInt64: function(self: ScalarWriter, value: int64): ScalarWriter
writeFloat32: function(self: ScalarWriter, value: float): ScalarWriter
writeFloat64: function(self: ScalarWriter, value: number): ScalarWriter
buffer: function(borrows self: ScalarWriter): Buffer? borrows (self)
flush: function(self: ScalarWriter): (boolean, string?)
close: function(takes self: ScalarWriter): (boolean, string?)
end
```
Writes fixed-width numbers into a destination.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: ScalarWriter): nil
```
Releases it as an ownership terminal.
Repeated calls are safe. Declared rather than written inline: a cleanup
contract is `nosuspend`, and an inline method cannot carry one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ScalarWriter` | this owner, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `writeBytes`
```nupp
writeBytes: function(self: ScalarWriter, bytes: string): ScalarWriter
```
Writes bytes as they are.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `bytes` | `string` | the bytes to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
###### Raises
- when bytes is not a string
##### `writeUint8`
```nupp
writeUint8: function(self: ScalarWriter, value: uint32): ScalarWriter
```
Writes one unsigned 8-bit integer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `uint32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeInt8`
```nupp
writeInt8: function(self: ScalarWriter, value: int32): ScalarWriter
```
Writes one signed 8-bit integer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `int32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeUint16`
```nupp
writeUint16: function(self: ScalarWriter, value: uint32): ScalarWriter
```
Writes one unsigned 16-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `uint32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeInt16`
```nupp
writeInt16: function(self: ScalarWriter, value: int32): ScalarWriter
```
Writes one signed 16-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `int32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeUint32`
```nupp
writeUint32: function(self: ScalarWriter, value: uint32): ScalarWriter
```
Writes one unsigned 32-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `uint32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeInt32`
```nupp
writeInt32: function(self: ScalarWriter, value: int32): ScalarWriter
```
Writes one signed 32-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `int32` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeUint64`
```nupp
writeUint64: function(self: ScalarWriter, value: uint64): ScalarWriter
```
Writes one unsigned 64-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `uint64` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeInt64`
```nupp
writeInt64: function(self: ScalarWriter, value: int64): ScalarWriter
```
Writes one signed 64-bit integer in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `int64` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeFloat32`
```nupp
writeFloat32: function(self: ScalarWriter, value: float): ScalarWriter
```
Writes one 32-bit float in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `float` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `writeFloat64`
```nupp
writeFloat64: function(self: ScalarWriter, value: number): ScalarWriter
```
Writes one 64-bit float in the machine's byte order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
| `value` | `number` | the number to write |
###### Returns
| Type | Description |
| --- | --- |
| `ScalarWriter` | this writer |
##### `buffer`
```nupp
buffer: function(borrows self: ScalarWriter): Buffer? borrows (self)
```
The buffer this writer fills, when it has one of its own.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `ScalarWriter` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `Buffer? borrows (self)` | the buffer, or nil when writing through a writer |
##### `flush`
```nupp
flush: function(self: ScalarWriter): (boolean, string?)
```
Flushes the destination, when there is one to flush.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ScalarWriter` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it flushed |
| `string?` | why it did not, when it did not |
##### `close`
```nupp
close: function(takes self: ScalarWriter): (boolean, string?)
```
Closes the writer, and the writer it was built over when there was one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `ScalarWriter` | this writer, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the underlying destination closed cleanly |
| `string?` | why it did not, when it did not |
### `Writer` _interface_
```nupp
affine interface Writer is nupp.Closeable
write: function(exclusive self: Writer, bytes: string): (boolean, string?)
writeSpan: function(exclusive self: Writer, borrows source: span.ByteSpan): (integer?, string?)
flush: function(self: Writer): (boolean, string?)
end
```
An appending byte sink.
The counterpart of `Reader`, and an interface for the same reason: a buffer,
an open file and a request body all satisfy it, and a producer written
against the contract fills any of them.
#### Methods
##### `write`
```nupp
write: function(exclusive self: Writer, bytes: string): (boolean, string?)
```
Appends bytes.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Writer` | this writer |
| `bytes` | `string` | the bytes to append |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether they were written |
| `string?` | why they were not, when unsuccessful |
###### Raises
- when bytes is not a string
##### `writeSpan`
```nupp
writeSpan: function(exclusive self: Writer, borrows source: span.ByteSpan): (integer?, string?)
```
Appends bytes from a checked shared span.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Writer` | this writer |
| `borrows source` | `span.ByteSpan` | bytes valid for the duration of the call |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes moved, or nil on failure |
| `string?` | why it could not, when unsuccessful |
##### `flush`
```nupp
flush: function(self: Writer): (boolean, string?)
```
Nothing is buffered behind this writer, so this only reports whether it is open.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the writer is open |
| `string?` | why it is not, when it is not |