nupp.codec.valuebuilder

Builds ordinary Lua values straight out of parsed bytes.

A parser written against this module never builds a table itself. It says what it found -- an array opens here, this byte range is a key, that one is a number -- and the module builds the value behind it.

local u32 = nupp.math.u32
local valuebuilder = require("nupp.codec.valuebuilder")

--- Decodes the fixed document `{"id": 41}`.
--- @raises string when the stream is left incomplete
@aot
local function decode(source: string, nullValue: any): any
    local builder = valuebuilder.new(nullValue)

    valuebuilder.openObject(builder, u32.wrap(1))
    valuebuilder.key(builder, source, u32.wrap(2), u32.wrap(2), false)
    valuebuilder.numberSlice(builder, source, u32.wrap(7), u32.wrap(2))
    valuebuilder.close(builder)

    return valuebuilder.finish(builder)
end

Values are reported as the parser reaches them, so there is no intermediate representation. Every offset, length, and index here counts from zero, because they address the parser's bytes rather than a Lua string. Each is a uint32, which an @aot body establishes with nupp.math.u32.wrap rather than by passing a number literal.

The lowering is why the module exists. Inside an @aot function these calls become one native construction pass: tables are presized from the capacities the parser authored, every unfinished container stays rooted in a Lua stack slot across allocations, writes go through the raw-set API so the barriers stay correct, and each string is copied once into Lua-owned storage. Outside @aot the same calls run as plain Lua, which is the aot = "off" oracle the lowering is checked against -- so one parser is portable across both.

It is the one construction boundary an AOT builder may cross, and a narrow one: no lua_State, no stack index, and no collector object appears in a signature here. A stream handle and a scratch buffer are local construction state under AOT, so neither can be returned, reassigned, stored in a table, or handed to an ordinary call.

See ahead-of-time.md for what generated code is allowed to do.

Module contents

Functions

FunctionKindDescription
booleanfunctionAdds a boolean value.
bytefunctionReads a byte at a zero-based offset.
byteAtfunctionReads a byte under a dominating offset < length(bytes) proof.
bytesfunctionViews the rooted bytes as a byte span, which is what the vector loads read.
closefunctionCloses the innermost open container and adds it to whatever encloses it.
countfunctionReturns the number of complete values in the current container.
decimal64functionPublishes a decimal from the uint64 mantissa and base-ten exponent already accumulated by a codec.
depthfunctionReturns how many containers are currently open.
finishfunctionEnds the stream and returns its single root value.
integer64functionPublishes an integer magnitude accumulated in native uint64 arithmetic.
integerSlicefunctionAdds an integer token from a range of the rooted source.
keyfunctionSets the next object key from a range of the rooted source.
keyScratchfunctionPublishes initialized scratch bytes as the next object key.
kindfunctionReturns the current container's tag: 5 for an array and 6 for an object.
lengthfunctionReturns the rooted string's byte length without making a substring.
newfunctionStarts a direct stream of values.
newByteScratchfunctionAllocates a bounded byte work buffer local to one decode.
newFixedByteScratchfunctionAllocates a fixed byte work buffer, every byte of it readable at once.
newFixedWordScratchfunctionAllocates a fixed uint32 work buffer, every word of it readable at once.
newSizedfunctionStarts a stream with authored bounds for native frame and transformed-byte scratch storage.
newWordScratchfunctionAllocates a bounded uint32 work buffer local to one decode.
nullfunctionAdds the null replacement the stream was started with by nupp.codec.valuebuilder.new or...
numberfunctionAdds a number the parser has already converted.
numberSlicefunctionAdds a number token from a range of the rooted source, converting it in place.
openArrayfunctionOpens an array.
openObjectfunctionOpens an object.
resetByteScratchfunctionMakes a byte scratch buffer empty without reallocating it.
scratchBytefunctionReads an initialized byte from a local work buffer.
scratchLengthfunctionReturns the initialized word count of a local work buffer.
scratchWordfunctionReads a word from a local work buffer.
setScratchBytefunctionWrites an existing byte or appends the next contiguous byte.
setScratchBytes4functionWrites four bytes at once, least significant first.
setScratchWordfunctionWrites an existing word or appends the next contiguous word.
statefunctionReturns the current container kind and whether it has a complete value in one query.
stringfunctionAdds a string value from a range of the rooted source.
stringScratchfunctionPublishes initialized scratch bytes as a normal Lua string value.
wordfunctionReads a native-endian uint32 at a zero-based word index.

Functions#

valuebuilder.booleanfunction#

function valuebuilder.boolean(builder: any, value: boolean): nil

Adds a boolean value.

Arguments

NameTypeDescription
builderany

the stream

valueboolean

the boolean to add

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the value would be a second root, or an object value with no key

valuebuilder.bytefunction#

function valuebuilder.byte(borrows bytes: any, offset: uint32): uint32

Reads a byte at a zero-based offset.

The scanning counterpart of nupp.codec.valuebuilder.length; read whole words with nupp.codec.valuebuilder.word when the bytes are a packed side table rather than the document.

Arguments

NameTypeDescription
borrows bytesany

the rooted string to read

offsetuint32

the zero-based byte offset

Returns

TypeDescription
uint32

the byte, 0 through 255

Raises

TypeCondition
string

when offset is outside bytes

valuebuilder.byteAtfunction#

function valuebuilder.byteAt(borrows bytes: any, offset: uint32): uint32

Reads a byte under a dominating offset < length(bytes) proof.

Ordinary Lua performs the same checked read as nupp.codec.valuebuilder.byte. In an AOT entry the verifier requires this call to be inside the true arm or body of that exact bounds check, then emits a direct rooted-string load. Reach for it in a parser's established cursor loop; use byte everywhere else.

Arguments

NameTypeDescription
borrows bytesany

the rooted string to read

offsetuint32

the proved in-bounds zero-based byte offset

Returns

TypeDescription
uint32

the byte, 0 through 255

Raises

TypeCondition
string

when offset is outside bytes outside AOT

valuebuilder.bytesfunction#

function valuebuilder.bytes(borrows bytes: any): ByteSpan

Views the rooted bytes as a byte span, which is what the vector loads read.

The general nupp.simd algebra loads from a Span<uint8>, and a value-building AOT entry has no way to be handed one: its parameters cross the Lua stack, where a pointer and a count are not values. This returns the span over the bytes the entry already roots, so species:load reads the input where it lies rather than through a byte-specialized counterpart.

Inside @aot the span is the parameter's own pointer and length, so the call costs nothing and a cursor + species.lanes <= length(bytes) guard proves a load through it exactly as the same guard proves one through a span parameter. It must name a parameter directly; a local holding a copy of one is refused, because the C name the load addresses is the parameter's.

In ordinary Lua the span is the same checked view nupp.mem.span.fromString returns, and the bytes have to be a string: a view roots nothing itself, so there is no rooted string for a buffer's unread bytes to be viewed through. Every vector path is inside a simd.species guard, which is nil there, so an interpreted body never reaches this.

Arguments

NameTypeDescription
borrows bytesany

the rooted string or buffer to view

Returns

TypeDescription
ByteSpan

the byte view over it

Raises

TypeCondition
string

when the bytes are not a string outside an AOT entry

valuebuilder.closefunction#

function valuebuilder.close(builder: any): nil

Closes the innermost open container and adds it to whatever encloses it.

Every nupp.codec.valuebuilder.openArray and nupp.codec.valuebuilder.openObject needs exactly one of these. Closing the outermost container publishes the root, which nupp.codec.valuebuilder.finish then returns.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
nil

Raises

TypeCondition
string

when no container is open, or when an object key has no value

string

when the closed container would be a second root

valuebuilder.countfunction#

function valuebuilder.count(builder: any): uint32

Returns the number of complete values in the current container.

Complete means a value has arrived, so an object with a key pending and no value yet still returns the count before it. A null dropped because the stream has no null replacement is a value that arrived.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
uint32

how many values the innermost open container holds

Raises

TypeCondition
string

when the stream has no current container

valuebuilder.decimal64function#

function valuebuilder.decimal64(builder: any, borrows source: any, start: uint32, length: uint32, magnitude: uint64, exponent: int32, negative: boolean, exact: boolean): nil

Publishes a decimal from the uint64 mantissa and base-ten exponent already accumulated by a codec. The native fast path handles Clinger's exact range; other inputs fall back to the authored source slice for correct rounding.

Arguments

NameTypeDescription
builderany
borrows sourceany
startuint32
lengthuint32
magnitudeuint64
exponentint32
negativeboolean
exactboolean

Returns

TypeDescription
nil

Raises

TypeCondition
string

outside an AOT-compiled function or when the source range is invalid

valuebuilder.depthfunction#

function valuebuilder.depth(builder: any): uint32

Returns how many containers are currently open.

This and nupp.codec.valuebuilder.state let an iterative parser see the construction state; there is deliberately no way to read a value back out of a container it has already been added to.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
uint32

the number of open containers, 0 at the top level

valuebuilder.finishfunction#

function valuebuilder.finish(builder: any): any

Ends the stream and returns its single root value.

valuebuilder.openArray(builder, 1)
valuebuilder.number(builder, 41)
valuebuilder.close(builder)
local result = valuebuilder.finish(builder)  -- {41}

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
any

the one value the stream published

Raises

TypeCondition
string

when a container is still open, or when no root was published

valuebuilder.integer64function#

function valuebuilder.integer64(builder: any, magnitude: uint64, negative: boolean): nil

Publishes an integer magnitude accumulated in native uint64 arithmetic. Internal to AOT codecs that must avoid a decimal rescan.

Arguments

NameTypeDescription
builderany
magnitudeuint64
negativeboolean

Returns

TypeDescription
nil

Raises

TypeCondition
string

outside an AOT-compiled function

valuebuilder.integerSlicefunction#

function valuebuilder.integerSlice(builder: any, borrows source: any, start: uint32, length: uint32): nil

Adds an integer token from a range of the rooted source.

The integer counterpart of nupp.codec.valuebuilder.numberSlice. AOT accumulates short integers directly without entering strtod, and falls back to the same checked binary64 conversion for longer tokens; the ordinary path retains tonumber as the behavioral oracle either way.

Arguments

NameTypeDescription
builderany

the stream

borrows sourceany

the rooted string the range indexes

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the source range is out of bounds

string

when the source range is not an integer token

string

when the value would be a second root, or an object value with no key

valuebuilder.keyfunction#

function valuebuilder.key(builder: any, borrows source: any, start: uint32, length: uint32, escaped: boolean): nil

Sets the next object key from a range of the rooted source.

A name the object already has is refused, whether the earlier member was stored or was a dropped null. The range is read in place, so a key that holds no escape costs one copy and no substring. Pass escaped as true only when the range may hold backslash escapes; false takes the bytes exactly as they lie. Assemble a key that is not a contiguous source range with nupp.codec.valuebuilder.keyScratch instead.

Arguments

NameTypeDescription
builderany

the stream

borrows sourceany

the rooted string the range indexes

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

escapedboolean

whether the range may hold backslash escapes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when outside an object, or when a key is already pending

string

when the source range is out of bounds

string

when an escape in the range is malformed

string

when the object already has a member of this name

valuebuilder.keyScratchfunction#

function valuebuilder.keyScratch(builder: any, scratch: any, start: uint32, length: uint32): nil

Publishes initialized scratch bytes as the next object key.

The key-position counterpart of nupp.codec.valuebuilder.stringScratch. The very next value added becomes this key's value.

Arguments

NameTypeDescription
builderany

the stream

scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when outside an object, when a key is already pending, or when the range is not initialized

valuebuilder.kindfunction#

function valuebuilder.kind(builder: any): uint32

Returns the current container's tag: 5 for an array and 6 for an object.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
uint32

the innermost open container's tag

Raises

TypeCondition
string

when the stream has no current container

valuebuilder.lengthfunction#

function valuebuilder.length(borrows bytes: any): uint32

Returns the rooted string's byte length without making a substring.

This, nupp.codec.valuebuilder.byte, and nupp.codec.valuebuilder.word are the three readers that let an AOT parser read its input where it already lies. #bytes returns the same in ordinary Lua; going through here is what keeps the read admissible inside @aot.

Arguments

NameTypeDescription
borrows bytesany

the rooted string to measure

Returns

TypeDescription
uint32

its length in bytes

valuebuilder.newfunction#

function valuebuilder.new(nullValue: any): any

Starts a direct stream of values.

This is the streaming shape. Open a container with nupp.codec.valuebuilder.openArray or nupp.codec.valuebuilder.openObject, add values to it, nupp.codec.valuebuilder.close it, and take the single root back from nupp.codec.valuebuilder.finish. A stream must publish exactly one root: adding a second value at the top level raises, and so does finishing with none.

The ordinary implementation keeps growable Lua state and is the aot = "off" oracle. VM-aware AOT keeps the same state in a bounded C stack object and roots every unfinished container on the Lua stack; it admits at most 1,024 open containers. Use nupp.codec.valuebuilder.newSized to choose that bound, and to reserve scratch for transformed strings, when the parser knows better.

local u32 = nupp.math.u32
local valuebuilder = require("nupp.codec.valuebuilder")

--- Builds `[true, null]` without a source document.
--- @raises string when the stream is left incomplete
@aot
local function pair(nullValue: any): any
    local builder = valuebuilder.new(nullValue)

    valuebuilder.openArray(builder, u32.wrap(2))
    valuebuilder.boolean(builder, true)
    valuebuilder.null(builder)
    valuebuilder.close(builder)

    return valuebuilder.finish(builder)
end

Arguments

NameTypeDescription
nullValueany

what nupp.codec.valuebuilder.null adds

Returns

TypeDescription
any

the stream handle, which under AOT is local construction state and cannot be returned, reassigned, stored, or passed to an ordinary call

valuebuilder.newByteScratchfunction#

function valuebuilder.newByteScratch(capacity: uint32): any

Allocates a bounded byte work buffer local to one decode.

This is where a codec assembles bytes that are not a range of the source, an unescaped string or a decoded field, before publishing them. Write with nupp.codec.valuebuilder.setScratchByte, read with nupp.codec.valuebuilder.scratchByte, empty it between values with nupp.codec.valuebuilder.resetByteScratch, and publish a finished range with nupp.codec.valuebuilder.stringScratch or nupp.codec.valuebuilder.keyScratch.

When the bytes are already a contiguous range of the source, skip all of this and use nupp.codec.valuebuilder.string or nupp.codec.valuebuilder.key, which read the source in place.

Arguments

NameTypeDescription
capacityuint32

the most bytes this buffer will ever hold

Returns

TypeDescription
any

the scratch handle, local to the call that allocated it

valuebuilder.newFixedByteScratchfunction#

function valuebuilder.newFixedByteScratch(capacity: uint32): any

Allocates a fixed byte work buffer, every byte of it readable at once.

The byte counterpart of nupp.codec.valuebuilder.newFixedWordScratch, and the difference from nupp.codec.valuebuilder.newByteScratch is the same one. That buffer grows: a byte is readable once something has written it, so the bound every access is checked against is a length that moves. This one is zero from the moment it exists, so every byte below the capacity is readable and writable immediately, in any order.

Reach for it where the buffer is a block of a size the source knows -- a digest's final block, a fixed-width field, a lookup -- and for the appending one where the size is the input's. The checks are the same and refuse the same indexes; what changes is that the bound is a number rather than a field to load, and that an @aot entry can stand the storage on the C stack instead of allocating it. bench/sha256 measured those two together at about a hundred nanoseconds of a two-hundred-and-ninety nanosecond digest.

There is no emptying it: nupp.codec.valuebuilder.resetByteScratch refuses a fixed buffer, because a buffer with no fill state has none to discard.

An @aot entry requires the capacity to be a literal, since a bound that is not known is not one anything can reason about. Ordinary Nupp takes any.

Arguments

NameTypeDescription
capacityuint32

how many bytes this buffer holds

Returns

TypeDescription
any

the scratch handle, local to the call that allocated it

valuebuilder.newFixedWordScratchfunction#

function valuebuilder.newFixedWordScratch(capacity: uint32): any

Allocates a fixed uint32 work buffer, every word of it readable at once.

The difference from nupp.codec.valuebuilder.newWordScratch is what "initialized" means. That buffer grows: a word is readable once something has written it, so reading ahead of the writes raises, and the bound every access is checked against is a length that moves. This one is zero from the moment it exists, so every word below the capacity is readable and writable immediately and the bound never moves.

Reach for it where the buffer is a table of a size the source knows -- a message schedule, a state vector, a lookup -- and for the appending one where the size is the input's. The checks are the same and refuse the same indexes; what changes is that a fixed buffer's bound is a number the code generator has rather than a field it must load, which is what lets it discharge the comparison itself in a counted loop. bench/sha256 measures nupp.digest.internal.sha256 at 0.849x the hand-written C before and 0.963x after.

An @aot entry requires the capacity to be a literal, since a bound that is not known is not one anything can reason about. Ordinary Nupp takes any.

Arguments

NameTypeDescription
capacityuint32

how many words this buffer holds

Returns

TypeDescription
any

the scratch handle, local to the call that allocated it

valuebuilder.newSizedfunction#

function valuebuilder.newSized(nullValue: any, maxDepth: uint32, stringCapacity: uint32, arrayMarker: any?, objectMarker: any?): any

Starts a stream with authored bounds for native frame and transformed-byte scratch storage.

Same contract as nupp.codec.valuebuilder.new; only the storage differs. The ordinary implementation keeps normal growable Lua state and ignores both bounds, because plain Lua has nothing to preallocate. AOT uses them to allocate two rooted regions once: the first 16 frames stay inline and deeper streams lazily spill to Lua-rooted storage, and the byte region is allocated on the first escaped string and then reused. Publication still copies exactly once into a normal Lua string.

Reach for this over nupp.codec.valuebuilder.new when the document may nest deeper than 1,024 containers, or when the parser already knows the longest string it can produce and would rather not grow into it.

Arguments

NameTypeDescription
nullValueany

what nupp.codec.valuebuilder.null adds

maxDepthuint32

the most containers that may be open at once

stringCapacityuint32

the most bytes one transformed string may need

arrayMarkerany?
objectMarkerany?

Returns

TypeDescription
any

the stream handle, on the same terms as nupp.codec.valuebuilder.new

valuebuilder.newWordScratchfunction#

function valuebuilder.newWordScratch(capacity: uint32): any

Allocates a bounded uint32 work buffer local to one decode.

AOT lowers this to Lua-owned userdata, so its address remains stable across value allocations without crossing the public API as a pointer. Words are initialized by contiguous writes before they can be read: append or overwrite with nupp.codec.valuebuilder.setScratchWord, and read it back with nupp.codec.valuebuilder.scratchWord. For transformed bytes rather than indexes, use nupp.codec.valuebuilder.newByteScratch. A zero-capacity scratch allocates no userdata and remains valid until an attempted read or write reports the ordinary bounds error.

Arguments

NameTypeDescription
capacityuint32

the most words this buffer will ever hold

Returns

TypeDescription
any

the scratch handle, local to the call that allocated it

valuebuilder.nullfunction#

function valuebuilder.null(builder: any): nil

Adds the null replacement the stream was started with by nupp.codec.valuebuilder.new or nupp.codec.valuebuilder.newSized.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the value would be a second root, or an object value with no key

valuebuilder.numberfunction#

function valuebuilder.number(builder: any, value: number): nil

Adds a number the parser has already converted.

Use nupp.codec.valuebuilder.numberSlice or nupp.codec.valuebuilder.integerSlice instead when the number is still a range of the source, so that the conversion happens without a substring.

Arguments

NameTypeDescription
builderany

the stream

valuenumber

the number to add

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the value would be a second root, or an object value with no key

valuebuilder.numberSlicefunction#

function valuebuilder.numberSlice(builder: any, borrows source: any, start: uint32, length: uint32): nil

Adds a number token from a range of the rooted source, converting it in place.

This accepts any numeric token. When the token is known to be an integer, nupp.codec.valuebuilder.integerSlice says so and is cheaper.

Arguments

NameTypeDescription
builderany

the stream

borrows sourceany

the rooted string the range indexes

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the source range is out of bounds

string

when the source range is not a number token

string

when the value would be a second root, or an object value with no key

valuebuilder.openArrayfunction#

function valuebuilder.openArray(builder: any, capacity: uint32): nil

Opens an array. Every value added after it belongs to it until the matching nupp.codec.valuebuilder.close.

capacity is a presizing hint taken from what the parser already counted, not a limit: a wrong guess costs a rehash, never an error.

Arguments

NameTypeDescription
builderany

the stream

capacityuint32

how many elements to presize the array part for

Returns

TypeDescription
nil

valuebuilder.openObjectfunction#

function valuebuilder.openObject(builder: any, capacity: uint32): nil

Opens an object. Each value inside it must be preceded by a key from nupp.codec.valuebuilder.key or nupp.codec.valuebuilder.keyScratch, until the matching nupp.codec.valuebuilder.close.

capacity presizes the hash part and is a hint, as in nupp.codec.valuebuilder.openArray.

Arguments

NameTypeDescription
builderany

the stream

capacityuint32

how many entries to presize the hash part for

Returns

TypeDescription
nil

valuebuilder.resetByteScratchfunction#

function valuebuilder.resetByteScratch(scratch: any): nil

Makes a byte scratch buffer empty without reallocating it.

Call this between values so that one bounded allocation serves a whole decode. Bytes below the old length remain in memory but are no longer readable, since nupp.codec.valuebuilder.scratchByte admits only initialized indexes.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the buffer is a fixed one, which has no fill state to discard

valuebuilder.scratchBytefunction#

function valuebuilder.scratchByte(scratch: any, index: uint32): uint32

Reads an initialized byte from a local work buffer.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

indexuint32

the zero-based byte index

Returns

TypeDescription
uint32

the byte, 0 through 255

Raises

TypeCondition
string

when the indexed byte has not been initialized

valuebuilder.scratchLengthfunction#

function valuebuilder.scratchLength(scratch: any): uint32

Returns the initialized word count of a local work buffer.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newWordScratch

Returns

TypeDescription
uint32

the number of initialized words

valuebuilder.scratchWordfunction#

function valuebuilder.scratchWord(scratch: any, index: uint32): uint32

Reads a word from a local work buffer.

Only initialized words can be read, so an index at or past the buffer's current length raises rather than answering whatever was there.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newWordScratch

indexuint32

the zero-based word index

Returns

TypeDescription
uint32

the word's value

Raises

TypeCondition
string

when the indexed word has not been initialized

valuebuilder.setScratchBytefunction#

function valuebuilder.setScratchByte(scratch: any, index: uint32, value: uint32): nil

Writes an existing byte or appends the next contiguous byte.

Appends and grows by one when index is the current length, overwrites below it, on the same terms as nupp.codec.valuebuilder.setScratchWord.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

indexuint32

the zero-based byte index, at most the current length

valueuint32

the byte to store, 0 through 255

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the write leaves a gap, exceeds capacity, or is not a byte

valuebuilder.setScratchBytes4function#

function valuebuilder.setScratchBytes4(scratch: any, index: uint32, value: uint32): nil

Writes four bytes at once, least significant first.

The counterpart of nupp.codec.valuebuilder.word on the writing side, and the reason it exists: a codec that produces bytes in groups pays one bounds check and one store per group rather than four of each. bench/base64 measured the byte-at-a-time path at over half of its encode.

The order is little-endian and stated rather than native, unlike the read: a defined order is one a plain-Lua implementation can honour without knowing the machine, and on every target Nupp has it is what a native store already does. index is a byte index, so the four bytes land at index through index + 3.

Appends and grows by four when index is the current length, overwrites below it, and refuses a write that would straddle the two.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

indexuint32

the zero-based byte index, at most the current length

valueuint32

the four bytes, least significant at index

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the write leaves a gap, straddles the length, or exceeds capacity

valuebuilder.setScratchWordfunction#

function valuebuilder.setScratchWord(scratch: any, index: uint32, value: uint32): nil

Writes an existing word or appends the next contiguous word.

Writing at the buffer's current length appends and grows it by one; writing below that overwrites. There is no way to write past the end, which is what makes "initialized" mean the same thing as "below the length" for nupp.codec.valuebuilder.scratchWord.

Arguments

NameTypeDescription
scratchany

a buffer from nupp.codec.valuebuilder.newWordScratch

indexuint32

the zero-based word index, at most the current length

valueuint32

the word to store

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the index leaves a gap or exceeds the capacity

valuebuilder.statefunction#

function valuebuilder.state(builder: any): uint32

Returns the current container kind and whether it has a complete value in one query. Zero means no container is open. Otherwise the low byte is 5 for an array or 6 for an object, and bit 0x100 is set after its first value.

This is the delimiter-loop shape of nupp.codec.valuebuilder.depth, nupp.codec.valuebuilder.kind, and nupp.codec.valuebuilder.count. It avoids making a native parser cross the builder boundary three times when it only needs to distinguish the root, an empty container, and a nonempty container.

Arguments

NameTypeDescription
builderany

the stream

Returns

TypeDescription
uint32

the packed current state

valuebuilder.stringfunction#

function valuebuilder.string(builder: any, borrows source: any, start: uint32, length: uint32, escaped: boolean): nil

Adds a string value from a range of the rooted source.

The value counterpart of nupp.codec.valuebuilder.key, on the same terms: the range is read in place, and escaped says whether it needs decoding first.

Arguments

NameTypeDescription
builderany

the stream

borrows sourceany

the rooted string the range indexes

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

escapedboolean

whether the range may hold backslash escapes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the source range is out of bounds

string

when an escape in the range is malformed

string

when the value would be a second root, or an object value with no key

valuebuilder.stringScratchfunction#

function valuebuilder.stringScratch(builder: any, scratch: any, start: uint32, length: uint32): nil

Publishes initialized scratch bytes as a normal Lua string value.

The copy into Lua-owned storage happens here, exactly once. Use nupp.codec.valuebuilder.keyScratch for the object-key position instead, and nupp.codec.valuebuilder.string when the bytes are already a range of the source and need no assembling.

Arguments

NameTypeDescription
builderany

the stream

scratchany

a buffer from nupp.codec.valuebuilder.newByteScratch

startuint32

the range's zero-based first byte

lengthuint32

the range's length in bytes

Returns

TypeDescription
nil

Raises

TypeCondition
string

when the range is not initialized

string

when the value would be a second root, or an object value with no key

valuebuilder.wordfunction#

function valuebuilder.word(bytes: string, index: uint32): uint32

Reads a native-endian uint32 at a zero-based word index.

This is how a parser reads a packed side table, a structural index or a tape, back out of a rooted string instead of a Lua array of numbers. The word at index begins at byte offset index * 4.

Arguments

NameTypeDescription
bytesstring

the rooted string to read

indexuint32

the zero-based word index

Returns

TypeDescription
uint32

the word's value

Raises

TypeCondition
string

when the complete word is outside bytes