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.
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. Code written against either contract works over every implementation without knowing which one 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.
Files, processes, network connections, TLS sessions, and HTTP bodies move bytes through the Reader and Writer contracts on this page. A parser written against byte I/O therefore accepts all of them.
Buffers, views, readers, writers, and their adapters are owners. A factory that takes a reader or writer transfers its close obligation to the adapter; a factory that borrows a buffer or byte queue leaves that value with its caller. close consumes an owner. Types that expose isReleased report their state without changing it.
Buffers#
newBuffer() creates an empty growable buffer. A string supplies initial bytes; an integer reserves capacity without changing the length.
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 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:
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 returns nothing, 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:
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.
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) == "") -- EOFread(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.
A read moves the reader's cursor as a write moves a writer's, so all four take the reader exclusively. Two readers over one cursor, such as a stream and a borrowed view of it, cannot both be reading at once; the checker refuses the second while the first is live.
readAll(reader, maxBytes) takes a reader and returns everything left in it as one string, closing the reader on the way out. A limit is checked as the bytes arrive, so a source larger than it returns a reason rather than a string:
assert(nupp.io.readAll(nupp.io.newStringReader("whole")) == "whole")
local text, reason = nupp.io.readAll(nupp.io.newStringReader("too long"), 3)
assert(text == nil and reason ~= nil)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. The writer holds its buffer exclusively for as long as it is live, so the buffer is read once the writer is gone:
local destination = nupp.io.newBuffer()
do
local writer = destination:newWriter()
assert(writer:write("prefix:"))
local payload = nupp.io.newBuffer("body")
assert(writer:writeSpan(payload:readSpan()) == 4)
assert(writer:flush())
end
assert(destination:getString() == "prefix:body")write returns a boolean and writeSpan returns the byte count. Both return 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. It is part of the contract because a file or socket buffers, and flush is where one reports a write that failed to land: close returns nothing, so a caller that has to know flushes first.
Lines#
newLines(reader, limit) takes a reader and separates it at \n and \r\n. The terminator is not returned, and a final fragment before EOF is still a line:
local lines = nupp.io.newLines(nupp.io.newStringReader("one\r\ntwo\nlast"))
assert(lines:read() == "one")
assert(lines:read() == "two")
assert(lines:read() == "last")
assert(lines:read() == nil) -- EOFThe default limit is 64 KiB. An overlong line or source failure returns nil with a reason. Closing or dropping the line reader closes the source it took.
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.
local writer = nupp.io.newScalarWriter()
writer:writeUint32(1447383632):writeFloat64(1.5)
local stored = writer:buffer()
assert(stored ~= nil)
local reader = nupp.io.newScalarReader(stored)
assert(reader:readUint32() == 1447383632)
assert(reader:readFloat64() == 1.5)
assert(reader:atEnd())Every write returns 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.
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:
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() returns nil and atEnd() is the question to ask instead. The scalar adapter takes that reader: closing the adapter, by hand or at the end of its scope, closes 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:
local destination = nupp.io.newBuffer("header:")
do
local scalars = nupp.io.newScalarWriter(destination)
scalars:writeUint8(33)
end
assert(destination:getString() == "header:!")A writer pointed at somebody else's Writer returns nil from buffer(), since the bytes are already gone. That writer is taken, so closing the scalar adapter closes 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.
Failures#
The files, process, network, TLS and HTTP modules return a failure from the environment as nil, err, where err is a nupp.io.Error: a kind a program can branch on and a message a person can read. The kind is the same on every platform and provider, so a missing file is "notFound" whether the operating system or the browser said so; the message is whatever the platform said and is not.
local text, err = nupp.io.files.read("settings.json")
if text == nil and err ~= nil and err.kind == "notFound" then
text = "{}"
endAn error prints as its message, and joins text the same way, so assert(files.read(path)) and print("cannot read: " .. err) read as they would with a string. The reads and writes of the Reader and Writer contracts are the exception: their reason stays a string, the same message, because a buffer or a string reader returns through the same contract.
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.
local queue = nupp.text.newBuffer()
queue:put("header:")
queue:put("!")
local reader = nupp.io.newScalarReader(queue)
assert(reader:readBytes(7) == "header:")
assert(queue:tostring() == "!")
assert(reader:readUint8() == 33)Reading drains the queue, and remaining() reports what it still holds. newQueueReader is the same bridge one layer down: it returns an ordinary Reader over the queue, for transferTo, readInto, and everything else that contract already covers.
local destination = nupp.io.newBuffer()
local queue = nupp.text.newBuffer()
queue:put("payload")
do
local reader = nupp.io.newQueueReader(queue)
assert(reader:transferTo(destination:newWriter()) == 7)
end
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.
Submodules
| Module | Description |
|---|---|
nupp.io.files | Portable filesystem access accepts native paths on native hosts and application-owned paths on browser hosts. |
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 returns 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.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 returns its components from the parse rather than... |
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Buffer | interface | Growable byte storage whose pointer and logical length never escape separately. |
BufferWriteLease | interface | An exclusive prepared region of a buffer. |
ByteQueue | interface | A queue of bytes something else is filling, which a reader may be opened over without owning it. |
ByteView | interface | A snapshot of bytes that cannot be written through. |
Error | record | Why an io operation failed: a kind to branch on and a message to show. |
ErrorKind | type | What kind of failure an io operation met, the same on every platform. |
Lines | interface | A reader taken apart into lines. |
Reader | interface | A forward-only byte source. |
ScalarReader | interface | Reads fixed-width numbers from a source of bytes. |
ScalarWriter | interface | Writes fixed-width numbers into a destination. |
Writer | interface | An appending byte sink. |
Functions
| Function | Kind | Description |
|---|---|---|
newBuffer | function | Creates a buffer, optionally over starting bytes or a starting capacity. |
newLines | function | Takes a reader apart into lines. |
newQueueReader | function | Opens a reader over a byte queue, which it does not own. |
newScalarReader | function | Opens a cursor for sized integer and float reads. |
newScalarWriter | function | Opens a cursor for sized integer and float writes. |
newStringReader | function | Opens a reader over bytes held as a string. |
readAll | function | Reads a reader to its end and returns everything it held as a string. |
Types#
Bufferinterface#
Growable byte storage whose pointer and logical length never escape separately.
Members
| Name | Kind | Description |
|---|---|---|
length | method | |
capacity | method | |
clear | method | |
ensureCapacity | method | |
resize | method | |
getString | method | |
setString | method | |
readSpan | method | |
reserveWrite | method | |
view | method | |
newReader | method | |
newWriter | method | |
isReleased | method | |
close | method |
lengthmethod#
length: function(borrows self: Buffer): integerArguments
| Name | Type | Description |
|---|---|---|
borrows self | Buffer |
Returns
| Type | Description |
|---|---|
integer |
capacitymethod#
capacity: function(borrows self: Buffer): integerArguments
| Name | Type | Description |
|---|---|---|
borrows self | Buffer |
Returns
| Type | Description |
|---|---|
integer |
ensureCapacitymethod#
ensureCapacity: function(exclusive self: Buffer, minimum: integer): nilArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Buffer | |
minimum | integer |
Returns
| Type | Description |
|---|---|
nil |
resizemethod#
resize: function(exclusive self: Buffer, length: integer): nilArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Buffer | |
length | integer |
Returns
| Type | Description |
|---|---|
nil |
getStringmethod#
getString: function(borrows self: Buffer, offset: integer?, count: integer?): stringArguments
| Name | Type | Description |
|---|---|---|
borrows self | Buffer | |
offset | integer? | |
count | integer? |
Returns
| Type | Description |
|---|---|
string |
setStringmethod#
setString: function(exclusive self: Buffer, bytes: string, offset: integer?): nilArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Buffer | |
bytes | string | |
offset | integer? |
Returns
| Type | Description |
|---|---|
nil |
readSpanmethod#
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Buffer |
Returns
| Type | Description |
|---|---|
span.ByteSpan borrows (self) |
reserveWritemethod#
reserveWrite: function(
exclusive self: Buffer,
offset: integer,
count: integer
): affine(BufferWriteLease, destroyLease) borrows (self)Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Buffer | |
offset | integer | |
count | integer |
Returns
| Type | Description |
|---|---|
affine(BufferWriteLease, destroyLease) borrows (self) |
viewmethod#
Arguments
| Name | Type | Description |
|---|---|---|
self | Buffer | |
offset | integer? | |
count | integer? |
Returns
| Type | Description |
|---|---|
affine(ByteView) |
newWritermethod#
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Buffer |
Returns
| Type | Description |
|---|---|
affine(Writer) borrows (self) |
BufferWriteLeaseinterface#
sealed interface BufferWriteLease ...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.
dropmethod#
drop: function(takes self: BufferWriteLease): nilArguments
| Name | Type | Description |
|---|---|---|
takes self | BufferWriteLease |
Returns
| Type | Description |
|---|---|
nil |
spanmethod#
span: function(exclusive self: BufferWriteLease): span.Writable<uint8> borrows (self)Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | BufferWriteLease |
Returns
| Type | Description |
|---|---|
span.Writable<uint8> borrows (self) |
commitmethod#
commit: function(takes self: BufferWriteLease, written: integer): nilArguments
| Name | Type | Description |
|---|---|---|
takes self | BufferWriteLease | |
written | integer |
Returns
| Type | Description |
|---|---|
nil |
ByteQueueinterface#
interface ByteQueue ...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.
Members
| Name | Kind | Description |
|---|---|---|
__len | ||
get | method | Consumes bytes from the front and returns them, or all of them when given no count. |
__len#
__len: function(self): integerArguments
| Name | Type | Description |
|---|---|---|
? | self |
Returns
| Type | Description |
|---|---|
integer |
ByteViewinterface#
A snapshot of bytes that cannot be written through.
Members
| Name | Kind | Description |
|---|---|---|
length | method | |
getString | method | |
readSpan | method | |
newReader | method | |
view | method | |
isReleased | method | |
close | method |
lengthmethod#
length: function(borrows self: ByteView): integerArguments
| Name | Type | Description |
|---|---|---|
borrows self | ByteView |
Returns
| Type | Description |
|---|---|
integer |
getStringmethod#
getString: function(borrows self: ByteView): stringArguments
| Name | Type | Description |
|---|---|---|
borrows self | ByteView |
Returns
| Type | Description |
|---|---|
string |
readSpanmethod#
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ByteView |
Returns
| Type | Description |
|---|---|
span.ByteSpan borrows (self) |
viewmethod#
Arguments
| Name | Type | Description |
|---|---|---|
self | ByteView | |
offset | integer? | |
count | integer? |
Returns
| Type | Description |
|---|---|
affine(ByteView) |
Errorrecord#
record Error ...Why an io operation failed: a kind to branch on and a message to show.
It prints as its message, and joins text as its message does, so code that treats a reason as text keeps working. Compare kind rather than matching the message, which is the platform's own words and differs between them.
Members
| Name | Kind | Description |
|---|---|---|
kind | field | What kind of failure it was. |
message | field | What happened, in the platform's or the module's words. |
__tostring | ||
__concat |
__tostring#
__tostring: function(self): stringArguments
| Name | Type | Description |
|---|---|---|
? | self |
Returns
| Type | Description |
|---|---|
string |
ErrorKindtype#
type ErrorKind = "notFound"
| "permission"
| "timeout"
| "refused"
| "closed"
| "invalid"
| "limit"
| "unsupported"
| "other"What kind of failure an io operation met, the same on every platform.
notFound: the file, program, host or other name does not exist.permission: the platform refused the operation to this process.timeout: a deadline passed before the operation finished.refused: the peer turned the connection away.closed: the resource, or the far end of it, has already gone.invalid: what was sent or received was not well formed.limit: a size, count or capacity bound was reached.unsupported: this platform or provider cannot do it at all.other: anything else; the message says what.
Linesinterface#
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.
Members
| Name | Kind | Description |
|---|---|---|
read | method | Reads the next line, without its terminator. |
isReleased | method | Whether this reader has been released. |
close | method | Releases this reader and the source it took. |
readmethod#
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 |
Returns
| Type | Description |
|---|---|
string? | the line, or nil at the end |
string? | why it could not read, when unsuccessful |
Readerinterface#
A forward-only byte source.
An interface rather than the concrete things that satisfy it. A buffer's reader, an open file 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.
Members
| Name | Kind | Description |
|---|---|---|
read | method | Reads up to count bytes. |
readSpan | method | Reads directly into a checked writable span. |
readInto | method | Reads into a buffer. |
transferTo | method | Writes everything left to a writer. |
readmethod#
Reads up to count bytes.
An empty answer is the end.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive 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
| Type | Condition |
|---|---|
string | when count is not a positive integer |
readSpanmethod#
readSpan: function(exclusive self: Reader, exclusive destination: span.Writable<uint8>): (integer?, string?)Reads directly into a checked writable span. A zero answer is the end.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Reader | this reader |
exclusive destination | span.Writable<uint8> | 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 |
readIntomethod#
readInto: function(
exclusive 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 |
|---|---|---|
exclusive 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 |
ScalarReaderinterface#
Reads fixed-width numbers from a source of bytes.
Members
| Name | Kind | Description |
|---|---|---|
remaining | method | How many bytes are left, when that is knowable. |
atEnd | method | Whether the source has nothing left. |
skip | method | Discards bytes. |
readBytes | method | Reads bytes as they are. |
readUint8 | method | Reads a unsigned 8-bit integer. |
readInt8 | method | Reads a signed 8-bit integer. |
readUint16 | method | Reads a unsigned 16-bit integer in the machine's byte order. |
readInt16 | method | Reads a signed 16-bit integer in the machine's byte order. |
readUint32 | method | Reads a unsigned 32-bit integer in the machine's byte order. |
readInt32 | method | Reads a signed 32-bit integer in the machine's byte order. |
readUint64 | method | Reads a unsigned 64-bit integer in the machine's byte order. |
readInt64 | method | Reads a signed 64-bit integer in the machine's byte order. |
readFloat32 | method | Reads a 32-bit float in the machine's byte order. |
readFloat64 | method | Reads a 64-bit float in the machine's byte order. |
close | method | Closes the reader, and the reader it was built over when there was one. |
remainingmethod#
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
| Type | Condition |
|---|---|
string | when the reader is closed |
atEndmethod#
atEnd: function(self: ScalarReader): booleanWhether the source has nothing left.
Arguments
| Name | Type | Description |
|---|---|---|
self | ScalarReader | this reader |
Returns
| Type | Description |
|---|---|
boolean | whether it is at the end |
Raises
| Type | Condition |
|---|---|
string | when the reader is closed, or its source failed |
skipmethod#
skip: function(borrows self: ScalarReader, count: integer): ScalarReader borrows (self)Discards bytes.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarReader | this reader |
count | integer | how many to discard |
Returns
| Type | Description |
|---|---|
ScalarReader borrows (self) | this reader |
Raises
| Type | Condition |
|---|---|
string | when there are not that many |
readBytesmethod#
readBytes: function(self: ScalarReader, count: integer): stringReads 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
| Type | Condition |
|---|---|
string | when there are not that many |
readUint8method#
readUint8: function(self: ScalarReader): uint32Reads a unsigned 8-bit integer.
Arguments
| Name | Type | Description |
|---|---|---|
self | ScalarReader | this reader |
Returns
| Type | Description |
|---|---|
uint32 | the value, 0 through 255 |
Raises
| Type | Condition |
|---|---|
string | when the source has no bytes left |
readInt8method#
readInt8: function(self: ScalarReader): int32Reads a signed 8-bit integer.
Arguments
| Name | Type | Description |
|---|---|---|
self | ScalarReader | this reader |
Returns
| Type | Description |
|---|---|
int32 | the value, -128 through 127 |
Raises
| Type | Condition |
|---|---|
string | when the source has no bytes left |
readUint16method#
readUint16: function(self: ScalarReader): uint32Reads a 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
| Type | Condition |
|---|---|
string | when fewer than 2 bytes are left |
readInt16method#
readInt16: function(self: ScalarReader): int32Reads a 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
| Type | Condition |
|---|---|
string | when fewer than 2 bytes are left |
readUint32method#
readUint32: function(self: ScalarReader): uint32Reads a 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
| Type | Condition |
|---|---|
string | when fewer than 4 bytes are left |
readInt32method#
readInt32: function(self: ScalarReader): int32Reads a 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
| Type | Condition |
|---|---|
string | when fewer than 4 bytes are left |
readUint64method#
readUint64: function(self: ScalarReader): uint64Reads a 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 |
Raises
| Type | Condition |
|---|---|
string | when fewer than 8 bytes are left |
readInt64method#
readInt64: function(self: ScalarReader): int64Reads a 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 |
Raises
| Type | Condition |
|---|---|
string | when fewer than 8 bytes are left |
readFloat32method#
readFloat32: function(self: ScalarReader): floatReads a 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
| Type | Condition |
|---|---|
string | when fewer than 4 bytes are left |
readFloat64method#
readFloat64: function(self: ScalarReader): numberReads a 64-bit float in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
self | ScalarReader | this reader |
Returns
| Type | Description |
|---|---|
number | the value |
Raises
| Type | Condition |
|---|---|
string | when fewer than 8 bytes are left |
closemethod#
close: function(takes self: ScalarReader): nilCloses the reader, and the reader it was built over when there was one.
Repeated calls are safe.
Arguments
| Name | Type | Description |
|---|---|---|
takes self | ScalarReader | this reader, spent by the call |
Returns
| Type | Description |
|---|---|
nil |
ScalarWriterinterface#
Writes fixed-width numbers into a destination.
Members
| Name | Kind | Description |
|---|---|---|
writeBytes | method | Writes bytes as they are. |
writeUint8 | method | Writes a unsigned 8-bit integer. |
writeInt8 | method | Writes a signed 8-bit integer. |
writeUint16 | method | Writes a unsigned 16-bit integer in the machine's byte order. |
writeInt16 | method | Writes a signed 16-bit integer in the machine's byte order. |
writeUint32 | method | Writes a unsigned 32-bit integer in the machine's byte order. |
writeInt32 | method | Writes a signed 32-bit integer in the machine's byte order. |
writeUint64 | method | Writes a unsigned 64-bit integer in the machine's byte order. |
writeInt64 | method | Writes a signed 64-bit integer in the machine's byte order. |
writeFloat32 | method | Writes a 32-bit float in the machine's byte order. |
writeFloat64 | method | Writes a 64-bit float in the machine's byte order. |
buffer | method | The buffer this writer fills, when it has one of its own. |
flush | method | Flushes the destination, when there is one to flush. |
close | method | Closes the writer, and the writer it was built over when there was one. |
writeBytesmethod#
writeBytes: function(borrows self: ScalarWriter, bytes: string): ScalarWriter borrows (self)Writes bytes as they are.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
bytes | string | the bytes to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
Raises
| Type | Condition |
|---|---|
string | when bytes is not a string |
writeUint8method#
writeUint8: function(borrows self: ScalarWriter, value: uint32): ScalarWriter borrows (self)Writes a unsigned 8-bit integer.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | uint32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeInt8method#
writeInt8: function(borrows self: ScalarWriter, value: int32): ScalarWriter borrows (self)Writes a signed 8-bit integer.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | int32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeUint16method#
writeUint16: function(borrows self: ScalarWriter, value: uint32): ScalarWriter borrows (self)Writes a unsigned 16-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | uint32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeInt16method#
writeInt16: function(borrows self: ScalarWriter, value: int32): ScalarWriter borrows (self)Writes a signed 16-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | int32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeUint32method#
writeUint32: function(borrows self: ScalarWriter, value: uint32): ScalarWriter borrows (self)Writes a unsigned 32-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | uint32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeInt32method#
writeInt32: function(borrows self: ScalarWriter, value: int32): ScalarWriter borrows (self)Writes a signed 32-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | int32 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeUint64method#
writeUint64: function(borrows self: ScalarWriter, value: uint64): ScalarWriter borrows (self)Writes a unsigned 64-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | uint64 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeInt64method#
writeInt64: function(borrows self: ScalarWriter, value: int64): ScalarWriter borrows (self)Writes a signed 64-bit integer in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | int64 | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeFloat32method#
writeFloat32: function(borrows self: ScalarWriter, value: float): ScalarWriter borrows (self)Writes a 32-bit float in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | float | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
writeFloat64method#
writeFloat64: function(borrows self: ScalarWriter, value: number): ScalarWriter borrows (self)Writes a 64-bit float in the machine's byte order.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | ScalarWriter | this writer |
value | number | the number to write |
Returns
| Type | Description |
|---|---|
ScalarWriter borrows (self) | this writer |
buffermethod#
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 |
flushmethod#
flush: function(self: ScalarWriter): (boolean, string?)Flushes the destination, when there is one to flush.
This is where a destination that failed to take its bytes says so: close returns nothing, so a caller that needs to know flushes first.
Arguments
| Name | Type | Description |
|---|---|---|
self | ScalarWriter | this writer |
Returns
| Type | Description |
|---|---|
boolean | whether it flushed |
string? | why it did not, when it did not |
closemethod#
close: function(takes self: ScalarWriter): nilCloses the writer, and the writer it was built over when there was one.
Repeated calls are safe.
Arguments
| Name | Type | Description |
|---|---|---|
takes self | ScalarWriter | this writer, spent by the call |
Returns
| Type | Description |
|---|---|
nil |
Writerinterface#
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.
Members
| Name | Kind | Description |
|---|---|---|
write | method | Appends bytes. |
writeSpan | method | Appends bytes from a checked shared span. |
flush | method | Nothing is buffered behind this writer, so this only reports whether it is open. |
writemethod#
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
| Type | Condition |
|---|---|
string | when bytes is not a string |
Functions#
newBufferfunction#
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.
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)
endArguments
| Name | Type | Description |
|---|---|---|
initial | integer | string? | the starting bytes, a starting capacity, or nothing |
Returns
| Type | Description |
|---|---|
affine(Buffer) | the buffer, owned by the caller |
Raises
| Type | Condition |
|---|---|
string | when initial is neither bytes nor a capacity |
newLinesfunction#
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
| Type | Condition |
|---|---|
string | when limit is not a positive integer |
newQueueReaderfunction#
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
| Type | Condition |
|---|---|
string | when source is not a byte queue |
newScalarReaderfunction#
const newScalarReader: function(source: string | ByteQueue): affine(ScalarReader)
& function(borrows source: ByteView | Buffer): affine(ScalarReader)
& function(takes source: Reader): affine(ScalarReader)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 returns no count, the queue returns 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.
do
local cursor = io.newScalarReader(header)
local magic = cursor:readUint32()
local count = cursor:readUint16()
endArguments
| Name | Type | Description |
|---|---|---|
source | string | ByteQueue | the bytes, snapshot, buffer, reader or queue to read |
Returns
| Type | Description |
|---|---|
affine(ScalarReader) | the new reader, owned by the caller |
Raises
| Type | Condition |
|---|---|
string | when source is none of those |
newScalarWriterfunction#
const newScalarWriter: function(): affine(ScalarWriter)
& function(borrows destination: Buffer): affine(ScalarWriter) borrows (destination)
& function(takes destination: Writer): affine(ScalarWriter)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.
local io = require("nupp.io")
do
local cursor = io.newScalarWriter()
cursor:writeUint32(0x4d495355):writeUint16(2)
local bytes = cursor:buffer()
assert(bytes ~= nil and bytes:getString() ~= "")
endArguments
| Name | Type | Description |
|---|---|---|
borrows destination | Buffer | the buffer or writer to append to, or nothing for a fresh buffer |
Returns
| Type | Description |
|---|---|
affine(ScalarWriter) borrows (destination) | the new writer, owned by the caller |
Raises
| Type | Condition |
|---|---|
string | when destination is neither a buffer nor a writer |
newStringReaderfunction#
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:
local io = require("nupp.io")
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
| Type | Condition |
|---|---|
string | when text is not a string |
readAllfunction#
function readAll(takes reader: Reader, maxBytes: integer?): string?, string?Reads a reader to its end and returns everything it held as a string.
The reader is taken, and closed on every way out. A limit is checked as the bytes arrive, so a source larger than it returns a reason rather than being held whole.
Arguments
| Name | Type | Description |
|---|---|---|
takes reader | Reader | the reader to drain |
maxBytes | integer? | the most bytes to accept, or no limit |
Returns
| Type | Description |
|---|---|
string? | the bytes, or nil when the reader failed or held more than the limit |
string? | why it could not, when unsuccessful |
Raises
| Type | Condition |
|---|---|
string | when maxBytes is not a positive integer |