Standard library#

nupp is the compiler-provided intrinsic namespace, present in every checked file. Reach for it when a program needs bytes, text, digests, logging, math, or a parser and does not want a dependency for them.

local digest = nupp.digest.hexDigest("sha256", "payload")
print(#digest, nupp.math.lerp(10, 20, 0.25))

Intrinsics and declared modules#

The namespace is a language surface, not an ambient source module and not a package tree assembled from declaration files. The compiler selects each implementation from the members checked source actually reaches, so nothing is loaded on a program's behalf.

Nupp-authored libraries outside that surface are ordinary declared modules on disk. A qualified module path loads one only when the source uses it:

const span = nupp.mem.span

const text = "four"
local bytes = span.fromString(text)

The full qualified form reaches the same declared file without the short binding:

const text = "four"
local bytes = nupp.mem.span.fromString(text)

It lowers to one hidden require in the containing module. It does not build a global nupp.mem table and does not add a load check at every call. The same rule applies to a dependency root such as tecs. See Modules for how a declared module is named and resolved.

Dive deeper

Keeping the intrinsic surface out of the module graph is what lets the compiler decide, per program, which implementations exist at all. A package tree would have to answer every parent-package import with a table whose members are all present, so either every program carries every facility or the answer depends on a load order the source does not show. A language surface hands out no such table: the members a file names are the members that get emitted.

Library modules#

The standard surface is deliberately small. These pages cover both the intrinsic namespaces and the declared modules:

  • nupp.digest provides incremental MD5, SHA-1, SHA-256 and SHA-512 with provider lookup, output-size metadata and consuming finalization.
  • nupp.checksum provides Adler-32 and explicitly named CRC variants as numeric values; protocols choose their serialization byte order.
  • nupp.mac provides keyed authentication, including HMAC-SHA256.
  • nupp.codec.json, nupp.codec.base64 and nupp.codec.hex encode and decode representations. nupp.codec.valuebuilder supports codec authors.
  • nupp.text.utf8 validates and walks UTF-8 text.
  • nupp.serde binds application types to reusable serialization schemas.
  • nupp.util owns the small general-purpose types: nupp.util.Bitset is a growable set of bit positions, and nupp.util.Store is a bag of values indexed by the typed keys nupp.util.newKey hands out. nupp.util.Pool leases cleared record instances of one declared type from a free list. nupp.util.fnv1a64 hashes bytes with FNV-1a. nupp.util.uuid4 and nupp.util.uuid7 generate identifiers, and are the only members here that reach a host provider.
  • nupp.system reports execution platform, architecture, endianness, pointer width and available parallelism, independently of the worker scheduler.
  • nupp.host asks whatever runs the program, a page or an embedding application, for something by kind, and carries streams between them. nupp.host.pump runs an embedded component's operations for a host-owned loop. See Host requests.
  • nupp.io owns byte buffers, readers, writers, and typed scalar reads and writes over them.
  • nupp.io.files owns filesystem metadata and directories.
  • nupp.io.net owns listeners and the connections they accept, and connections a program opens itself.
  • nupp.io.tls encrypts one of those connections.
  • nupp.io.path models filesystem paths, and nupp.io.uri models resource names.
  • nupp.log owns leveled logging over a swappable destination.
  • nupp.math owns scalar and two-dimensional vector helpers.
  • nupp.mem.span owns rooted, bounds-checked shared and writable C array views.
  • nupp.mem.soa stores every top-level field of a reified struct in its own column.
  • nupp.mem.arena leases zero-filled struct rows from pages that never move.
  • nupp.gpu owns resident buffers, generated kernel dispatches, and tensor views. nupp.gpu.layout owns the checked layout algebra.
  • nupp.random provides cryptographically secure random bytes and owns deterministic pseudo-random sequences with explicit, serializable state.
  • nupp.suspension, nupp.tasks, and nupp.workers provide waiting, application task scopes, and isolated worker lanes. nupp.suspension.host is the other side of waiting: what a host installs to decide which coroutine runs while something is parked.
  • nupp.time owns monotonic time, wall time, sleeps, and deadlines.
  • nupp.peg compiles byte-oriented parsing-expression grammars.

See GPU compute for generated GPU kernels and the browser provider, and Worker tasks for the worker scheduler and sendable values.

Availability and initialization#

nupp and nupp.math are intrinsic namespaces. The other standard modules have their own declared module identities and are loaded through qualified references or ordinary require calls. Providers resolve while their facades load.

Selection follows use#

A member's implementation is emitted only when checked source resolves that member, and an alias stays as precise as the name it came from:

const uuid4 = nupp.util.uuid4
print(uuid4()) -- selects UUID support

At -O1 and above, feature effects are recomputed after constant folding, so a facility used only in a branch or loop that dead-code elimination removed keeps neither its adapter nor its provider. Code generation makes the final selection from the constructs it actually writes, so comptime erasure, materialization, and other lowering cannot leave a source-only facility in the generated first-line bootstrap. Native FFI declarations are split by that same set: selecting UUID does not declare the path, files, or process ABI. SHA-256 declares none of its own: it is Nupp rather than a native provider.

Provider initialization#

A module selects its implementation while it is required and retains the actual functions. Generated module prologues bind those modules before the consumer body runs. Exported operations perform no SPI lookup.

Implementations satisfy the interface in the library's .spi module and are advertised in nupp/spi.json. Standard-library consumers choose the unique highest priority, or use their built-in fallback when discovery is empty. See SPI for ordinary selection code and package discovery.

The public surface does not expose the JSON provider module, a provider's own handles, or FFI pointers. Those are implementation details, so application code keeps the same Nupp API when a provider changes.

Forcing a native feature#

nativeFeatures turns a binary feature on or off for an unusual packaging arrangement. Leave it unset and use automatic detection unless the packaging requires otherwise.

Byte positions#

Storage offsets count from zero; element indexes count from one. Storage is a nupp.io.Buffer, a nupp.io.ByteView, a sharedbytes.Region or an open file: a range in one is a zero-based offset and a count, because an offset names a distance from the beginning and maps directly onto a native byte range. An element view, a span or a SoA view, and text, a Lua string or nupp.text.utf8, use 1-based positions, as a Lua array and string.sub do. Do not pass a string.find position to a buffer method.

Errors and ownership#

An operation that can fail because of the environment returns nil, reason. The files, process, network, TLS and HTTP modules give that reason as a nupp.io.Error, whose kind names the failure the same way on every platform, so a program branches on err.kind == "notFound" rather than on the message. An invalid argument or a malformed programmer-owned value raises at the call site. Every owner the library hands out, from a buffer to an open file, is a nupp.Closeable: close consumes it and returns nothing, nupp.drop closes it early, and the end of its scope closes it otherwise. A destination that buffers writes reports a failure through flush, so a caller that has to know flushes before it closes. Buffers and views report use after release, and a reader or writer returns a reason once it has been closed. See ownership.md for the cleanup obligation an affine result carries.

FAQ#

Does an intrinsic need a require?#

No. A member reached through the nupp namespace is selected by the compiler from the source that names it, which is why the example above runs with no imports. A declared module such as nupp.mem.span is a file on disk, so it is bound with require or reached through its qualified name, as Intrinsics and declared modules shows.

Does an unused facility cost anything in the built program?#

No adapter and no native artifact are emitted for a member no checked source resolves. Selection is recomputed after constant folding at -O1 and above, so a facility whose only use was eliminated drops out with it. See Selection follows use for what counts as a use.

Can a buffer offset be passed to a Lua string function?#

Not directly. A buffer offset counts from zero and a Lua string position counts from one, so the two differ by one on every call. Convert at the boundary, and see Byte positions for which convention an API uses.