chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
## When To Use std.args
|
||||
|
||||
In Zerolang, use `std.args` for hosted command-line programs that need positional
|
||||
arguments, option lookup, or simple numeric argument parsing.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.args.len()` | `usize` | Returns the process argument count. |
|
||||
| `std.args.get(index)` | `Maybe<String>` | Returns the argument at `index` when present. |
|
||||
| `std.args.has(index)` | `Bool` | Reports whether `index` has an argument. |
|
||||
| `std.args.getOr(index, fallback)` | `String` | Returns the argument or a caller-provided fallback. |
|
||||
| `std.args.find(name)` | `Maybe<usize>` | Finds the first exact argument match after the executable path. |
|
||||
| `std.args.valueAfter(name)` | `Maybe<String>` | Returns the argument immediately after a matched option name. |
|
||||
| `std.args.parseU32(index)` | `Maybe<u32>` | Parses an indexed argument as `u32`. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Iterator-style argument APIs.
|
||||
- Target diagnostics for platforms without process arguments.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let count: usize = std.args.len()
|
||||
let first: String = std.args.getOr(1, "default")
|
||||
let maybe_count: Maybe<u32> = std.args.parseU32(2)
|
||||
if count > 2 && maybe_count.has {
|
||||
check world.out.write(first)
|
||||
check world.out.write("\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
The module is hosted-only. Freestanding, edge, and embedded targets should
|
||||
reject it unless they explicitly provide an argument capability.
|
||||
|
||||
On native Windows-style targets, `std.args` is byte-oriented process input. It
|
||||
is not a Unicode argv normalization layer.
|
||||
|
||||
Programs that need portable argument semantics should keep target-specific
|
||||
decoding outside the target-neutral core.
|
||||
@@ -0,0 +1,38 @@
|
||||
## When To Use std.ascii
|
||||
|
||||
In Zerolang, use `std.ascii` when a program needs byte-level ASCII predicates, case
|
||||
conversion, or digit values without Unicode normalization.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.ascii.isDigit(byte)` | `Bool` | Checks `0` through `9`. |
|
||||
| `std.ascii.isAlpha(byte)` | `Bool` | Checks `A` through `Z` or `a` through `z`. |
|
||||
| `std.ascii.isAlnum(byte)` | `Bool` | Checks ASCII alphabetic or digit bytes. |
|
||||
| `std.ascii.isWhitespace(byte)` | `Bool` | Checks space, tab, line feed, and carriage return. |
|
||||
| `std.ascii.isLower(byte)` / `std.ascii.isUpper(byte)` | `Bool` | Checks ASCII case ranges. |
|
||||
| `std.ascii.isHexDigit(byte)` | `Bool` | Checks decimal digits and `a-f` / `A-F`. |
|
||||
| `std.ascii.toLower(byte)` / `std.ascii.toUpper(byte)` | `u8` | Converts ASCII letters and leaves other bytes unchanged. |
|
||||
| `std.ascii.digitValue(byte)` | `Maybe<u8>` | Converts an ASCII decimal digit to `0..9`. |
|
||||
| `std.ascii.hexValue(byte)` | `Maybe<u8>` | Converts an ASCII hexadecimal digit to `0..15`. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let digit: Maybe<u8> = std.ascii.digitValue(55_u8)
|
||||
let hex: Maybe<u8> = std.ascii.hexValue(70_u8)
|
||||
if std.ascii.isAlpha(65_u8) && std.ascii.toLower(90_u8) == 122_u8 && digit.has && digit.value == 7_u8 && hex.has && hex.value == 15_u8 {
|
||||
check world.out.write("ascii ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: value helpers return `null` when the byte is outside the accepted range.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,87 @@
|
||||
## When To Use std.cli
|
||||
|
||||
In Zerolang, use `std.cli` for hosted command-line flag and option helpers that sit one
|
||||
level above raw `std.args` access.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.cli.argEquals(index, expected)` | `Bool` | Checks one argument against an exact string. |
|
||||
| `std.cli.command()` | `Maybe<String>` | Returns argument 1 as the command name. |
|
||||
| `std.cli.commandOr(fallback)` | `String` | Returns the command name or a fallback. |
|
||||
| `std.cli.commandEquals(expected)` | `Bool` | Checks argument 1 against an exact command string. |
|
||||
| `std.cli.argOr(index, fallback)` | `String` | Returns an argument or a fallback. |
|
||||
| `std.cli.argU32Or(index, fallback)` | `u32` | Parses an argument as `u32` or returns a fallback. |
|
||||
| `std.cli.hasFlag(name)` | `Bool` | Reports whether an exact flag is present. |
|
||||
| `std.cli.optionValue(name)` | `Maybe<String>` | Returns the value immediately after an option name. |
|
||||
| `std.cli.optionValueOr(name, fallback)` | `String` | Returns the option value or a fallback. |
|
||||
| `std.cli.optionU32(name)` | `Maybe<u32>` | Parses an option value as `u32`. |
|
||||
| `std.cli.successExitCode()` | `i32` | Returns the conventional success exit code. |
|
||||
| `std.cli.usageExitCode()` | `i32` | Returns the conventional command-line usage error code. |
|
||||
| `std.cli.isHelp(command)` | `Bool` | Recognizes `help`, `--help`, and `-h`. |
|
||||
| `std.cli.needsHelp()` | `Bool` | Reports whether the current invocation has no command or asks for help. |
|
||||
| `std.cli.commandIn2(command, first, second)` | `Bool` | Checks a command against two accepted command names. |
|
||||
| `std.cli.commandIn3(command, first, second, third)` | `Bool` | Checks a command against three accepted command names. |
|
||||
| `std.cli.formatUsage(buffer, program, syntax)` | `Maybe<Span<u8>>` | Writes `usage: <program> <syntax>` into caller storage. |
|
||||
| `std.cli.formatCommand(buffer, name, syntax, summary)` | `Maybe<Span<u8>>` | Writes one indented command help row. |
|
||||
| `std.cli.formatOption(buffer, name, valueName, summary)` | `Maybe<Span<u8>>` | Writes one indented option help row. |
|
||||
| `std.cli.formatSection(buffer, title)` | `Maybe<Span<u8>>` | Writes a section heading such as `Options:\n`. |
|
||||
| `std.cli.formatHelpRow(buffer, label, summary)` | `Maybe<Span<u8>>` | Writes one padded, newline-terminated help row using the default label width. |
|
||||
| `std.cli.formatHelpRowCustom(buffer, label, summary, indent, width)` | `Maybe<Span<u8>>` | Writes one padded help row using caller-supplied indentation and label width. |
|
||||
| `std.cli.formatHelpRowWithWidth(buffer, label, summary, width)` | `Maybe<Span<u8>>` | Writes one padded help row using a caller-supplied label width. |
|
||||
| `std.cli.formatHelp(buffer, usage, description)` | `Maybe<Span<u8>>` | Writes a help header with `Usage:` and an optional description. |
|
||||
| `std.cli.formatError(buffer, message)` | `Maybe<Span<u8>>` | Writes an `error: ...` line. |
|
||||
| `std.cli.formatUnknownCommand(buffer, command)` | `Maybe<Span<u8>>` | Writes a conventional unknown-command error. |
|
||||
| `std.cli.formatMissingOperand(buffer, operand)` | `Maybe<Span<u8>>` | Writes a conventional missing-operand error. |
|
||||
| `std.cli.formatInvalidOption(buffer, option)` | `Maybe<Span<u8>>` | Writes a conventional invalid-option error. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Table-driven command schemas.
|
||||
- Bool, signed integer, and `usize` option shortcuts; compose `optionValue` with `std.parse`.
|
||||
- Process exit from inside `std.cli`; use the returned exit-code constants with host process handling.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
if std.cli.needsHelp() {
|
||||
var command_storage: [96]u8 = [0_u8; 96]
|
||||
let commands: Maybe<Span<u8>> = std.cli.formatHelpRow(command_storage, "hello [name]", "print a greeting")
|
||||
if commands.has {
|
||||
var help_storage: [256]u8 = [0_u8; 256]
|
||||
let help: Maybe<Span<u8>> = std.cli.formatHelp(help_storage, "zero-test [command]", "Small hosted CLI.")
|
||||
if help.has {
|
||||
check world.out.write(help.value)
|
||||
check world.out.write("\nCommands:\n")
|
||||
check world.out.write(commands.value)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
let command: String = std.cli.commandOr("")
|
||||
if std.mem.eql(command, "hello") {
|
||||
let name: String = std.cli.argOr(2, "world")
|
||||
check world.out.write("hello ")
|
||||
check world.out.write(name)
|
||||
check world.out.write("\n")
|
||||
return
|
||||
}
|
||||
var error_storage: [64]u8 = [0_u8; 64]
|
||||
let error: Maybe<Span<u8>> = std.cli.formatUnknownCommand(error_storage, command)
|
||||
if error.has {
|
||||
check world.err.write(error.value)
|
||||
check world.err.write("\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.cli` is a thin, hosted layer over `std.args`. It keeps subcommand, fallback,
|
||||
typed argument, help row, and usage error patterns regular without hiding
|
||||
process arguments behind a global parser or allocating command tables. For
|
||||
custom help layouts, compose `formatHelp`, `formatSection`, `formatHelpRow`,
|
||||
`formatHelpRowWithWidth`, and `formatHelpRowCustom` rather than relying on a
|
||||
global formatter state.
|
||||
@@ -0,0 +1,82 @@
|
||||
## When To Use std.codec
|
||||
|
||||
In Zerolang, use `std.codec` for byte encodings: endian integer reads/writes,
|
||||
varints, base32, base64, hex, and checksums over caller-owned storage.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.codec.crc32(bytes)` | `u32` | Computes CRC-32 for a string-backed byte input. |
|
||||
| `std.codec.crc32Bytes(bytes)` | `u32` | Computes CRC-32 for a span or mutable span without allocation. |
|
||||
| `std.codec.encodedVarintLen(value)` | `usize` | Returns the byte length of an unsigned varint encoding. |
|
||||
| `std.codec.encodedVarintLen64(value)` | `usize` | Returns the byte length of an unsigned 64-bit varint encoding. |
|
||||
| `std.codec.encodedSignedVarintLen(value)` / `std.codec.encodedSignedVarintLen64(value)` | `usize` | Returns the byte length of a ZigZag signed varint encoding. |
|
||||
| `std.codec.varintEncode(buffer, value)` | `Maybe<Span<u8>>` | Writes an unsigned varint into caller storage. |
|
||||
| `std.codec.varintDecode(bytes)` | `Maybe<u32>` | Decodes a bounded unsigned varint. |
|
||||
| `std.codec.varintEncode64(buffer, value)` / `std.codec.varintDecode64(bytes)` | `Maybe<Span<u8>>` / `Maybe<u64>` | Writes and reads bounded unsigned 64-bit varints. |
|
||||
| `std.codec.signedVarintEncode(buffer, value)` / `std.codec.signedVarintDecode(bytes)` | `Maybe<Span<u8>>` / `Maybe<i32>` | Writes and reads ZigZag signed 32-bit varints. |
|
||||
| `std.codec.signedVarintEncode64(buffer, value)` / `std.codec.signedVarintDecode64(bytes)` | `Maybe<Span<u8>>` / `Maybe<i64>` | Writes and reads ZigZag signed 64-bit varints. |
|
||||
| `std.codec.readU16Le(bytes)` | `Maybe<u16>` | Bounds-checked little-endian read from a byte span. |
|
||||
| `std.codec.readU16Be(bytes)` | `Maybe<u16>` | Bounds-checked big-endian read from a byte span. |
|
||||
| `std.codec.readU32Le(bytes)` | `Maybe<u32>` | Bounds-checked little-endian read from a byte span. |
|
||||
| `std.codec.readU32Be(bytes)` | `Maybe<u32>` | Bounds-checked big-endian read from a byte span. |
|
||||
| `std.codec.readU64Le(bytes)` | `Maybe<u64>` | Bounds-checked little-endian read from a byte span. |
|
||||
| `std.codec.readU64Be(bytes)` | `Maybe<u64>` | Bounds-checked big-endian read from a byte span. |
|
||||
| `std.codec.writeU16Le(buffer, value)` | `Maybe<Span<u8>>` | Writes little-endian bytes into caller storage. |
|
||||
| `std.codec.writeU16Be(buffer, value)` | `Maybe<Span<u8>>` | Writes big-endian bytes into caller storage. |
|
||||
| `std.codec.writeU32Le(buffer, value)` | `Maybe<Span<u8>>` | Writes little-endian bytes into caller storage. |
|
||||
| `std.codec.writeU32Be(buffer, value)` | `Maybe<Span<u8>>` | Writes big-endian bytes into caller storage. |
|
||||
| `std.codec.writeU64Le(buffer, value)` | `Maybe<Span<u8>>` | Writes little-endian `u64` bytes into caller storage. |
|
||||
| `std.codec.writeU64Be(buffer, value)` | `Maybe<Span<u8>>` | Writes big-endian `u64` bytes into caller storage. |
|
||||
| `std.codec.base32EncodedLen(len)` / `std.codec.base32RawEncodedLen(len)` | `usize` | Returns padded or unpadded base32 encoded length. |
|
||||
| `std.codec.base32Encode(buffer, bytes)` / `std.codec.base32RawEncode(buffer, bytes)` | `Maybe<Span<u8>>` | Writes RFC 4648 base32 text into caller storage. |
|
||||
| `std.codec.base32DecodedLen(bytes)` / `std.codec.base32RawDecodedLen(bytes)` | `Maybe<usize>` | Validates padded or unpadded base32 text and returns decoded length. |
|
||||
| `std.codec.base32Decode(buffer, bytes)` / `std.codec.base32RawDecode(buffer, bytes)` | `Maybe<Span<u8>>` | Writes decoded base32 bytes into caller storage. |
|
||||
| `std.codec.base64EncodedLen(len)` | `usize` | Returns the encoded length for a base64 payload. |
|
||||
| `std.codec.base64Encode(buffer, bytes)` | `Maybe<String>` | Writes base64 text into caller storage. |
|
||||
| `std.codec.base64DecodedLen(bytes)` | `Maybe<usize>` | Validates padded base64 text and returns decoded length. |
|
||||
| `std.codec.base64Decode(buffer, bytes)` | `Maybe<Span<u8>>` | Writes decoded base64 bytes into caller storage. |
|
||||
| `std.codec.base64RawEncodedLen(len)` / `std.codec.base64RawEncode(buffer, bytes)` | `usize` / `Maybe<Span<u8>>` | Uses the standard base64 alphabet without padding. |
|
||||
| `std.codec.base64RawDecodedLen(bytes)` / `std.codec.base64RawDecode(buffer, bytes)` | `Maybe<usize>` / `Maybe<Span<u8>>` | Validates and decodes unpadded standard base64. |
|
||||
| `std.codec.base64UrlEncodedLen(len)` / `std.codec.base64UrlEncode(buffer, bytes)` | `usize` / `Maybe<Span<u8>>` | Uses the URL-safe base64 alphabet without padding. |
|
||||
| `std.codec.base64UrlDecodedLen(bytes)` / `std.codec.base64UrlDecode(buffer, bytes)` | `Maybe<usize>` / `Maybe<Span<u8>>` | Validates and decodes unpadded URL-safe base64. |
|
||||
| `std.codec.hexEncode(buffer, bytes)` | `Maybe<String>` | Writes lowercase hexadecimal text into caller storage. |
|
||||
| `std.codec.hexDecodedLen(bytes)` | `Maybe<usize>` | Validates hex text and returns decoded length. |
|
||||
| `std.codec.hexDecode(buffer, bytes)` | `Maybe<Span<u8>>` | Writes decoded hex bytes into caller storage. |
|
||||
| `std.codec.utf8Valid(bytes)` | `Bool` | Validates a byte span as UTF-8. |
|
||||
| `std.codec.urlEncode(buffer, text)` | `Maybe<String>` | Percent-encodes a string into caller storage. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Buffer-backed write APIs.
|
||||
- Streaming encoders and decoders.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
use std.codec
|
||||
|
||||
use std.mem
|
||||
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let len: usize = std.codec.encodedVarintLen(300)
|
||||
let checksum: u32 = std.codec.crc32("zero")
|
||||
let bytes: Span<u8> = std.mem.span("zero")
|
||||
let byte_checksum: u32 = std.codec.crc32Bytes(bytes)
|
||||
var encoded: [5]u8 = [0_u8; 5]
|
||||
let varint: Maybe<Span<u8>> = std.codec.varintEncode(encoded, 300_u32)
|
||||
var decoded: [4]u8 = [0_u8; 4]
|
||||
let text: Maybe<Span<u8>> = std.codec.base64Decode(decoded, "emVybw==")
|
||||
var base32_storage: [8]u8 = [0_u8; 8]
|
||||
let base32: Maybe<Span<u8>> = std.codec.base32Encode(base32_storage, "zero")
|
||||
if len == 2 && checksum == byte_checksum && varint.has && text.has && std.mem.eql(text.value, "zero") && base32.has && std.mem.eql(base32.value, "PJSXE3Y=") {
|
||||
check world.out.write("codec primitives ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
Codec helpers are byte-oriented and allocation-free. Decoders return
|
||||
`Maybe<T>` on malformed input or insufficient caller storage.
|
||||
@@ -0,0 +1,256 @@
|
||||
## When To Use std.collections
|
||||
|
||||
In Zerolang, use `std.collections` for fixed-capacity collection operations where the caller
|
||||
owns the storage and growth must be explicit.
|
||||
|
||||
Runnable today:
|
||||
|
||||
The collection helpers operate over caller-owned fixed arrays or `MutSpan<T>`
|
||||
storage plus an explicit live length. They do not allocate, grow, or retain
|
||||
hidden state. Generic helpers currently support the same non-owned scalar item
|
||||
types as the generic `std.mem` item helpers: `Bool`, `u8`, `u16`, `usize`,
|
||||
`i32`, `u32`, `i64`, and `u64`.
|
||||
|
||||
Use `FixedSet<T>`, `FixedDeque<T>`, `FixedRingBuffer<T>`, or
|
||||
`FixedMap<K, V>` when it is clearer to carry storage and live length as one
|
||||
value. These resource values still borrow caller-owned mutable storage;
|
||||
inserting, removing, clearing, or truncating values never allocates.
|
||||
|
||||
For byte collections, storage can come from a fixed array or from an explicit
|
||||
allocator. Use `std.mem.allocBytes(alloc, capacity)` to request a
|
||||
`MutSpan<u8>`, then pass that mutable span to `FixedSet<u8>`,
|
||||
`FixedDeque<u8>`, or `FixedMap<u8, u8>`. The wrapper does not own the storage;
|
||||
the allocator-backed span must remain live for as long as the collection value
|
||||
is used.
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.collections.push(items, len, value)` | `usize` | Writes `value` at `len` when capacity remains and returns the next length. Returns the unchanged length on overflow. |
|
||||
| `std.collections.append(items, len, values)` | `usize` | Copies all non-overlapping `values` into `items` at `len` when the full append fits. Returns the unchanged length on overflow or invalid length. |
|
||||
| `std.collections.clear(items, len)` | `usize` | Returns `0` as the next live length. Storage contents are left unchanged. |
|
||||
| `std.collections.truncate(items, len, newLen)` | `usize` | Returns the smaller of the current live length and `newLen`, after clamping invalid `len` to storage capacity. Storage contents are left unchanged. |
|
||||
| `std.collections.first(items, len)` | `Maybe<T>` | Returns the first live item, or `null` when the live prefix is empty. |
|
||||
| `std.collections.last(items, len)` | `Maybe<T>` | Returns the last live item, or `null` when the live prefix is empty. |
|
||||
| `std.collections.pop(items, len)` | `usize` | Returns the next live length after removing the last item. Returns `0` when empty. Storage contents are left unchanged. |
|
||||
| `std.collections.dequePushBack(items, len, value)` | `usize` | Writes `value` at the back when capacity remains and returns the next length. |
|
||||
| `std.collections.dequePushFront(items, len, value)` | `usize` | Inserts `value` at the front by shifting the live prefix right. Returns the unchanged length when full or invalid. |
|
||||
| `std.collections.dequeFront(items, len)` | `Maybe<T>` | Returns the first live deque item, or `null` when empty. |
|
||||
| `std.collections.dequeBack(items, len)` | `Maybe<T>` | Returns the last live deque item, or `null` when empty. |
|
||||
| `std.collections.dequePopBack(items, len)` | `usize` | Returns the next live length after removing the back item. Storage contents are left unchanged. |
|
||||
| `std.collections.dequePopFront(items, len)` | `usize` | Removes the front item by shifting the live suffix left and returns the next length. Returns the unchanged length when empty or invalid. |
|
||||
| `std.collections.fixedDeque(items, len)` | `FixedDeque<T>` | Builds a fixed-capacity deque value over caller-owned mutable storage and clamps the initial live length to capacity. |
|
||||
| `std.collections.fixedDequeBack(deque)` | `Maybe<T>` | Returns the last live deque item, or `null` when empty. |
|
||||
| `std.collections.fixedDequeClear(deque)` | `usize` | Clears a `FixedDeque<T>` and returns `0` as the next live length. |
|
||||
| `std.collections.fixedDequeFront(deque)` | `Maybe<T>` | Returns the first live deque item, or `null` when empty. |
|
||||
| `std.collections.fixedDequeIsFull(deque)` | `Bool` | Reports whether a `FixedDeque<T>` has no remaining capacity. |
|
||||
| `std.collections.fixedDequeLen(deque)` | `usize` | Returns the current live length of a `FixedDeque<T>`. |
|
||||
| `std.collections.fixedDequePopBack(deque)` | `Maybe<T>` | Removes and returns the back item when present. |
|
||||
| `std.collections.fixedDequePopFront(deque)` | `Maybe<T>` | Removes and returns the front item when present, shifting the live suffix left. |
|
||||
| `std.collections.fixedDequePushBack(deque, value)` | `Bool` | Appends `value` at the back when capacity remains. Returns whether the live deque changed. |
|
||||
| `std.collections.fixedDequePushFront(deque, value)` | `Bool` | Inserts `value` at the front when capacity remains. Returns whether the live deque changed. |
|
||||
| `std.collections.fixedDequeRemaining(deque)` | `usize` | Returns remaining storage capacity for a `FixedDeque<T>`. |
|
||||
| `std.collections.fixedDequeTruncate(deque, newLen)` | `usize` | Updates a `FixedDeque<T>` live prefix to the smaller of the current live length and `newLen`, clamped to storage. |
|
||||
| `std.collections.fixedDequeView(deque)` | `Span<T>` | Returns a read-only prefix view over live `FixedDeque<T>` items. |
|
||||
| `std.collections.fixedRingBuffer(items, head, len)` | `FixedRingBuffer<T>` | Builds a fixed-capacity ring buffer over caller-owned mutable storage, normalizing `head` and clamping `len` to capacity. |
|
||||
| `std.collections.fixedRingBufferBack(ring)` | `Maybe<T>` | Returns the last live logical item, or `null` when empty. |
|
||||
| `std.collections.fixedRingBufferCapacity(ring)` | `usize` | Returns the storage capacity. |
|
||||
| `std.collections.fixedRingBufferClear(ring)` | `usize` | Clears a `FixedRingBuffer<T>`, resets its head to `0`, and returns `0` as the next live length. |
|
||||
| `std.collections.fixedRingBufferFront(ring)` | `Maybe<T>` | Returns the first live logical item, or `null` when empty. |
|
||||
| `std.collections.fixedRingBufferGet(ring, index)` | `Maybe<T>` | Reads a logical index through wrap-around storage, or returns `null` outside the live length. |
|
||||
| `std.collections.fixedRingBufferIsFull(ring)` | `Bool` | Reports whether a `FixedRingBuffer<T>` has no remaining capacity. |
|
||||
| `std.collections.fixedRingBufferLen(ring)` | `usize` | Returns the current live length. |
|
||||
| `std.collections.fixedRingBufferPopBack(ring)` | `Maybe<T>` | Removes and returns the back logical item when present. |
|
||||
| `std.collections.fixedRingBufferPopFront(ring)` | `Maybe<T>` | Removes and returns the front logical item when present, advancing the stored head. |
|
||||
| `std.collections.fixedRingBufferPushBack(ring, value)` | `Bool` | Appends `value` at the logical back when capacity remains. |
|
||||
| `std.collections.fixedRingBufferPushFront(ring, value)` | `Bool` | Inserts `value` at the logical front when capacity remains. |
|
||||
| `std.collections.fixedRingBufferRemaining(ring)` | `usize` | Returns remaining storage capacity. |
|
||||
| `std.collections.fixedRingBufferTruncate(ring, newLen)` | `usize` | Updates the live logical prefix to the smaller of the current live length and `newLen`, clamped to storage. |
|
||||
| `std.collections.fill(items, len, value)` | `Bool` | Writes `value` across the live prefix. Returns `false` for invalid length. |
|
||||
| `std.collections.insertAt(items, len, index, value)` | `usize` | Inserts `value` at `index` by shifting the live suffix right. Returns the unchanged length when full or invalid. |
|
||||
| `std.collections.replaceAt(items, len, index, value)` | `Bool` | Replaces the live item at `index`. Returns `false` for invalid length or index. |
|
||||
| `std.collections.swapAt(items, len, left, right)` | `Bool` | Swaps two live items. Returns `false` for invalid length or index. |
|
||||
| `std.collections.insertUnique(items, len, value)` | `usize` | Treats the live prefix as a fixed-capacity set. Appends `value` only when absent and capacity remains. |
|
||||
| `std.collections.setClear(items, len)` | `usize` | Returns `0` as the next live set length. Storage contents are left unchanged. |
|
||||
| `std.collections.setContains(items, len, value)` | `Bool` | Reports whether the live prefix contains `value` as a fixed-capacity set. |
|
||||
| `std.collections.setInsert(items, len, value)` | `usize` | Appends `value` only when absent and capacity remains. |
|
||||
| `std.collections.setRemaining(items, len)` | `usize` | Returns remaining fixed-capacity set storage. Returns `0` when `len` is at or past capacity. |
|
||||
| `std.collections.setIsFull(items, len)` | `Bool` | Reports whether the fixed-capacity set has no remaining storage. |
|
||||
| `std.collections.setRemove(items, len, value)` | `usize` | Removes the first matching set value with swap-remove and returns the next length. |
|
||||
| `std.collections.setTruncate(items, len, newLen)` | `usize` | Returns the smaller of the current live set length and `newLen`, after clamping invalid `len` to storage capacity. |
|
||||
| `std.collections.setView(items, len)` | `Span<T>` | Returns a clamped read-only prefix view over the live fixed-capacity set items. |
|
||||
| `std.collections.fixedSet(items, len)` | `FixedSet<T>` | Builds a fixed-capacity set value over caller-owned mutable storage and clamps the initial live length to capacity. |
|
||||
| `std.collections.fixedSetClear(set)` | `usize` | Clears a `FixedSet<T>` and returns `0` as the next live length. |
|
||||
| `std.collections.fixedSetContains(set, value)` | `Bool` | Reports whether a `FixedSet<T>` contains `value` in its live prefix. |
|
||||
| `std.collections.fixedSetInsert(set, value)` | `Bool` | Inserts `value` when absent and capacity remains. Returns whether the live set changed. |
|
||||
| `std.collections.fixedSetIsFull(set)` | `Bool` | Reports whether a `FixedSet<T>` has no remaining capacity. |
|
||||
| `std.collections.fixedSetLen(set)` | `usize` | Returns the current live length of a `FixedSet<T>`. |
|
||||
| `std.collections.fixedSetRemaining(set)` | `usize` | Returns remaining storage capacity for a `FixedSet<T>`. |
|
||||
| `std.collections.fixedSetRemove(set, value)` | `Bool` | Removes `value` with swap-remove when present. Returns whether the live set changed. |
|
||||
| `std.collections.fixedSetTruncate(set, newLen)` | `usize` | Updates a `FixedSet<T>` live prefix to the smaller of the current live length and `newLen`, clamped to storage. |
|
||||
| `std.collections.fixedSetView(set)` | `Span<T>` | Returns a read-only prefix view over live `FixedSet<T>` items. |
|
||||
| `std.collections.fixedMap(keys, values, len)` | `FixedMap<K, V>` | Builds a fixed-capacity map value over caller-owned mutable key/value storage and clamps the initial live length to the shorter storage. |
|
||||
| `std.collections.fixedMapClear(map)` | `usize` | Clears a `FixedMap<K, V>` and returns `0` as the next live length. |
|
||||
| `std.collections.fixedMapContains(map, key)` | `Bool` | Reports whether a `FixedMap<K, V>` contains `key` in its live key prefix. |
|
||||
| `std.collections.fixedMapGet(map, key)` | `Maybe<V>` | Returns the value for `key` when present. |
|
||||
| `std.collections.fixedMapIndex(map, key)` | `usize` | Searches live keys and returns the matching index, or the live length when absent. |
|
||||
| `std.collections.fixedMapIsFull(map)` | `Bool` | Reports whether a `FixedMap<K, V>` has no remaining capacity in the shorter storage. |
|
||||
| `std.collections.fixedMapKeys(map)` | `Span<K>` | Returns a read-only prefix view over live `FixedMap<K, V>` keys. |
|
||||
| `std.collections.fixedMapLen(map)` | `usize` | Returns the current live length of a `FixedMap<K, V>`. |
|
||||
| `std.collections.fixedMapPut(map, key, value)` | `Bool` | Updates an existing key's value or appends a key/value pair when capacity remains. Returns whether the key exists afterward. |
|
||||
| `std.collections.fixedMapRemaining(map)` | `usize` | Returns remaining storage capacity in the shorter key/value storage. |
|
||||
| `std.collections.fixedMapRemove(map, key)` | `Bool` | Removes a key/value pair with swap-remove when present. Returns whether the live map changed. |
|
||||
| `std.collections.fixedMapTruncate(map, newLen)` | `usize` | Updates the live prefix to the smaller of the current live length and `newLen`, clamped to storage. |
|
||||
| `std.collections.fixedMapValues(map)` | `Span<V>` | Returns a read-only prefix view over live `FixedMap<K, V>` values. |
|
||||
| `std.collections.mapClear(keys, values, len)` | `usize` | Returns `0` as the next live map length. Key/value storage contents are left unchanged. |
|
||||
| `std.collections.mapContains(keys, len, key)` | `Bool` | Reports whether the live key prefix contains `key`. |
|
||||
| `std.collections.mapIndex(keys, len, key)` | `usize` | Searches the live key prefix and returns the matching index. Returns the live length when absent. |
|
||||
| `std.collections.mapIsFull(keys, values, len)` | `Bool` | Reports whether the shorter of key/value storage has no remaining capacity. |
|
||||
| `std.collections.mapKeys(keys, len)` | `Span<K>` | Returns a clamped read-only prefix view over the live map keys. |
|
||||
| `std.collections.mapGet(keys, values, len, key)` | `Maybe<V>` | Treats parallel key/value storage as a fixed-capacity map and returns the value for `key` when present. |
|
||||
| `std.collections.mapPut(keys, values, len, key, value)` | `usize` | Updates an existing key's value or appends a key/value pair when capacity remains. Returns the unchanged length on overflow or invalid length. |
|
||||
| `std.collections.mapRemaining(keys, values, len)` | `usize` | Returns remaining capacity in the shorter of key/value storage. |
|
||||
| `std.collections.mapRemove(keys, values, len, key)` | `usize` | Removes the first matching key/value pair with swap-remove and returns the next length. Returns the unchanged length when absent or invalid. |
|
||||
| `std.collections.mapTruncate(keys, values, len, newLen)` | `usize` | Returns the smaller of the live map length and `newLen`, clamped to key and value storage. |
|
||||
| `std.collections.mapValues(keys, values, len)` | `Span<V>` | Returns a clamped read-only prefix view over live map values, bounded by both key and value storage. |
|
||||
| `std.collections.view(items, len)` | `Span<T>` | Returns a clamped read-only prefix view over the live collection items. |
|
||||
| `std.collections.remaining(items, len)` | `usize` | Returns remaining capacity after the live prefix. Returns `0` when `len` is at or past capacity. |
|
||||
| `std.collections.isFull(items, len)` | `Bool` | Reports whether no capacity remains. Invalid lengths at or past capacity count as full. |
|
||||
| `std.collections.contains(items, len, needle)` | `Bool` | Searches only the live prefix for `needle`. |
|
||||
| `std.collections.count(items, len, needle)` | `usize` | Counts matching values in the live prefix. |
|
||||
| `std.collections.removeAt(items, len, index)` | `usize` | Removes `index` by shifting the live suffix left and returns the next length. Returns the unchanged length for invalid length or index. |
|
||||
| `std.collections.removeValue(items, len, value)` | `usize` | Removes the first matching live value with swap-remove and returns the next length. Returns the unchanged length when absent. |
|
||||
| `std.collections.removeSwap(items, len, index)` | `usize` | Replaces `index` with the last live item and returns the next length. Returns the unchanged length for invalid length or index. |
|
||||
| `std.collections.reverse(items, len)` | `Bool` | Reverses the live prefix in place. Returns `false` for invalid length. |
|
||||
| `std.collections.rotateLeft(items, len, count)` | `Bool` | Rotates the live prefix left by `count`. Returns `false` for invalid length. |
|
||||
| `std.collections.rotateRight(items, len, count)` | `Bool` | Rotates the live prefix right by `count`. Returns `false` for invalid length. |
|
||||
| `std.collections.moveToFront(items, len, index)` | `usize` | Moves the item at `index` to the front by shifting the live prefix. Returns the unchanged length for invalid length or index. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var values: [5]i32 = [0, 0, 0, 0, 0]
|
||||
let extra: [2]i32 = [4, 1]
|
||||
var len: usize = 0
|
||||
len = std.collections.push(values, len, 3)
|
||||
len = std.collections.push(values, len, 1)
|
||||
len = std.collections.setInsert(values, len, 3)
|
||||
len = std.collections.setInsert(values, len, 2)
|
||||
let has_three: Bool = std.collections.setContains(values, len, 3)
|
||||
let set_live: Span<i32> = std.collections.setView(values, len)
|
||||
let set_remaining: usize = std.collections.setRemaining(values, len)
|
||||
len = std.collections.setRemove(values, len, 2)
|
||||
let set_truncated: usize = std.collections.setTruncate(values, len, 1)
|
||||
var fixed_storage: [4]i32 = [1, 2, 0, 0]
|
||||
var fixed_set: FixedSet<i32> = std.collections.fixedSet(fixed_storage, 2_usize)
|
||||
let fixed_inserted: Bool = std.collections.fixedSetInsert(&mut fixed_set, 3)
|
||||
let fixed_removed: Bool = std.collections.fixedSetRemove(&mut fixed_set, 1)
|
||||
let fixed_live: Span<i32> = std.collections.fixedSetView(&fixed_set)
|
||||
let fixed_remaining: usize = std.collections.fixedSetRemaining(&fixed_set)
|
||||
let fixed_len: usize = std.collections.fixedSetLen(&fixed_set)
|
||||
let fixed_truncated: usize = std.collections.fixedSetTruncate(&mut fixed_set, 1_usize)
|
||||
var fixed_keys: [3]u8 = [1_u8, 2_u8, 0_u8]
|
||||
var fixed_scores: [3]u16 = [10_u16, 20_u16, 0_u16]
|
||||
var fixed_map: FixedMap<u8, u16> = std.collections.fixedMap(fixed_keys, fixed_scores, 2_usize)
|
||||
let fixed_map_added: Bool = std.collections.fixedMapPut(&mut fixed_map, 3_u8, 30_u16)
|
||||
let fixed_map_updated: Bool = std.collections.fixedMapPut(&mut fixed_map, 2_u8, 25_u16)
|
||||
let fixed_map_score: Maybe<u16> = std.collections.fixedMapGet(&fixed_map, 2_u8)
|
||||
let fixed_map_keys: Span<u8> = std.collections.fixedMapKeys(&fixed_map)
|
||||
let fixed_map_values: Span<u16> = std.collections.fixedMapValues(&fixed_map)
|
||||
let fixed_map_index: usize = std.collections.fixedMapIndex(&fixed_map, 2_u8)
|
||||
let fixed_map_full: Bool = std.collections.fixedMapIsFull(&fixed_map)
|
||||
let fixed_map_removed: Bool = std.collections.fixedMapRemove(&mut fixed_map, 1_u8)
|
||||
let inserted_len: usize = std.collections.insertAt(values, len, 1_usize, 2)
|
||||
let replaced: Bool = std.collections.replaceAt(values, inserted_len, 1_usize, 6)
|
||||
let swapped: Bool = std.collections.swapAt(values, inserted_len, 0_usize, 1_usize)
|
||||
len = std.collections.removeAt(values, inserted_len, 1_usize)
|
||||
let first: Maybe<i32> = std.collections.first(values, len)
|
||||
let last: Maybe<i32> = std.collections.last(values, len)
|
||||
let popped_len: usize = std.collections.pop(values, len)
|
||||
len = std.collections.truncate(values, len, 2)
|
||||
len = std.collections.append(values, len, extra)
|
||||
let live: Span<i32> = std.collections.view(values, len)
|
||||
var deque_values: [4]i32 = [0, 0, 0, 0]
|
||||
var deque_len: usize = 0
|
||||
deque_len = std.collections.dequePushBack(deque_values, deque_len, 2)
|
||||
deque_len = std.collections.dequePushFront(deque_values, deque_len, 1)
|
||||
deque_len = std.collections.dequePushBack(deque_values, deque_len, 3)
|
||||
let deque_front: Maybe<i32> = std.collections.dequeFront(deque_values, deque_len)
|
||||
let deque_back: Maybe<i32> = std.collections.dequeBack(deque_values, deque_len)
|
||||
let deque_after_pop_back: usize = std.collections.dequePopBack(deque_values, deque_len)
|
||||
deque_len = std.collections.dequePopFront(deque_values, deque_after_pop_back)
|
||||
var fixed_deque_values: [4]i32 = [0, 0, 0, 0]
|
||||
var fixed_deque: FixedDeque<i32> = std.collections.fixedDeque(fixed_deque_values, 0_usize)
|
||||
let fixed_deque_pushed: Bool = std.collections.fixedDequePushBack(&mut fixed_deque, 2)
|
||||
let fixed_deque_front_pushed: Bool = std.collections.fixedDequePushFront(&mut fixed_deque, 1)
|
||||
let fixed_deque_back: Maybe<i32> = std.collections.fixedDequeBack(&fixed_deque)
|
||||
let fixed_deque_front: Maybe<i32> = std.collections.fixedDequeFront(&fixed_deque)
|
||||
let fixed_deque_live: Span<i32> = std.collections.fixedDequeView(&fixed_deque)
|
||||
let fixed_deque_removed: Maybe<i32> = std.collections.fixedDequePopFront(&mut fixed_deque)
|
||||
var fixed_ring_values: [4]i32 = [0, 0, 0, 0]
|
||||
var fixed_ring: FixedRingBuffer<i32> = std.collections.fixedRingBuffer(fixed_ring_values, 0_usize, 0_usize)
|
||||
let fixed_ring_back_pushed: Bool = std.collections.fixedRingBufferPushBack(&mut fixed_ring, 2)
|
||||
let fixed_ring_front_pushed: Bool = std.collections.fixedRingBufferPushFront(&mut fixed_ring, 1)
|
||||
let fixed_ring_middle: Maybe<i32> = std.collections.fixedRingBufferGet(&fixed_ring, 1_usize)
|
||||
let fixed_ring_front: Maybe<i32> = std.collections.fixedRingBufferPopFront(&mut fixed_ring)
|
||||
let fixed_ring_wrapped: Bool = std.collections.fixedRingBufferPushBack(&mut fixed_ring, 3)
|
||||
var transform: [4]i32 = [1, 2, 3, 4]
|
||||
let reversed: Bool = std.collections.reverse(transform, 4_usize)
|
||||
let filled: Bool = std.collections.fill(transform, 2_usize, 9)
|
||||
let rotated_left: Bool = std.collections.rotateLeft(transform, 4_usize, 1_usize)
|
||||
let rotated_right: Bool = std.collections.rotateRight(transform, 4_usize, 2_usize)
|
||||
var keys: [3]u8 = [1_u8, 2_u8, 3_u8]
|
||||
var scores: [3]u16 = [10_u16, 20_u16, 30_u16]
|
||||
var map_len: usize = 2
|
||||
map_len = std.collections.mapPut(keys, scores, map_len, 3_u8, 30_u16)
|
||||
map_len = std.collections.mapPut(keys, scores, map_len, 2_u8, 25_u16)
|
||||
let has_score: Bool = std.collections.mapContains(keys, map_len, 2_u8)
|
||||
let score: Maybe<u16> = std.collections.mapGet(keys, scores, map_len, 2_u8)
|
||||
let live_keys: Span<u8> = std.collections.mapKeys(keys, map_len)
|
||||
let live_scores: Span<u16> = std.collections.mapValues(keys, scores, map_len)
|
||||
let map_remaining: usize = std.collections.mapRemaining(keys, scores, map_len)
|
||||
let map_full: Bool = std.collections.mapIsFull(keys, scores, map_len)
|
||||
map_len = std.collections.mapRemove(keys, scores, map_len, 2_u8)
|
||||
let removed_index: usize = std.collections.mapIndex(keys, map_len, 2_u8)
|
||||
if len == 4 && inserted_len == 3 && replaced && swapped && std.collections.clear(values, len) == 0 && std.collections.setClear(values, len) == 0 && first.has && first.value == 6 && last.has && last.value == 1 && popped_len == 1 && std.collections.remaining(values, len) == 1 && !std.collections.isFull(values, len) && has_three && std.mem.len(set_live) == 3 && set_remaining == 2 && set_truncated == 1 && fixed_inserted && fixed_removed && std.mem.len(fixed_live) == 2 && fixed_remaining == 2 && fixed_len == 2 && fixed_truncated == 1 && std.collections.fixedSetClear(&mut fixed_set) == 0 && fixed_map_added && fixed_map_updated && fixed_map_score.has && fixed_map_score.value == 25_u16 && std.mem.len(fixed_map_keys) == 3 && std.mem.len(fixed_map_values) == 3 && fixed_map_index == 1 && fixed_map_full && fixed_map_removed && std.collections.fixedMapClear(&mut fixed_map) == 0 && std.collections.contains(values, len, 4) && std.collections.count(values, len, 1) == 2 && std.mem.len(live) == 4 && deque_front.has && deque_front.value == 1 && deque_back.has && deque_back.value == 3 && deque_after_pop_back == 2 && deque_len == 1 && deque_values[0] == 2 && fixed_deque_pushed && fixed_deque_front_pushed && fixed_deque_back.has && fixed_deque_back.value == 2 && fixed_deque_front.has && fixed_deque_front.value == 1 && std.mem.len(fixed_deque_live) == 2 && fixed_deque_removed.has && fixed_deque_removed.value == 1 && fixed_ring_back_pushed && fixed_ring_front_pushed && fixed_ring_middle.has && fixed_ring_middle.value == 2 && fixed_ring_front.has && fixed_ring_front.value == 1 && fixed_ring_wrapped && reversed && filled && rotated_left && rotated_right && transform[0] == 1 && transform[1] == 9 && transform[2] == 9 && transform[3] == 2 && has_score && score.has && score.value == 25_u16 && std.mem.len(live_keys) == 3 && std.mem.len(live_scores) == 3 && map_remaining == 0 && map_full && std.collections.mapClear(keys, scores, map_len) == 0 && std.collections.mapTruncate(keys, scores, map_len, 2_usize) == 2 && map_len == 2 && removed_index == 2 {
|
||||
check world.out.write("collections ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Allocator-Backed Storage
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var key_storage: [4]u8 = [0_u8; 4]
|
||||
var value_storage: [4]u8 = [0_u8; 4]
|
||||
var key_alloc: FixedBufAlloc = std.mem.fixedBufAlloc(key_storage)
|
||||
var value_alloc: FixedBufAlloc = std.mem.fixedBufAlloc(value_storage)
|
||||
let keys_maybe: Maybe<MutSpan<u8>> = std.mem.allocBytes(key_alloc, 4_usize)
|
||||
let values_maybe: Maybe<MutSpan<u8>> = std.mem.allocBytes(value_alloc, 4_usize)
|
||||
if keys_maybe.has && values_maybe.has {
|
||||
var map: FixedMap<u8, u8> = std.collections.fixedMap(keys_maybe.value, values_maybe.value, 0_usize)
|
||||
let stored: Bool = std.collections.fixedMapPut(&mut map, 7_u8, 42_u8)
|
||||
let value: Maybe<u8> = std.collections.fixedMapGet(&map, 7_u8)
|
||||
if stored && value.has && value.value == 42_u8 {
|
||||
check world.out.write("allocator-backed map ok\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: writes to caller-provided mutable storage.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: capacity and index failures are value-level. Helpers return the
|
||||
unchanged length instead of growing or raising.
|
||||
|
||||
`append` rejects source spans that the checker can prove overlap the destination
|
||||
storage. Use separate storage when copying a live prefix back into the same
|
||||
collection.
|
||||
|
||||
Ownership: helpers reject owned item elements; move or transfer owned values
|
||||
explicitly.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,58 @@
|
||||
## When To Use std.crypto
|
||||
|
||||
In Zerolang, use `std.crypto` for small hashes, SHA-256 digests, keyed hashes,
|
||||
constant-time equality, and target entropy helpers with explicit capability
|
||||
boundaries.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.crypto.hash32(bytes)` | `u32` | Computes the current 32-bit hash helper over bytes. |
|
||||
| `std.crypto.hmac32(key, bytes)` | `u32` | Computes the current keyed 32-bit helper over bytes. |
|
||||
| `std.crypto.constantTimeEql(a, b)` | `Bool` | Compares byte spans without data-dependent early exit. |
|
||||
| `std.crypto.secureRandomU32()` | `u32` | Reads target entropy where the target provides it. |
|
||||
| `std.crypto.fixedHex32(buffer, value)` | `Maybe<Span<u8>>` | Writes an 8-byte lowercase hex value into caller storage. |
|
||||
| `std.crypto.hashHex32(buffer, bytes)` | `Maybe<Span<u8>>` | Writes the 32-bit hash as fixed-width lowercase hex. |
|
||||
| `std.crypto.hmacHex32(buffer, key, bytes)` | `Maybe<Span<u8>>` | Writes the keyed 32-bit helper as fixed-width lowercase hex. |
|
||||
| `std.crypto.stableId32(buffer, bytes)` | `Maybe<Span<u8>>` | Writes a deterministic 8-byte ID from input bytes. |
|
||||
| `std.crypto.randomId32(buffer)` | `Maybe<Span<u8>>` | Writes an 8-byte random ID from target entropy. |
|
||||
| `std.crypto.sha256(buffer, bytes)` | `Maybe<Span<u8>>` | Writes the 32-byte SHA-256 digest into caller storage. |
|
||||
| `std.crypto.sha256Hex(buffer, bytes)` | `Maybe<Span<u8>>` | Writes the SHA-256 digest as 64 lowercase hex bytes. |
|
||||
| `std.crypto.hmacSha256(buffer, key, bytes)` | `Maybe<Span<u8>>` | Writes the 32-byte HMAC-SHA256 digest into caller storage. |
|
||||
| `std.crypto.hmacSha256Hex(buffer, key, bytes)` | `Maybe<Span<u8>>` | Writes the HMAC-SHA256 digest as 64 lowercase hex bytes. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: codec, memory, or rand
|
||||
- allocation behavior: no allocation; text helpers write caller-provided buffers
|
||||
- target support: hash helpers are target-neutral; secure random requires a rand-capable target
|
||||
- error behavior: caller-buffer helpers return `null` when storage is too small
|
||||
- ownership notes: borrows caller-provided byte spans
|
||||
- example: `examples/std-platform.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let hash: u32 = std.crypto.hash32(std.mem.span("message"))
|
||||
let hmac: u32 = std.crypto.hmac32(std.mem.span("key"), std.mem.span("message"))
|
||||
var id_buf: [8]u8 = [0_u8; 8]
|
||||
var sha_buf: [64]u8 = [0_u8; 64]
|
||||
var hmac_buf: [64]u8 = [0_u8; 64]
|
||||
let id: Maybe<Span<u8>> = std.crypto.stableId32(id_buf, std.mem.span("message"))
|
||||
let sha: Maybe<Span<u8>> = std.crypto.sha256Hex(sha_buf, std.mem.span("abc"))
|
||||
let hmac_sha: Maybe<Span<u8>> = std.crypto.hmacSha256Hex(hmac_buf, std.mem.span("key"), std.mem.span("message"))
|
||||
if hash > 0 && hmac > 0 && id.has && sha.has && hmac_sha.has && std.crypto.constantTimeEql(std.mem.span("same"), std.mem.span("same")) {
|
||||
check world.out.write("crypto ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.crypto` is a small helper surface. The SHA-256 and HMAC-SHA256 helpers
|
||||
cover common digest, keyed digest, and fixture needs without allocation. The
|
||||
fixed-width ID helpers are useful for deterministic labels, cache keys,
|
||||
fixtures, and examples. The module is not a TLS stack, certificate store,
|
||||
password hashing API, or secret-management API.
|
||||
@@ -0,0 +1,53 @@
|
||||
## When To Use std.csv
|
||||
|
||||
In Zerolang, use `std.csv` for allocation-free CSV validation, record scanning,
|
||||
field decoding, and small fixed-arity CSV writers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.csv.valid(bytes)` | `Bool` | Validates bounded CSV input with quoted fields and CRLF or LF records. |
|
||||
| `std.csv.recordCount(bytes)` | `Maybe<usize>` | Counts valid records, returning null on malformed input. |
|
||||
| `std.csv.record(bytes, index)` | `Maybe<Span<u8>>` | Borrows one record slice by ordinal, excluding the line terminator. |
|
||||
| `std.csv.fieldCount(record)` | `Maybe<usize>` | Counts fields in one valid record. |
|
||||
| `std.csv.field(buffer, record, index)` | `Maybe<Span<u8>>` | Decodes one field into caller storage. |
|
||||
| `std.csv.encodedFieldLen(field)` | `usize` | Computes the bytes needed to write one CSV field. |
|
||||
| `std.csv.writeField(buffer, field)` | `Maybe<Span<u8>>` | Writes one CSV field with quotes when required. |
|
||||
| `std.csv.writeRecord2(buffer, left, right)` | `Maybe<Span<u8>>` | Writes a two-field record ending in `\n`. |
|
||||
| `std.csv.writeRecord3(buffer, first, second, third)` | `Maybe<Span<u8>>` | Writes a three-field record ending in `\n`. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: parse
|
||||
- allocation behavior: no allocation; decoded fields and writer output use caller storage
|
||||
- target support: target-neutral
|
||||
- error behavior: `Maybe` helpers return null on malformed input or insufficient storage
|
||||
- ownership notes: records borrow from the input; fields and writer output borrow from caller buffers
|
||||
- examples: `conformance/native/pass/std-csv.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let input: Span<u8> = "name,quote\nAda,\"a,b\"\n"
|
||||
let record: Maybe<Span<u8>> = std.csv.record(input, 1_usize)
|
||||
var field_buf: [16]u8 = [0_u8; 16]
|
||||
var quote: Maybe<Span<u8>> = null
|
||||
if record.has {
|
||||
quote = std.csv.field(field_buf, record.value, 1_usize)
|
||||
}
|
||||
if std.csv.valid(input) && quote.has && std.mem.eql(quote.value, "a,b") {
|
||||
check world.out.write("csv ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
CSV helpers follow the common RFC 4180 field rules: comma-separated fields,
|
||||
quoted fields, doubled quotes inside quoted fields, and CRLF or LF record
|
||||
separators. Quoted fields may contain newlines.
|
||||
|
||||
The writer surface is fixed-arity. Use `writeField` when a custom writer loop
|
||||
is needed.
|
||||
@@ -0,0 +1,47 @@
|
||||
## When To Use std.diag
|
||||
|
||||
In Zerolang, use `std.diag` to turn byte offsets into source locations and
|
||||
small diagnostic snippets without allocating.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.diag.line(bytes, offset)` | `usize` | Returns the 1-based line for a byte offset, clamping offsets past the end. |
|
||||
| `std.diag.column(bytes, offset)` | `usize` | Returns the 1-based byte column for a byte offset. |
|
||||
| `std.diag.lineStart(bytes, offset)` | `usize` | Returns the byte index where the containing line starts. |
|
||||
| `std.diag.lineEnd(bytes, offset)` | `usize` | Returns the byte index where the containing line ends, trimming a trailing CR before LF. |
|
||||
| `std.diag.lineText(bytes, offset)` | `Span<u8>` | Borrows the containing line without its newline. |
|
||||
| `std.diag.rangeLen(bytes, start, end)` | `usize` | Returns the clamped byte length for a half-open range. |
|
||||
| `std.diag.rangeText(bytes, start, end)` | `Span<u8>` | Borrows the clamped half-open byte range. |
|
||||
| `std.diag.formatLocation(buffer, path, line, column)` | `Maybe<Span<u8>>` | Writes `path:line:column` into caller storage. |
|
||||
| `std.diag.formatOffsetLocation(buffer, path, bytes, offset)` | `Maybe<Span<u8>>` | Computes line and column from an offset, then writes `path:line:column`. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: parse for source offset scanning; memory for caller-buffer formatting
|
||||
- allocation behavior: no allocation, except formatting writes into caller storage
|
||||
- target support: target-neutral
|
||||
- error behavior: formatting returns `null` when the buffer is too small
|
||||
- ownership notes: text helpers return borrowed views into the input bytes
|
||||
- example: `conformance/native/pass/std-diag.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let source: Span<u8> = "one\ntwo\nthree"
|
||||
var storage: [32]u8 = [0_u8; 32]
|
||||
let location: Maybe<Span<u8>> = std.diag.formatOffsetLocation(storage, "input.0", source, 5)
|
||||
if location.has && std.mem.eql(location.value, "input.0:2:2") && std.mem.eql(std.diag.lineText(source, 5), "two") {
|
||||
check world.out.write("diag ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
Offsets are byte offsets, not Unicode scalar indexes or terminal display
|
||||
columns. That keeps parser diagnostics deterministic and cheap across targets.
|
||||
Line and column numbers are 1-based for user-facing output. Range helpers use
|
||||
half-open byte ranges and clamp both ends to the input length.
|
||||
@@ -0,0 +1,52 @@
|
||||
## When To Use std.env
|
||||
|
||||
In Zerolang, use `std.env` for hosted environment variable lookup and simple typed
|
||||
configuration values.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.env.get(name)` | `Maybe<String>` | Returns a hosted process environment value when present. |
|
||||
| `std.env.has(name)` | `Bool` | Reports whether a hosted environment variable exists. |
|
||||
| `std.env.getOr(name, fallback)` | `String` | Returns the environment value or a caller-provided fallback. |
|
||||
| `std.env.equals(name, expected)` | `Bool` | Compares an environment value with expected text without exposing a missing value as success. |
|
||||
| `std.env.parseBool(name)` | `Maybe<Bool>` | Parses an environment value as `Bool`. |
|
||||
| `std.env.parseBoolOr(name, fallback)` | `Bool` | Parses a boolean environment value or returns a caller-provided fallback. |
|
||||
| `std.env.parseI32(name)` | `Maybe<i32>` | Parses an environment value as `i32`. |
|
||||
| `std.env.parseI32Or(name, fallback)` | `i32` | Parses an `i32` environment value or returns a caller-provided fallback. |
|
||||
| `std.env.parseU32(name)` | `Maybe<u32>` | Parses an environment value as `u32`. |
|
||||
| `std.env.parseU32Or(name, fallback)` | `u32` | Parses a `u32` environment value or returns a caller-provided fallback. |
|
||||
| `std.env.parseUsize(name)` | `Maybe<usize>` | Parses an environment value as `usize`. |
|
||||
| `std.env.parseUsizeOr(name, fallback)` | `usize` | Parses a `usize` environment value or returns a caller-provided fallback. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Dotenv/source composition.
|
||||
- Secret redaction metadata.
|
||||
- Rich diagnostics for missing keys, invalid values, and source precedence.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let mode: String = std.env.getOr("ZERO_MODE", "default")
|
||||
let verbose: Bool = std.env.parseBoolOr("ZERO_VERBOSE", false)
|
||||
let delta: i32 = std.env.parseI32Or("ZERO_DELTA", 0)
|
||||
let limit: u32 = std.env.parseU32Or("ZERO_LIMIT", 10)
|
||||
let workers: usize = std.env.parseUsizeOr("ZERO_WORKERS", 1)
|
||||
if std.env.equals("ZERO_MODE", "debug") && verbose && delta >= 0 && limit > 0_u32 && workers > 0 {
|
||||
check world.out.write(mode)
|
||||
check world.out.write("\n")
|
||||
} else {
|
||||
check world.out.write("default\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
Environment access is a hosted capability. Non-host targets reject `std.env`
|
||||
unless they explicitly provide an environment capability.
|
||||
|
||||
Diagnostics name the selected target context.
|
||||
@@ -0,0 +1,62 @@
|
||||
## When To Use std.fmt
|
||||
|
||||
In Zerolang, use `std.fmt` when a program needs to format booleans or integers into a
|
||||
caller-owned buffer instead of allocating text.
|
||||
|
||||
Runnable today:
|
||||
|
||||
Formatting helpers write into caller-provided `MutSpan<u8>` storage and return a
|
||||
borrowed prefix on success.
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.fmt.bool(buffer, value)` | `Maybe<Span<u8>>` | Writes `true` or `false`. |
|
||||
| `std.fmt.u32(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal unsigned 32-bit text. |
|
||||
| `std.fmt.u32Base(buffer, value, base)` | `Maybe<Span<u8>>` | Writes unsigned 32-bit text in base 2 through 36. |
|
||||
| `std.fmt.u64(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal unsigned 64-bit text. |
|
||||
| `std.fmt.u64Base(buffer, value, base)` | `Maybe<Span<u8>>` | Writes unsigned 64-bit text in base 2 through 36. |
|
||||
| `std.fmt.usize(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal `usize` text. |
|
||||
| `std.fmt.usizeBase(buffer, value, base)` | `Maybe<Span<u8>>` | Writes `usize` text in base 2 through 36. |
|
||||
| `std.fmt.i32(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal signed 32-bit text, including the minimum value. |
|
||||
| `std.fmt.i32Base(buffer, value, base)` | `Maybe<Span<u8>>` | Writes signed 32-bit text in base 2 through 36. |
|
||||
| `std.fmt.i32Sign(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal signed 32-bit text with an explicit `+` for non-negative values. |
|
||||
| `std.fmt.i64(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal signed 64-bit text, including the minimum value. |
|
||||
| `std.fmt.i64Base(buffer, value, base)` | `Maybe<Span<u8>>` | Writes signed 64-bit text in base 2 through 36. |
|
||||
| `std.fmt.i64Sign(buffer, value)` | `Maybe<Span<u8>>` | Writes decimal signed 64-bit text with an explicit `+` for non-negative values. |
|
||||
| `std.fmt.hexLowerU32(buffer, value)` | `Maybe<Span<u8>>` | Writes lowercase hexadecimal without a prefix. |
|
||||
| `std.fmt.padLeft(buffer, text, width, pad)` | `Maybe<Span<u8>>` | Left-pads `text` with `pad` until `width`, or copies `text` when already wide enough. |
|
||||
| `std.fmt.padRight(buffer, text, width, pad)` | `Maybe<Span<u8>>` | Right-pads `text` with `pad` until `width`, or copies `text` when already wide enough. |
|
||||
| `std.fmt.writeSpan(writer, text)` | `Bool` | Writes bytes into a `FixedWriter`. |
|
||||
| `std.fmt.writeBool(writer, value)` | `Bool` | Formats a boolean into a `FixedWriter`. |
|
||||
| `std.fmt.writeU32(writer, value)` / `std.fmt.writeU64(writer, value)` / `std.fmt.writeUsize(writer, value)` | `Bool` | Formats unsigned decimal text into a `FixedWriter`. |
|
||||
| `std.fmt.writeI32(writer, value)` / `std.fmt.writeI64(writer, value)` | `Bool` | Formats signed decimal text into a `FixedWriter`. |
|
||||
| `std.fmt.writeI32Sign(writer, value)` / `std.fmt.writeI64Sign(writer, value)` | `Bool` | Formats signed decimal text with an explicit `+` for non-negative values into a `FixedWriter`. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var number_buf: [12]u8 = [0_u8; 12]
|
||||
var big_buf: [20]u8 = [0_u8; 20]
|
||||
var hex_buf: [8]u8 = [0_u8; 8]
|
||||
var padded_buf: [6]u8 = [0_u8; 6]
|
||||
let number: Maybe<Span<u8>> = std.fmt.i32(number_buf, -42)
|
||||
let big: Maybe<Span<u8>> = std.fmt.u64(big_buf, 18446744073709551615_u64)
|
||||
let hex: Maybe<Span<u8>> = std.fmt.hexLowerU32(hex_buf, 48879_u32)
|
||||
let padded: Maybe<Span<u8>> = std.fmt.padLeft(padded_buf, "42", 5, 48_u8)
|
||||
var writer_storage: [24]u8 = [0_u8; 24]
|
||||
var writer: FixedWriter = std.io.fixedWriter(writer_storage, 0)
|
||||
let wrote: Bool = std.fmt.writeSpan(&mut writer, "n=") && std.fmt.writeI32(&mut writer, -42)
|
||||
if number.has && big.has && hex.has && padded.has && wrote && std.mem.eql(number.value, "-42") && std.mem.eql(big.value, "18446744073709551615") && std.mem.eql(hex.value, "beef") && std.mem.eql(padded.value, "00042") && std.mem.eql(std.io.fixedWriterView(&writer), "n=-42") {
|
||||
check world.out.write("fmt ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: writes to caller-provided mutable storage or an explicit fixed writer.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: returns `null` when the buffer is too small.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,82 @@
|
||||
## When To Use std.fs
|
||||
|
||||
In Zerolang, use `std.fs` for hosted file reads, writes, existence checks, copies, renames,
|
||||
and explicit file-resource cleanup.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.fs.read(path, buf)` | `usize` | Reads bytes from a hosted path into a caller-provided `MutSpan<u8>` buffer. |
|
||||
| `std.fs.write(path, bytes)` | `usize` | Writes bytes to a hosted path and returns the byte count. |
|
||||
| `std.fs.host()` | `Fs` | Creates the hosted filesystem capability. |
|
||||
| `std.fs.open(fs, path)` | `Maybe<owned<File>>` | Opens a file and returns `null` when unavailable. |
|
||||
| `std.fs.openOrRaise(fs, path)` | `owned<File>` | Opens a file or raises `raises [NotFound, TooLarge, Io]`. |
|
||||
| `std.fs.create(fs, path)` | `Maybe<owned<File>>` | Creates a file and returns `null` when unavailable. |
|
||||
| `std.fs.createOrRaise(fs, path)` | `owned<File>` | Creates a file or raises `raises [NotFound, TooLarge, Io]`. |
|
||||
| `std.fs.readOrRaise(&mut file, buf)` | `usize` | Reads into caller storage or raises. |
|
||||
| `std.fs.writeAll(&mut file, bytes)` | `Bool` | Writes bytes to an owned file handle. |
|
||||
| `std.fs.writeAllOrRaise(&mut file, bytes)` | `Void` | Writes all bytes or raises. |
|
||||
| `std.fs.fileLen(&mut file)` | `Maybe<usize>` | Reports file length when available. |
|
||||
| `std.fs.fileLenOrRaise(&mut file)` | `usize` | Reports the file length or raises. |
|
||||
| `std.fs.fileSize(fs, path)` | `Maybe<usize>` | Opens a hosted path through `fs` and reports file length when available. |
|
||||
| `std.fs.readAll(alloc, fs, path, limit)` | `Maybe<owned<ByteBuf>>` | Reads through an explicit allocator and size limit. |
|
||||
| `std.fs.readAllOrRaise(alloc, fs, path, limit)` | `owned<ByteBuf>` | Reads through an explicit allocator and size limit. |
|
||||
| `std.fs.readBytes(path, buf)` | `Maybe<usize>` | Fills caller storage and returns the total file size; a value above `len(buf)` means the buffer holds only the first `len(buf)` bytes. |
|
||||
| `std.fs.readBytesAt(path, offset, buf)` | `Maybe<usize>` | Fills caller storage starting at a byte offset and returns the total file size, so bounded buffers can process larger files in chunks. |
|
||||
| `std.fs.writeBytes(path, bytes)` | `Maybe<usize>` | Writes byte spans to a hosted path. |
|
||||
| `std.fs.appendBytes(path, bytes)` | `Maybe<usize>` | Appends byte spans to a hosted path, creating the file when missing. |
|
||||
| `std.fs.exists(path)` | `Bool` | Checks whether a hosted path exists. |
|
||||
| `std.fs.isFile(path)` | `Bool` | Checks whether a hosted path opens and reports a file length. |
|
||||
| `std.fs.isDir(path)` | `Bool` | Checks whether a hosted path is a directory. |
|
||||
| `std.fs.makeDir(path)` | `Bool` | Creates a hosted directory. |
|
||||
| `std.fs.ensureDir(path)` | `Bool` | Succeeds when a hosted directory already exists or can be created. |
|
||||
| `std.fs.removeDir(path)` | `Bool` | Removes a hosted directory. |
|
||||
| `std.fs.remove(path)` | `Bool` | Removes a hosted file path. |
|
||||
| `std.fs.rename(old, new)` | `Bool` | Renames a hosted file path. |
|
||||
| `std.fs.dirEntryCount(path)` | `Maybe<usize>` | Counts entries in a hosted directory. |
|
||||
| `std.fs.dirEntryName(buffer, path, index)` | `Maybe<Span<u8>>` | Writes one hosted directory entry name into caller storage. |
|
||||
| `std.fs.tempName(buffer, prefix)` | `Maybe<String>` | Writes a temporary path into caller storage. |
|
||||
| `std.fs.atomicWrite(path, temp, bytes)` | `Bool` | Writes through a caller-provided temporary path and renames. |
|
||||
| `std.fs.close(&mut file)` | `Void` | Closes an owned file handle explicitly; remaining owned files are cleaned up deterministically. |
|
||||
| `std.fs.readFile(fs, path, buffer)` | `Maybe<usize>` | Opens, fills caller storage, and closes through explicit `Fs`; returns the total file size, so a value above `len(buffer)` signals truncation. |
|
||||
| `std.fs.writeFile(fs, path, bytes)` | `Bool` | Creates, writes all bytes, and closes through explicit `Fs`. |
|
||||
| `std.fs.appendFile(fs, path, bytes)` | `Bool` | Opens, appends all bytes, and closes through explicit `Fs`. |
|
||||
| `std.fs.readFileBytes(fs, path, buffer)` | `Maybe<Span<u8>>` | Opens, reads a full file, closes it, and returns the live prefix of caller storage; `null` when the file exceeds the buffer. |
|
||||
| `std.fs.readFileEquals(fs, path, buffer, expected)` | `Bool` | Reads a full file through caller storage and compares the bytes with an expected span. |
|
||||
| `std.fs.copyFile(from, to, buffer)` | `Bool` | Copies a hosted file through caller-provided scratch storage. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Richer permissions and platform-specific file modes.
|
||||
- Recursive directory walking helpers.
|
||||
- Async or nonblocking I/O.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises [NotFound, TooLarge, Io] {
|
||||
let fs: Fs = std.fs.host()
|
||||
var buf: [32]u8 = [0_u8; 32]
|
||||
if std.fs.ensureDir(".zero/out") && std.fs.writeFile(fs, ".zero/out/example.txt", "hello\n") {
|
||||
let bytes: Maybe<Span<u8>> = std.fs.readFileBytes(fs, ".zero/out/example.txt", buf)
|
||||
let size: Maybe<usize> = std.fs.fileSize(fs, ".zero/out/example.txt")
|
||||
if bytes.has && size.has && size.value == 6 && std.fs.isFile(".zero/out/example.txt") && std.fs.readFileEquals(fs, ".zero/out/example.txt", buf, "hello\n") && std.fs.rename(".zero/out/example.txt", ".zero/out/example-renamed.txt") {
|
||||
if std.fs.remove(".zero/out/example-renamed.txt") {
|
||||
check world.out.write("fs ok\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
The path helpers are a small current API, not a hidden global filesystem.
|
||||
|
||||
Stable file APIs make effects, ownership, and cleanup visible through
|
||||
capabilities.
|
||||
|
||||
Hosted filesystem APIs are denied on non-host targets with `TAR002`.
|
||||
Target-neutral packages should keep filesystem code outside their cross-target
|
||||
entry point.
|
||||
@@ -0,0 +1,406 @@
|
||||
## When To Use std.http
|
||||
|
||||
In Zerolang, use `std.http` for HTTP request parsing, response envelope writing, hosted
|
||||
fetch, local listen support, and web API helpers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.http.parseMethod(text)` | `HttpMethod` | Parses a small HTTP method token. |
|
||||
| `std.http.client(net)` | `HttpClient` | Creates hosted client metadata from a network capability. |
|
||||
| `std.http.server(net, address)` | `HttpServer` | Creates hosted server metadata from a network capability and address. |
|
||||
| `std.http.listen(world)` | `Void raises [Io]` | Starts a loopback HTTP listener from `zero run`, auto-selecting a development port from 3000 upward. |
|
||||
| `std.http.listen(world, port)` | `Void raises [Io]` | Starts a loopback HTTP listener on exactly `port`, failing if the port is occupied. |
|
||||
| `std.http.fetch(client, request, response, timeout)` | `HttpResult` | Performs a hosted HTTP(S) request with a `Duration` timeout and writes response metadata, headers, and body into caller-owned storage. |
|
||||
| `std.http.resultOk(result)` | `Bool` | True when transport succeeded and the status is 2xx. |
|
||||
| `std.http.resultStatus(result)` | `u16` | Reads the HTTP status, or `0` when no status was available. |
|
||||
| `std.http.resultBodyLen(result)` | `usize` | Reads the number of response body bytes written into the response buffer. |
|
||||
| `std.http.resultError(result)` | `HttpError` | Reads the transport/provider error. |
|
||||
| `std.http.errorNone()` | `HttpError` | Transport succeeded. |
|
||||
| `std.http.errorInvalidUrl()` | `HttpError` | The request URL was invalid. |
|
||||
| `std.http.errorUnsupportedProtocol()` | `HttpError` | The URL protocol is not supported. |
|
||||
| `std.http.errorDns()` | `HttpError` | DNS lookup failed. |
|
||||
| `std.http.errorConnect()` | `HttpError` | Connecting to the remote host failed. |
|
||||
| `std.http.errorTls()` | `HttpError` | TLS verification or connection setup failed. |
|
||||
| `std.http.errorTimeout()` | `HttpError` | The request timed out. |
|
||||
| `std.http.errorTooLarge()` | `HttpError` | The response did not fit in the caller-owned buffer. |
|
||||
| `std.http.errorProviderUnavailable()` | `HttpError` | The hosted HTTP provider is unavailable. |
|
||||
| `std.http.errorIo()` | `HttpError` | The provider reported an I/O failure. |
|
||||
| `std.http.errorInvalidRequest()` | `HttpError` | The request envelope was invalid. |
|
||||
| `std.http.errorName(error)` | `String` | Returns a stable label for a transport error. |
|
||||
| `std.http.responseLen(response)` | `usize` | Reads the response byte count written after the internal metadata prefix. |
|
||||
| `std.http.responseHeadersLen(response)` | `usize` | Reads the raw response header byte count. |
|
||||
| `std.http.responseBodyOffset(response)` | `usize` | Reads the body start offset within the response buffer. |
|
||||
| `std.http.headerValue(response, name)` | `HttpHeaderValue` | Locates a response header value by case-insensitive header name. |
|
||||
| `std.http.headerFound(value)` | `Bool` | True when `headerValue` found a matching header. |
|
||||
| `std.http.headerOffset(value)` | `usize` | Reads the header value byte offset within the response buffer. |
|
||||
| `std.http.headerLen(value)` | `usize` | Reads the header value byte length. |
|
||||
| `std.http.tlsBoundary()` | `String` | Names the platform or C-library TLS boundary. |
|
||||
| `std.http.statusReason(status)` | `String` | Returns a common reason phrase for status-line writing. |
|
||||
| `std.http.statusIsInformational(status)` | `Bool` | True for 1xx statuses. |
|
||||
| `std.http.statusIsSuccess(status)` | `Bool` | True for 2xx statuses. |
|
||||
| `std.http.statusIsRedirect(status)` | `Bool` | True for 3xx statuses. |
|
||||
| `std.http.statusIsClientError(status)` | `Bool` | True for 4xx statuses. |
|
||||
| `std.http.statusIsServerError(status)` | `Bool` | True for 5xx statuses. |
|
||||
| `std.http.writeRequest(buffer, startLine, body)` | `Maybe<Span<u8>>` | Writes `METHOD URL`, optional content length, blank line, and body into caller storage. |
|
||||
| `std.http.writeRequestWithHeader(buffer, startLine, headerLine, body)` | `Maybe<Span<u8>>` | Writes a request envelope with one validated `name: value` header line. |
|
||||
| `std.http.writeRequestWithHeaders(buffer, startLine, headers, body)` | `Maybe<Span<u8>>` | Writes a request envelope with a validated newline-separated header block. |
|
||||
| `std.http.writeMethodRequest(buffer, method, target, body)` | `Maybe<Span<u8>>` | Writes a request envelope from separate method and target spans. |
|
||||
| `std.http.writeGetRequest(buffer, target)` | `Maybe<Span<u8>>` | Writes an empty GET request envelope. |
|
||||
| `std.http.writeHeadRequest(buffer, target)` | `Maybe<Span<u8>>` | Writes an empty HEAD request envelope. |
|
||||
| `std.http.writeDeleteRequest(buffer, target)` | `Maybe<Span<u8>>` | Writes an empty DELETE request envelope. |
|
||||
| `std.http.writeJsonRequest(buffer, startLine, body)` | `Maybe<Span<u8>>` | Writes a JSON request envelope with `content-type` and `content-length`. |
|
||||
| `std.http.writeJsonMethodRequest(buffer, method, target, body)` | `Maybe<Span<u8>>` | Writes a JSON request envelope from separate method and target spans. |
|
||||
| `std.http.writeJsonRequestWithHeader(buffer, startLine, headerLine, body)` | `Maybe<Span<u8>>` | Writes a JSON request envelope with one validated extra header line. |
|
||||
| `std.http.writeJsonRequestWithHeaders(buffer, startLine, headers, body)` | `Maybe<Span<u8>>` | Writes a JSON request envelope with a validated extra header block. |
|
||||
| `std.http.writePostJsonRequest(buffer, target, body)` | `Maybe<Span<u8>>` | Writes a POST JSON request envelope. |
|
||||
| `std.http.writePutJsonRequest(buffer, target, body)` | `Maybe<Span<u8>>` | Writes a PUT JSON request envelope. |
|
||||
| `std.http.writePatchJsonRequest(buffer, target, body)` | `Maybe<Span<u8>>` | Writes a PATCH JSON request envelope. |
|
||||
| `std.http.writeResponse(buffer, status, body)` | `Maybe<Span<u8>>` | Writes an HTTP/1.1 response envelope into caller storage. |
|
||||
| `std.http.writeResponseWithHeader(buffer, status, headerLine, body)` | `Maybe<Span<u8>>` | Writes a response envelope with one validated `name: value` header line. |
|
||||
| `std.http.writeResponseWithHeaders(buffer, status, headers, body)` | `Maybe<Span<u8>>` | Writes a response envelope with a validated newline-separated header block. |
|
||||
| `std.http.writeJsonResponse(buffer, status, body)` | `Maybe<Span<u8>>` | Writes a JSON HTTP/1.1 response envelope into caller storage. |
|
||||
| `std.http.writeJsonResponseWithHeader(buffer, status, headerLine, body)` | `Maybe<Span<u8>>` | Writes a JSON response envelope with one validated `name: value` header line. |
|
||||
| `std.http.writeJsonResponseWithHeaders(buffer, status, headers, body)` | `Maybe<Span<u8>>` | Writes a JSON response envelope with a validated extra header block. |
|
||||
| `std.http.writeJsonResponseWithCookie(buffer, status, cookie, body)` | `Maybe<Span<u8>>` | Writes a JSON response envelope with one `Set-Cookie` header value. |
|
||||
| `std.http.writeJsonError(buffer, status, code)` | `Maybe<Span<u8>>` | Writes `{"error":"code"}` after validating the code is JSON-safe lower-case ASCII, digits, `_`, or `-`. |
|
||||
| `std.http.writeCorsPreflight(buffer, allowOrigin, allowMethods, allowHeaders)` | `Maybe<Span<u8>>` | Writes a 204 CORS preflight response with caller-provided allow headers. |
|
||||
| `std.http.writeCorsJsonResponse(buffer, statusLine, body, allowOrigin)` | `Maybe<Span<u8>>` | Writes a JSON response with `access-control-allow-origin`; `statusLine` is a fragment such as `"200 OK"`. |
|
||||
| `std.http.writeTextResponse(buffer, status, body)` | `Maybe<Span<u8>>` | Writes a `text/plain; charset=utf-8` response envelope into caller storage. |
|
||||
| `std.http.writeTextOk(buffer, body)` | `Maybe<Span<u8>>` | Writes a 200 plain-text response envelope into caller storage. |
|
||||
| `std.http.writeHtmlResponse(buffer, status, body)` | `Maybe<Span<u8>>` | Writes a `text/html; charset=utf-8` response envelope into caller storage. |
|
||||
| `std.http.writeHtmlOk(buffer, body)` | `Maybe<Span<u8>>` | Writes a 200 HTML response envelope into caller storage. |
|
||||
| `std.http.writeRedirect(buffer, status, location)` | `Maybe<Span<u8>>` | Writes a redirect response with a safe `Location` header; rejects non-3xx statuses and empty or control-character locations. |
|
||||
| `std.http.writeFound(buffer, location)` | `Maybe<Span<u8>>` | Writes a 302 redirect response. |
|
||||
| `std.http.writeSeeOther(buffer, location)` | `Maybe<Span<u8>>` | Writes a 303 redirect response. |
|
||||
| `std.http.writeMovedPermanently(buffer, location)` | `Maybe<Span<u8>>` | Writes a 301 redirect response. |
|
||||
| `std.http.writePermanentRedirect(buffer, location)` | `Maybe<Span<u8>>` | Writes a 308 redirect response. |
|
||||
| `std.http.contentTypeForPath(path)` | `String` | Returns a small static-file media type from a path suffix. |
|
||||
| `std.http.writeStaticResponse(buffer, status, path, body)` | `Maybe<Span<u8>>` | Writes a response with `content-type` inferred from the path suffix. |
|
||||
| `std.http.writeJsonOk(buffer, body)` | `Maybe<Span<u8>>` | Writes a 200 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonCreated(buffer, body)` | `Maybe<Span<u8>>` | Writes a 201 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonBadRequest(buffer, body)` | `Maybe<Span<u8>>` | Writes a 400 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonUnauthorized(buffer, body)` | `Maybe<Span<u8>>` | Writes a 401 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonForbidden(buffer, body)` | `Maybe<Span<u8>>` | Writes a 403 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonNotFound(buffer, body)` | `Maybe<Span<u8>>` | Writes a 404 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonMethodNotAllowed(buffer, body)` | `Maybe<Span<u8>>` | Writes a 405 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonConflict(buffer, body)` | `Maybe<Span<u8>>` | Writes a 409 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonUnprocessable(buffer, body)` | `Maybe<Span<u8>>` | Writes a 422 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonTooManyRequests(buffer, body)` | `Maybe<Span<u8>>` | Writes a 429 JSON response envelope into caller storage. |
|
||||
| `std.http.writeJsonInternalServerError(buffer, body)` | `Maybe<Span<u8>>` | Writes a 500 JSON response envelope into caller storage. |
|
||||
| `std.http.writeNoContent(buffer)` | `Maybe<Span<u8>>` | Writes a 204 response envelope with an empty body. |
|
||||
| `std.http.requestMethodName(request)` | `Maybe<Span<u8>>` | Borrows the method token from a request envelope. |
|
||||
| `std.http.requestTarget(request)` | `Maybe<Span<u8>>` | Borrows the raw target from a request envelope. |
|
||||
| `std.http.requestPath(request)` | `Maybe<Span<u8>>` | Borrows the path from an absolute or origin-form request target. |
|
||||
| `std.http.pathSegmentCount(path)` | `usize` | Counts non-empty path segments in a path span. |
|
||||
| `std.http.pathSegment(path, index)` | `Maybe<Span<u8>>` | Borrows a zero-based non-empty path segment from a path span. |
|
||||
| `std.http.pathMatchesPattern(path, pattern)` | `Bool` | Matches path segments against literals, `:params`, and a trailing `*` wildcard. |
|
||||
| `std.http.pathParam(path, pattern, name)` | `Maybe<Span<u8>>` | Borrows a named path parameter from a matched path pattern. |
|
||||
| `std.http.requestPathSegmentCount(request)` | `usize` | Counts non-empty path segments from a request envelope path. |
|
||||
| `std.http.requestPathSegment(request, index)` | `Maybe<Span<u8>>` | Borrows a zero-based non-empty request path segment. |
|
||||
| `std.http.requestQuery(request)` | `Maybe<Span<u8>>` | Borrows the query string from a request target. |
|
||||
| `std.http.requestQueryValue(request, name)` | `Maybe<Span<u8>>` | Borrows a query value by name from a request envelope. |
|
||||
| `std.http.requestHeader(request, name)` | `Maybe<Span<u8>>` | Borrows a case-insensitive request header value. |
|
||||
| `std.http.requestBearerToken(request)` | `Maybe<Span<u8>>` | Borrows the bearer token from the `Authorization` request header. |
|
||||
| `std.http.requestCookie(request, name)` | `Maybe<Span<u8>>` | Borrows a named cookie value from the `Cookie` request header. |
|
||||
| `std.http.requestContentLength(request)` | `Maybe<usize>` | Parses the `Content-Length` request header. |
|
||||
| `std.http.requestContentType(request)` | `Maybe<Span<u8>>` | Borrows the media type from `Content-Type`, excluding parameters. |
|
||||
| `std.http.requestAccepts(request, media)` | `Bool` | Checks whether `Accept` allows an exact media type, type wildcard, or `*/*`; absent `Accept` allows any media and `q=0` rejects a range. |
|
||||
| `std.http.requestAcceptsJson(request)` | `Bool` | Checks whether `Accept` allows `application/json`. |
|
||||
| `std.http.requestBody(request)` | `Maybe<Span<u8>>` | Borrows the request body after the blank line. |
|
||||
| `std.http.requestBodyWithin(request, max)` | `Maybe<Span<u8>>` | Borrows the request body only when it is at most `max` bytes. |
|
||||
| `std.http.requestHasJsonContentType(request)` | `Bool` | True when the request content type is `application/json`, ignoring ASCII case and allowing parameters. |
|
||||
| `std.http.requestJsonBodyWithin(request, max)` | `Maybe<Span<u8>>` | Borrows the body only when content type is JSON, body length is within `max`, and bytes validate as JSON. |
|
||||
| `std.http.requestJsonField(request, name, max)` | `Maybe<Span<u8>>` | Borrows a top-level JSON field from a bounded JSON request body. |
|
||||
| `std.http.requestMatches(request, method, path)` | `Bool` | True when a request envelope has the exact method and normalized path. |
|
||||
| `std.http.methodAllowed(method, allowed)` | `Bool` | Checks a method against a comma-separated allow list. |
|
||||
| `std.http.requestMethodAllowed(request, allowed)` | `Bool` | Checks a request method against a comma-separated allow list. |
|
||||
| `std.http.requestMethodIs(request, method)` | `Bool` | True when a request envelope has the exact method. |
|
||||
| `std.http.requestIsGet(request, path)` | `Bool` | True when a request envelope is `GET` for the normalized path. |
|
||||
| `std.http.requestIsHead(request, path)` | `Bool` | True when a request envelope is `HEAD` for the normalized path. |
|
||||
| `std.http.requestIsOptions(request, path)` | `Bool` | True when a request envelope is `OPTIONS` for the normalized path. |
|
||||
| `std.http.requestIsPost(request, path)` | `Bool` | True when a request envelope is `POST` for the normalized path. |
|
||||
| `std.http.requestIsPut(request, path)` | `Bool` | True when a request envelope is `PUT` for the normalized path. |
|
||||
| `std.http.requestIsPatch(request, path)` | `Bool` | True when a request envelope is `PATCH` for the normalized path. |
|
||||
| `std.http.requestIsDelete(request, path)` | `Bool` | True when a request envelope is `DELETE` for the normalized path. |
|
||||
| `std.http.requestPathStartsWith(request, prefix)` | `Bool` | True when the normalized request path starts with `prefix`. |
|
||||
| `std.http.requestPathTailAfter(request, prefix)` | `Maybe<Span<u8>>` | Borrows the normalized request path after `prefix`, or `null` when it does not match. |
|
||||
| `std.http.requestRouteMatches(request, method, pattern)` | `Bool` | True when the method matches and the path matches a segment pattern. |
|
||||
| `std.http.requestRouteMethodAllowed(request, pattern, allowed)` | `Bool` | True when the path pattern matches and the method is in a comma-separated allow list. |
|
||||
| `std.http.requestPathParam(request, pattern, name)` | `Maybe<Span<u8>>` | Borrows a named request path parameter from a matched pattern. |
|
||||
| `std.http.headerBlockSafe(headers)` | `Bool` | Validates a newline-separated header block before writing it. |
|
||||
| `std.http.headerBytes(response, value)` | `Maybe<Span<u8>>` | Borrows a response header value after validating packed metadata. |
|
||||
| `std.http.responseBody(response, result)` | `Maybe<Span<u8>>` | Borrows the response body when the transport result succeeded. |
|
||||
| `std.http.responseBodyBytes(response)` | `Maybe<Span<u8>>` | Borrows the body bytes from a local response envelope written by response helpers. |
|
||||
| `std.http.responseStatus(response)` | `Maybe<u16>` | Parses the status code from a local response envelope. |
|
||||
| `std.http.responseStatusIs(response, status)` | `Bool` | Checks the status code in a local response envelope. |
|
||||
| `std.http.responseHeader(response, name)` | `Maybe<Span<u8>>` | Borrows a case-insensitive header value from a local response envelope. |
|
||||
| `std.http.responseContentType(response)` | `Maybe<Span<u8>>` | Borrows the media type from a local response envelope. |
|
||||
| `std.http.responseRedirectLocation(response)` | `Maybe<Span<u8>>` | Borrows a redirect `Location` header from a local response envelope. |
|
||||
| `std.http.responseBodyEquals(response, expected)` | `Bool` | Compares a local response body with expected bytes. |
|
||||
| `std.http.responseMatches(response, status, contentType, body)` | `Bool` | Checks local response status, optional content type, and body bytes. |
|
||||
| `std.http.testRequest(buffer, method, target, body)` | `Maybe<Span<u8>>` | Writes a synthetic request envelope for handler tests. |
|
||||
| `std.http.testJsonRequest(buffer, method, target, body)` | `Maybe<Span<u8>>` | Writes a synthetic JSON request envelope for handler tests. |
|
||||
|
||||
The `WithHeader` and `WithHeaders` writers reject header names they manage
|
||||
themselves. Request writers reject `content-length` and `transfer-encoding`;
|
||||
JSON request writers also reject `content-type`; response writers reject
|
||||
`content-length`, `transfer-encoding`, and `connection`; JSON response writers
|
||||
also reject `content-type`.
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: net, memory, parse, or none for named error constants
|
||||
- allocation behavior: metadata helpers do not allocate; `fetch` writes into a
|
||||
caller-owned response buffer and uses the provider runtime internally
|
||||
- target support: request/response parsing and writing helpers are
|
||||
target-neutral; client/server require a net-capable target; `fetch` and
|
||||
`listen` run on supported Darwin arm64 and Linux x64 host executable targets
|
||||
- error behavior: metadata helpers are infallible; `fetch` returns status,
|
||||
body length, and error metadata so non-2xx responses are distinguishable from
|
||||
transport failures
|
||||
- ownership notes: HTTP helpers borrow network capability metadata and write
|
||||
only to caller-owned buffers
|
||||
- examples: `conformance/native/pass/std-http-metadata-neutral.graph`,
|
||||
`conformance/native/pass/std-http-fetch.graph`,
|
||||
`conformance/native/pass/std-http-errors.graph`,
|
||||
`conformance/native/pass/std-http-response-helpers.graph`,
|
||||
`conformance/native/pass/std-http-api-helpers.graph`,
|
||||
`conformance/native/pass/std-http-cors-helpers.graph`,
|
||||
`conformance/native/pass/std-http-auth-helpers.graph`,
|
||||
`conformance/native/pass/std-http-path-segments.graph`,
|
||||
`examples/json-api-client.graph`,
|
||||
`examples/json-api-router.graph`,
|
||||
`examples/std-http-json.graph`,
|
||||
`examples/std-http-request.graph`,
|
||||
`examples/std-http-headers.graph`
|
||||
|
||||
## Example
|
||||
|
||||
Metadata helpers:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let net: Net = std.net.host()
|
||||
let addr: Address = std.net.address("localhost", 8080_u16)
|
||||
let _client: HttpClient = std.http.client(net)
|
||||
let _server: HttpServer = std.http.server(net, addr)
|
||||
let method: HttpMethod = std.http.parseMethod("GET")
|
||||
if method == std.http.parseMethod("GET") && std.mem.len(std.mem.span("body")) == 4 {
|
||||
check world.out.write("http ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
GET request:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let net: Net = std.net.host()
|
||||
let client: HttpClient = std.http.client(net)
|
||||
var response: [512]u8 = [0_u8; 512]
|
||||
let request: Span<u8> = std.mem.span("GET https://example.com\n\n")
|
||||
let result: HttpResult = std.http.fetch(client, request, response, std.time.ms(1000))
|
||||
if std.http.resultOk(result) {
|
||||
check world.out.write("http get ok\n")
|
||||
return
|
||||
}
|
||||
check world.err.write("http get failed\n")
|
||||
}
|
||||
```
|
||||
|
||||
Request with headers and body:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let net: Net = std.net.host()
|
||||
let client: HttpClient = std.http.client(net)
|
||||
var request_buf: [256]u8 = [0_u8; 256]
|
||||
let request: Maybe<Span<u8>> = std.http.writeJsonRequestWithHeader(request_buf, "POST https://example.com/api", "accept: application/json", "{\"ping\":1}")
|
||||
var response: [512]u8 = [0_u8; 512]
|
||||
if request.has {
|
||||
let result: HttpResult = std.http.fetch(client, request.value, response, std.time.ms(1000))
|
||||
if std.http.resultOk(result) {
|
||||
check world.out.write("http post ok\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
check world.err.write("http post failed\n")
|
||||
}
|
||||
```
|
||||
|
||||
Request routing:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let request: Span<u8> = std.mem.span("POST /users/7?tenant=demo\naccept: application/json\ncontent-type: application/json; charset=utf-8\n\n{\"id\":7}")
|
||||
var response: [256]u8 = [0_u8; 256]
|
||||
let id: Maybe<Span<u8>> = std.http.requestPathParam(request, "/users/:id", "id")
|
||||
let body_id: Maybe<Span<u8>> = std.http.requestJsonField(request, "id", 64)
|
||||
let tenant: Maybe<Span<u8>> = std.http.requestQueryValue(request, "tenant")
|
||||
if std.http.requestRouteMatches(request, "POST", "/users/:id") && id.has && tenant.has && body_id.has && std.http.requestAcceptsJson(request) {
|
||||
let written: Maybe<Span<u8>> = std.http.writeJsonResponseWithHeader(response, 201_u16, "location: /users/7", "{\"created\":true}")
|
||||
if written.has {
|
||||
check world.out.write("http route ok\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
let failed: Maybe<Span<u8>> = std.http.writeJsonError(response, 400, "bad_request")
|
||||
if failed.has {
|
||||
check world.err.write("http route failed\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CORS preflight and JSON response:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let request: Span<u8> = std.mem.span("OPTIONS /users\naccess-control-request-method: POST\n\n")
|
||||
var response: [256]u8 = [0_u8; 256]
|
||||
if std.http.requestIsOptions(request, "/users") {
|
||||
let written: Maybe<Span<u8>> = std.http.writeCorsPreflight(response, "*", "GET, POST, OPTIONS", "content-type, authorization")
|
||||
if written.has {
|
||||
check world.out.write("http cors preflight ok\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
let failed: Maybe<Span<u8>> = std.http.writeCorsJsonResponse(response, "400 Bad Request", "{\"error\":\"bad_request\"}", "*")
|
||||
if failed.has {
|
||||
check world.err.write("http cors failed\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Authorization and session cookie helpers:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let request: Span<u8> = std.mem.span("GET /me\nauthorization: Bearer token-123\ncookie: sid=abc; theme=dark\n\n")
|
||||
let token: Maybe<Span<u8>> = std.http.requestBearerToken(request)
|
||||
let session: Maybe<Span<u8>> = std.http.requestCookie(request, "sid")
|
||||
if token.has && session.has {
|
||||
check world.out.write("http auth ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response body:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let maybe_request: Maybe<String> = std.args.get(1)
|
||||
if !maybe_request.has {
|
||||
check world.err.write("usage: pass HTTP request envelope\n")
|
||||
return
|
||||
}
|
||||
let net: Net = std.net.host()
|
||||
let client: HttpClient = std.http.client(net)
|
||||
var response: [512]u8 = [0_u8; 512]
|
||||
let result: HttpResult = std.http.fetch(client, std.mem.span(maybe_request.value), response, std.time.ms(5000))
|
||||
let bytes: Maybe<Span<u8>> = std.http.responseBody(response, result)
|
||||
var arena_buf: [16]u8 = [0_u8; 16]
|
||||
var arena: FixedBufAlloc = std.mem.fixedBufAlloc(arena_buf)
|
||||
var parsed: Maybe<JsonDoc> = null
|
||||
if bytes.has {
|
||||
parsed = std.json.parseBytes(arena, bytes.value)
|
||||
}
|
||||
if std.http.resultOk(result) && bytes.has && parsed.has {
|
||||
check world.out.write("http response json ok\n")
|
||||
return
|
||||
}
|
||||
check world.err.write("http response json failed\n")
|
||||
}
|
||||
```
|
||||
|
||||
Loopback API server:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
check std.http.listen(world)
|
||||
}
|
||||
|
||||
fn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {
|
||||
if std.http.requestIsGet(request, "/ping") {
|
||||
return std.http.writeJsonOk(response, "{\"message\":\"pong\"}")
|
||||
}
|
||||
if std.http.requestIsGet(request, "/robots.txt") {
|
||||
return std.http.writeTextOk(response, "user-agent: *\nallow: /\n")
|
||||
}
|
||||
if std.http.requestIsGet(request, "/old") {
|
||||
return std.http.writeMovedPermanently(response, "/ping")
|
||||
}
|
||||
return std.http.writeJsonError(response, 404, "not_found")
|
||||
}
|
||||
```
|
||||
|
||||
Run it and use the port printed by the listener:
|
||||
|
||||
```sh
|
||||
zero run .
|
||||
# listening on http://127.0.0.1:3001
|
||||
curl -sS -i http://127.0.0.1:3001/ping
|
||||
```
|
||||
|
||||
When no port is passed, `listen(world)` starts at `3000` and increments by one
|
||||
until it finds a free loopback port. This lets small local services coexist on a
|
||||
development machine. When the program passes an explicit port, such as
|
||||
`std.http.listen(world, 3000_u16)`, Zero tries exactly that port and reports the
|
||||
bind failure instead of auto-incrementing.
|
||||
|
||||
Synthetic handler check:
|
||||
|
||||
```zero
|
||||
fn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {
|
||||
if std.http.requestRouteMethodAllowed(request, "/users/:id", "GET, HEAD") {
|
||||
return std.http.writeJsonResponseWithHeaders(response, 200_u16, "cache-control: no-store", "{\"ok\":true}")
|
||||
}
|
||||
return std.http.writeJsonError(response, 404_u16, "not_found")
|
||||
}
|
||||
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var request_storage: [128]u8 = [0_u8; 128]
|
||||
var response_storage: [256]u8 = [0_u8; 256]
|
||||
let request: Maybe<Span<u8>> = std.http.testRequest(request_storage, "GET", "/users/7", "")
|
||||
if request.has {
|
||||
let response: Maybe<Span<u8>> = handle(request.value, response_storage)
|
||||
if response.has && std.http.responseMatches(response.value, 200_u16, "application/json", "{\"ok\":true}") {
|
||||
check world.out.write("handler ok\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
check world.err.write("handler failed\n")
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.http.fetch` is the outbound HTTP client primitive. The request argument is
|
||||
one byte envelope: `METHOD URL`, followed by zero or more `Header-Name: value`
|
||||
lines, a blank line, and optional request body bytes. Use `writeRequest` and
|
||||
`writeJsonRequest` when you want the standard library to write that envelope
|
||||
into caller storage. The timeout is a `Duration`, typically built with
|
||||
`std.time.ms(...)`.
|
||||
|
||||
`fetch` supports `http://` and `https://` URLs on supported host executable
|
||||
targets, uses the C-library curl provider, does not follow redirects, and
|
||||
verifies TLS through the provider. TLS verification is always enabled.
|
||||
`ZERO_HTTP_TEST_CA_BUNDLE` can point the provider at an explicit CA bundle
|
||||
while keeping certificate verification on.
|
||||
|
||||
The response buffer starts with internal metadata, followed by raw response
|
||||
headers and then the response body. Use `responseHeadersLen` and
|
||||
`responseBodyOffset` rather than hard-coding offsets. `headerValue` scans the
|
||||
response buffer and returns packed offset/length metadata for the matching
|
||||
value; `headerFound`, `headerOffset`, and `headerLen` inspect that metadata
|
||||
without allocating. Prefer `headerBytes` and `responseBody` when you need a
|
||||
borrowed byte span and want metadata bounds checked in one call.
|
||||
|
||||
Compare `resultError` with the named `HttpError` helpers rather than raw
|
||||
numbers. Use `errorName(resultError(result))` for diagnostics and logs.
|
||||
`errorNone` means the transport succeeded; HTTP non-2xx statuses still carry
|
||||
`errorNone` and can be inspected with `resultStatus`.
|
||||
|
||||
The module does not expose raw socket read/write APIs, streaming bodies, a
|
||||
global router, or a heap-allocated response object.
|
||||
@@ -0,0 +1,56 @@
|
||||
## When To Use std.inet
|
||||
|
||||
In Zerolang, use `std.inet` to validate and parse internet address literals:
|
||||
IPv4, IPv6, and RFC 1123 hostnames. These helpers are target-neutral and need
|
||||
no network capability, so they work in validators and parsers on any compiler
|
||||
target. Use `std.net` when a program actually opens connections or listeners.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.inet.isIpv4(text)` | `Bool` | Validates a dotted-quad IPv4 literal: four decimal octets 0-255 with no leading zeros. |
|
||||
| `std.inet.parseIpv4(text)` | `Maybe<u32>` | Parses an IPv4 literal into a big-endian packed `u32`. |
|
||||
| `std.inet.writeIpv4(buffer, value)` | `Maybe<Span<u8>>` | Writes a packed IPv4 value as dotted-quad text into caller storage. |
|
||||
| `std.inet.isIpv4Unspecified(value)` | `Bool` | Checks `0.0.0.0/32`. |
|
||||
| `std.inet.isIpv4Loopback(value)` | `Bool` | Checks `127.0.0.0/8`. |
|
||||
| `std.inet.isIpv4Private(value)` | `Bool` | Checks RFC 1918 private ranges. |
|
||||
| `std.inet.isIpv4LinkLocal(value)` | `Bool` | Checks `169.254.0.0/16`. |
|
||||
| `std.inet.isIpv4Multicast(value)` | `Bool` | Checks `224.0.0.0/4`. |
|
||||
| `std.inet.isIpv6(text)` | `Bool` | Validates an RFC 4291 IPv6 literal, including `::` compression and embedded IPv4. |
|
||||
| `std.inet.parseIpv6(buffer, text)` | `Maybe<Span<u8>>` | Parses an IPv6 literal into 16 network-order bytes in a caller buffer. |
|
||||
| `std.inet.isIp(text)` | `Bool` | Validates either a strict IPv4 literal or an RFC 4291 IPv6 literal. |
|
||||
| `std.inet.parseIp(buffer, text)` | `Maybe<Span<u8>>` | Parses IPv4 into 4 bytes or IPv6 into 16 bytes in caller storage. |
|
||||
| `std.inet.isIpv6Unspecified(bytes)` | `Bool` | Checks `::/128` over a 16-byte IPv6 span. |
|
||||
| `std.inet.isIpv6Loopback(bytes)` | `Bool` | Checks `::1/128` over a 16-byte IPv6 span. |
|
||||
| `std.inet.isIpv6Multicast(bytes)` | `Bool` | Checks `ff00::/8`. |
|
||||
| `std.inet.isIpv6LinkLocal(bytes)` | `Bool` | Checks `fe80::/10`. |
|
||||
| `std.inet.isIpv6Private(bytes)` | `Bool` | Checks the `fc00::/7` unique-local range. |
|
||||
| `std.inet.isIpv6UniqueLocal(bytes)` | `Bool` | Alias for the `fc00::/7` unique-local range. |
|
||||
| `std.inet.isIpv6MappedIpv4(bytes)` | `Bool` | Checks `::ffff:0:0/96` IPv4-mapped addresses. |
|
||||
| `std.inet.ipv6MappedIpv4(bytes)` | `Maybe<u32>` | Extracts the packed IPv4 value from an IPv4-mapped IPv6 span. |
|
||||
| `std.inet.isHostname(text)` | `Bool` | Validates an RFC 1123 hostname: dot-separated 1-63 byte alphanumeric/hyphen labels, 253 bytes total. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [16]u8 = [0; 16]
|
||||
let buffer: MutSpan<u8> = storage
|
||||
let quad: Maybe<u32> = std.inet.parseIpv4("192.168.0.1")
|
||||
let mapped: Maybe<Span<u8>> = std.inet.parseIpv6(buffer, "::ffff:192.168.1.1")
|
||||
if std.inet.isHostname("example.com") && (quad.has && mapped.has) && (std.inet.isIpv4Private(quad.value) && std.inet.isIpv6MappedIpv4(mapped.value)) {
|
||||
check world.out.write("inet ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none.
|
||||
|
||||
Allocation behavior: `parseIp` and `parseIpv6` write into caller buffers;
|
||||
`writeIpv4` writes into caller storage. The other helpers allocate nothing.
|
||||
|
||||
Error behavior: validators return `Bool`; parsers return `null` for invalid
|
||||
literals or undersized buffers.
|
||||
|
||||
Target support: current compiler targets; no network capability required.
|
||||
@@ -0,0 +1,128 @@
|
||||
## When To Use std.io
|
||||
|
||||
In Zerolang, use `std.io` for byte reads and writes over caller-owned storage.
|
||||
The module provides explicit cursor helpers for spans plus fixed reader and
|
||||
writer values for sequential stream-style code.
|
||||
|
||||
Span helpers:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.copy(dst, src)` | `usize` | Copies as many bytes as fit and returns the count. |
|
||||
| `std.io.copyN(dst, src, count)` | `Maybe<usize>` | Copies exactly `count` bytes when both spans are large enough. |
|
||||
| `std.io.read(bytes, offset, dst)` | `Maybe<usize>` | Copies available bytes and returns the next cursor. |
|
||||
| `std.io.readExact(bytes, offset, dst)` | `Maybe<usize>` | Fills the destination only when enough input bytes remain. |
|
||||
| `std.io.readAt(bytes, offset, dst)` | `Maybe<usize>` | Reads from an explicit offset without owning state. |
|
||||
| `std.io.readAll(bytes, dst)` | `Maybe<Span<u8>>` | Copies all input bytes and returns the written prefix. |
|
||||
| `std.io.readByte(bytes, offset)` | `Maybe<u8>` | Reads one byte at an explicit cursor. |
|
||||
| `std.io.writeByte(buffer, offset, byte)` | `Maybe<usize>` | Writes one byte and returns the next cursor. |
|
||||
| `std.io.writeSpan(buffer, offset, bytes)` | `Maybe<usize>` | Writes bytes and returns the next cursor. |
|
||||
| `std.io.writeAll(buffer, offset, bytes)` | `Maybe<usize>` | Writes all bytes only when the full input fits. |
|
||||
| `std.io.writeAt(buffer, offset, bytes)` | `Maybe<usize>` | Writes at an explicit offset. |
|
||||
| `std.io.written(buffer, len)` | `Span<u8>` | Borrows the written prefix, clamped to buffer length. |
|
||||
| `std.io.remaining(buffer, offset)` | `usize` | Reports remaining byte capacity from an explicit cursor. |
|
||||
|
||||
Line and delimiter helpers:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.nextLine(bytes, start)` | `Maybe<Span<u8>>` | Borrows the next line without `\n` or trailing `\r`. |
|
||||
| `std.io.nextLineStart(bytes, start)` | `usize` | Advances to the next line start or end of input. |
|
||||
| `std.io.readLine(bytes, start)` | `Maybe<Span<u8>>` | Alias for line-oriented reads using the same line rules. |
|
||||
| `std.io.readLineStart(bytes, start)` | `usize` | Advances to the next line start using the same line rules. |
|
||||
| `std.io.readUntilDelimiter(bytes, start, delimiter)` | `Maybe<Span<u8>>` | Borrows bytes up to the delimiter or end of input. |
|
||||
| `std.io.readUntilDelimiterStart(bytes, start, delimiter)` | `usize` | Advances past the delimiter when found, otherwise to end of input. |
|
||||
| `std.io.countLines(bytes)` | `usize` | Counts lines using the same next-line rules. |
|
||||
|
||||
Fixed stream helpers:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.fixedReader(bytes, cursor)` | `FixedReader` | Builds a sequential reader over borrowed bytes. |
|
||||
| `std.io.fixedReaderRead(&mut reader, dst)` | `usize` | Reads up to `dst` length and advances the reader cursor. |
|
||||
| `std.io.fixedReaderReadExact(&mut reader, dst)` | `Bool` | Reads exactly `dst` length or leaves the cursor unchanged. |
|
||||
| `std.io.fixedReaderReadLine(&mut reader)` | `Maybe<Span<u8>>` | Borrows the next line and advances the cursor. |
|
||||
| `std.io.fixedReaderReadUntilDelimiter(&mut reader, delimiter)` | `Maybe<Span<u8>>` | Borrows bytes up to a delimiter and advances the cursor. |
|
||||
| `std.io.fixedReaderReadAll(&mut reader, dst)` | `Maybe<Span<u8>>` | Copies remaining bytes into caller storage. |
|
||||
| `std.io.fixedReaderReadAt(&reader, offset, dst)` | `Maybe<usize>` | Reads from an offset without moving the cursor. |
|
||||
| `std.io.fixedReaderReadByte(&mut reader)` | `Maybe<u8>` | Reads one byte and advances the cursor. |
|
||||
| `std.io.fixedReaderLimit(&reader, count)` | `FixedReader` | Creates a bounded reader view from the current cursor. |
|
||||
| `std.io.fixedReaderSeek(&mut reader, cursor)` | `Bool` | Moves the cursor when the target is in range. |
|
||||
| `std.io.fixedReaderCursor(&reader)` | `usize` | Reports the cursor. |
|
||||
| `std.io.fixedReaderLen(&reader)` | `usize` | Reports input length. |
|
||||
| `std.io.fixedReaderRemaining(&reader)` | `usize` | Reports unread bytes. |
|
||||
| `std.io.fixedReaderDone(&reader)` | `Bool` | Reports whether the cursor reached the end. |
|
||||
| `std.io.fixedWriter(buffer, cursor)` | `FixedWriter` | Builds a sequential writer over caller storage. |
|
||||
| `std.io.fixedWriterWrite(&mut writer, bytes)` | `Bool` | Writes bytes and advances the writer cursor. |
|
||||
| `std.io.fixedWriterWriteAll(&mut writer, bytes)` | `Bool` | Writes all bytes only when they fit. |
|
||||
| `std.io.fixedWriterWriteByte(&mut writer, byte)` | `Bool` | Writes one byte and advances the cursor. |
|
||||
| `std.io.fixedWriterWriteAt(&mut writer, offset, bytes)` | `Bool` | Writes at an offset without moving the cursor. |
|
||||
| `std.io.fixedWriterView(&writer)` | `Span<u8>` | Borrows the live writer prefix. |
|
||||
| `std.io.fixedWriterSeek(&mut writer, cursor)` | `Bool` | Moves the cursor when the target is in range. |
|
||||
| `std.io.fixedWriterTruncate(&mut writer, len)` | `usize` | Clamps the live prefix length. |
|
||||
| `std.io.fixedWriterClear(&mut writer)` | `usize` | Resets the cursor to zero. |
|
||||
| `std.io.fixedWriterCursor(&writer)` | `usize` | Reports the cursor. |
|
||||
| `std.io.fixedWriterCapacity(&writer)` | `usize` | Reports storage length. |
|
||||
| `std.io.fixedWriterRemaining(&writer)` | `usize` | Reports unwritten capacity. |
|
||||
|
||||
Stream composition helpers:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.copyBuffer(&mut reader, &mut writer, scratch)` | `usize` | Copies until EOF or writer capacity using caller scratch storage. |
|
||||
| `std.io.copyReaderN(&mut reader, &mut writer, count, scratch)` | `Maybe<usize>` | Copies exactly `count` bytes through scratch storage. |
|
||||
| `std.io.discard(&mut reader, scratch)` | `usize` | Reads and drops bytes through scratch storage. |
|
||||
| `std.io.teeRead(&mut reader, &mut writer, dst)` | `Maybe<usize>` | Reads into `dst` and writes the same bytes to the writer. |
|
||||
| `std.io.multiRead(&mut first, &mut second, dst)` | `usize` | Fills `dst` from the first reader, then the second. |
|
||||
|
||||
Error labels:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.errorNone()` | `u32` | No IO error. |
|
||||
| `std.io.errorEof()` | `u32` | End of input. |
|
||||
| `std.io.errorShortRead()` | `u32` | Input ended before an exact read completed. |
|
||||
| `std.io.errorShortWrite()` | `u32` | Output capacity ended before an exact write completed. |
|
||||
| `std.io.errorCapacity()` | `u32` | Caller storage was too small. |
|
||||
| `std.io.errorPermission()` | `u32` | Permission was denied. |
|
||||
| `std.io.errorTimeout()` | `u32` | Operation timed out. |
|
||||
| `std.io.errorIo()` | `u32` | General IO failure. |
|
||||
| `std.io.errorName(code)` | `String` | Returns a stable label for a known code. |
|
||||
|
||||
Buffered capacity helpers:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.io.bufferedReader(buffer)` | `BufferedReader` | Builds a reader descriptor over caller storage. |
|
||||
| `std.io.bufferedWriter(buffer)` | `BufferedWriter` | Builds a writer descriptor over caller storage. |
|
||||
| `std.io.readerCapacity(&reader)` | `usize` | Reports reader storage capacity. |
|
||||
| `std.io.writerCapacity(&writer)` | `usize` | Reports writer storage capacity. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: memory
|
||||
- allocation behavior: uses caller buffer; no hidden heap
|
||||
- target support: target-neutral
|
||||
- error behavior: exact cursor reads and writes return `Maybe.none` or `false` on overflow or insufficient input
|
||||
- ownership notes: borrows or writes caller-owned storage
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var scratch: [4]u8 = [0_u8; 4]
|
||||
var output: [16]u8 = [0_u8; 16]
|
||||
var reader: FixedReader = std.io.fixedReader("one\ntwo", 0)
|
||||
var writer: FixedWriter = std.io.fixedWriter(output, 0)
|
||||
let line: Maybe<Span<u8>> = std.io.fixedReaderReadLine(&mut reader)
|
||||
let copied: usize = std.io.copyBuffer(&mut reader, &mut writer, scratch)
|
||||
if line.has && copied == 3 && std.mem.eql(std.io.fixedWriterView(&writer), "two") {
|
||||
check world.out.write("io ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.io` is a caller-owned buffer surface, not an ambient process I/O layer.
|
||||
Process stdin/stdout stay behind explicit capabilities such as `World` streams.
|
||||
@@ -0,0 +1,179 @@
|
||||
## When To Use std.json
|
||||
|
||||
In Zerolang, use `std.json` for validation, shallow field lookup, object and
|
||||
array cursor access, explicit-allocator parsing, and caller-buffer JSON writing.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.json.validate(text)` | `Bool` | Checks the current JSON subset without allocation. |
|
||||
| `std.json.validateBytes(bytes)` | `Bool` | Checks a `Span<u8>` JSON payload without allocation. |
|
||||
| `std.json.parse(alloc, text)` | `Maybe<JsonDoc>` | Parses with an explicit allocator and returns `null` on failure. |
|
||||
| `std.json.parseBytes(alloc, bytes)` | `Maybe<JsonDoc>` | Parses a `Span<u8>` payload with an explicit allocator and returns `null` on failure. |
|
||||
| `std.json.streamTokens(text)` | `usize` | Counts stream tokens without building an owned tree. |
|
||||
| `std.json.streamTokensBytes(bytes)` | `usize` | Counts stream tokens from a `Span<u8>` payload. |
|
||||
| `std.json.writeString(buffer, text)` | `Maybe<String>` | Writes an escaped JSON string into caller storage. |
|
||||
| `std.json.decodeBoundary()` | `String` | Documents the typed decode boundary exposed by current metadata. |
|
||||
| `std.json.errorNone()` | `u32` | Validation status for a clean payload. |
|
||||
| `std.json.errorInvalid()` | `u32` | Validation status for malformed JSON. |
|
||||
| `std.json.errorTrailing()` | `u32` | Validation status for trailing non-whitespace bytes. |
|
||||
| `std.json.errorName(code)` | `String` | Returns a stable label for a validation status. |
|
||||
| `std.json.errorExpected(code)` | `String` | Returns stable expected-token text for a validation status. |
|
||||
| `std.json.validateError(bytes)` | `u32` | Validates a byte span and returns a structured status code. |
|
||||
| `std.json.errorOffset(bytes)` | `usize` | Returns the byte offset where validation fails, or the input length for valid JSON. |
|
||||
| `std.json.errorLine(bytes)` | `usize` | Returns the one-based line for the validation error offset. |
|
||||
| `std.json.errorColumn(bytes)` | `usize` | Returns the one-based column for the validation error offset. |
|
||||
| `std.json.field(bytes, key)` | `Maybe<Span<u8>>` | Returns the raw top-level object field value. |
|
||||
| `std.json.objectFieldCount(bytes)` | `Maybe<usize>` | Counts fields in a JSON object slice. |
|
||||
| `std.json.objectKey(buffer, bytes, ordinal)` | `Maybe<Span<u8>>` | Decodes an ordinal object key into caller storage. |
|
||||
| `std.json.objectValue(bytes, ordinal)` | `Maybe<Span<u8>>` | Returns an ordinal object value as a raw JSON slice. |
|
||||
| `std.json.arrayCount(bytes)` | `Maybe<usize>` | Counts items in a JSON array slice. |
|
||||
| `std.json.arrayValue(bytes, ordinal)` | `Maybe<Span<u8>>` | Returns an ordinal array value as a raw JSON slice. |
|
||||
| `std.json.path(bytes, path)` | `Maybe<Span<u8>>` | Returns a raw value for a dot-separated object path. |
|
||||
| `std.json.pathString(buffer, bytes, path)` | `Maybe<Span<u8>>` | Looks up and decodes a string at a dot-separated object path. |
|
||||
| `std.json.pathU32(bytes, path)` | `Maybe<u32>` | Looks up and decodes a `u32` at a dot-separated object path. |
|
||||
| `std.json.pathBool(bytes, path)` | `Maybe<Bool>` | Looks up and decodes a bool at a dot-separated object path. |
|
||||
| `std.json.stringDecode(buffer, value)` | `Maybe<Span<u8>>` | Decodes a JSON string value, including Unicode escapes as UTF-8, into caller storage. |
|
||||
| `std.json.string(buffer, bytes, key)` | `Maybe<Span<u8>>` | Looks up and decodes a top-level string field. |
|
||||
| `std.json.u32(bytes, key)` | `Maybe<u32>` | Looks up and decodes a top-level unsigned integer field. |
|
||||
| `std.json.bool(bytes, key)` | `Maybe<Bool>` | Looks up and decodes a top-level boolean field. |
|
||||
| `std.json.writeStringBytes(buffer, text)` | `Maybe<Span<u8>>` | Writes an escaped JSON string from byte input. |
|
||||
| `std.json.writeObject1String(buffer, key, value)` | `Maybe<Span<u8>>` | Writes a one-field object with a string value. |
|
||||
| `std.json.writeObject1U32(buffer, key, value)` | `Maybe<Span<u8>>` | Writes a one-field object with a `u32` value. |
|
||||
| `std.json.writeObject1Bool(buffer, key, value)` | `Maybe<Span<u8>>` | Writes a one-field object with a bool value. |
|
||||
| `std.json.writeFieldRaw(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one object field from a key and validated raw JSON value. |
|
||||
| `std.json.writeFieldString(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one object field with an escaped string value. |
|
||||
| `std.json.writeFieldU32(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one object field with a `u32` value. |
|
||||
| `std.json.writeFieldBool(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one object field with a bool value. |
|
||||
| `std.json.writeObject2Fields(buffer, field0, field1)` | `Maybe<Span<u8>>` | Writes a two-field object from field fragments and validates the final object. |
|
||||
| `std.json.writeObject2StringField(buffer, key, value, field1)` | `Maybe<Span<u8>>` | Writes a two-field object from a string field and a prebuilt field fragment. |
|
||||
| `std.json.writeObject2U32Field(buffer, key, value, field1)` | `Maybe<Span<u8>>` | Writes a two-field object from a `u32` field and a prebuilt field fragment. |
|
||||
| `std.json.writeObject2BoolField(buffer, key, value, field1)` | `Maybe<Span<u8>>` | Writes a two-field object from a bool field and a prebuilt field fragment. |
|
||||
| `std.json.writeArray2Strings(buffer, value0, value1)` | `Maybe<Span<u8>>` | Writes a two-item array with escaped string values. |
|
||||
| `std.json.writeArray2U32(buffer, value0, value1)` | `Maybe<Span<u8>>` | Writes a two-item array with `u32` values. |
|
||||
| `std.json.writeArray2Bools(buffer, value0, value1)` | `Maybe<Span<u8>>` | Writes a two-item array with bool values. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: memory, parse, or alloc
|
||||
- allocation behavior: validation and streaming are allocation-free; parse uses explicit allocator only; direct writers write caller buffers
|
||||
- target support: target-neutral
|
||||
- error behavior: `Maybe` helpers return null on failure
|
||||
- diagnostics: `errorOffset`, `errorLine`, and `errorColumn` locate validation failures; valid JSON reports the end of input
|
||||
- ownership notes: parsed documents are owned by explicit allocator storage in this compiler slice
|
||||
- examples: `examples/std-data-formats.graph`, `examples/std-json-bytes.graph`, `conformance/native/pass/std-codec-json-url.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var arena_buf: [16]u8 = [0_u8; 16]
|
||||
var arena: FixedBufAlloc = std.mem.fixedBufAlloc(arena_buf)
|
||||
let parsed: Maybe<JsonDoc> = std.json.parse(arena, "{\"ok\":true}")
|
||||
var out: [16]u8 = [0_u8; 16]
|
||||
let text: Maybe<String> = std.json.writeString(out, "zero")
|
||||
if parsed.has && text.has && std.json.streamTokens("{\"ok\":true}") == 3 {
|
||||
check world.out.write("json ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Byte-span parse form:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let bytes: Span<u8> = std.mem.span("{\"ok\":1}")
|
||||
var arena_buf: [16]u8 = [0_u8; 16]
|
||||
var arena: FixedBufAlloc = std.mem.fixedBufAlloc(arena_buf)
|
||||
let parsed: Maybe<JsonDoc> = std.json.parseBytes(arena, bytes)
|
||||
if parsed.has && std.json.validateBytes(bytes) && std.json.streamTokensBytes(bytes) == 3 {
|
||||
check world.out.write("json bytes ok\n")
|
||||
return
|
||||
}
|
||||
check world.err.write("json bytes failed\n")
|
||||
}
|
||||
```
|
||||
|
||||
Top-level object lookup and caller-buffer writing:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let input: Span<u8> = "{\"name\":\"zero\",\"count\":42,\"ok\":true}"
|
||||
var name_buf: [8]u8 = [0_u8; 8]
|
||||
let name: Maybe<Span<u8>> = std.json.string(name_buf, input, "name")
|
||||
let count: Maybe<u32> = std.json.u32(input, "count")
|
||||
var count_field_buf: [24]u8 = [0_u8; 24]
|
||||
let count_field: Maybe<Span<u8>> = std.json.writeFieldU32(count_field_buf, "count", 42_u32)
|
||||
var out: [48]u8 = [0_u8; 48]
|
||||
var written: Maybe<Span<u8>> = null
|
||||
if count_field.has {
|
||||
written = std.json.writeObject2StringField(out, "name", "zero", count_field.value)
|
||||
}
|
||||
if name.has && count.has && written.has && std.json.validateError(written.value) == std.json.errorNone() {
|
||||
check world.out.write("json lookup ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Object and array cursors return borrowed raw JSON slices. Object keys are
|
||||
decoded into caller-owned storage:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let input: Span<u8> = "{\"user\":{\"name\":\"zero\",\"count\":42},\"items\":[1,2]}"
|
||||
let field_count: Maybe<usize> = std.json.objectFieldCount(input)
|
||||
var key_buf: [8]u8 = [0_u8; 8]
|
||||
let first_key: Maybe<Span<u8>> = std.json.objectKey(key_buf, input, 0)
|
||||
let items: Maybe<Span<u8>> = std.json.field(input, "items")
|
||||
var name_buf: [8]u8 = [0_u8; 8]
|
||||
let name: Maybe<Span<u8>> = std.json.pathString(name_buf, input, "user.name")
|
||||
if field_count.has && first_key.has && items.has && std.json.arrayCount(items.value).has && name.has {
|
||||
check world.out.write("json cursors ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
JSON validation diagnostics are allocation-free. Use `std.diag` when a
|
||||
human-readable file location is needed:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let input: Span<u8> = "{\n \"ok\": tru\n}"
|
||||
var location_buf: [24]u8 = [0_u8; 24]
|
||||
let location: Maybe<Span<u8>> = std.diag.formatOffsetLocation(location_buf, "config.json", input, std.json.errorOffset(input))
|
||||
if std.json.validateError(input) == std.json.errorInvalid() && std.json.errorLine(input) == 2 && location.has {
|
||||
check world.out.write(location.value)
|
||||
check world.out.write("\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Small array responses use the same caller-buffer pattern:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var out: [32]u8 = [0_u8; 32]
|
||||
let tags: Maybe<Span<u8>> = std.json.writeArray2Strings(out, "api", "agent")
|
||||
if tags.has {
|
||||
check world.out.write(tags.value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
JSON should not fake allocation-free semantics. Validation, field lookup,
|
||||
string decode, and writing stay allocation-free. String decode writes UTF-8 for
|
||||
Unicode escapes and rejects malformed surrogate pairs.
|
||||
|
||||
Parsing into an owned document requires an explicit allocator. The current
|
||||
`JsonDoc` value is opaque; examples inspect `Maybe.has` and use token streaming
|
||||
for allocation-free summaries. Field lookup is intentionally shallow: it reads
|
||||
top-level object fields and returns raw slices or typed scalar decodes. Object
|
||||
path lookup follows dot-separated object keys; array indexing remains explicit
|
||||
through `arrayValue`. When an object contains duplicate keys, name-based lookup
|
||||
returns the first matching value, while ordinal object cursors preserve the
|
||||
source order and expose every field. Validation diagnostics report byte offsets
|
||||
in the source payload; line and column helpers treat lines and columns as
|
||||
one-based byte positions.
|
||||
@@ -0,0 +1,65 @@
|
||||
## When To Use std.log
|
||||
|
||||
In Zerolang, use `std.log` for explicit-buffer structured log record formatting.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.log.levelDebug()` | `String` | Static `debug` level text. |
|
||||
| `std.log.levelInfo()` | `String` | Static `info` level text. |
|
||||
| `std.log.levelWarn()` | `String` | Static `warn` level text. |
|
||||
| `std.log.levelError()` | `String` | Static `error` level text. |
|
||||
| `std.log.message(buffer, level, message)` | `Maybe<Span<u8>>` | Writes one newline-terminated JSON Lines record with `level` and `message`. |
|
||||
| `std.log.keyValue(buffer, level, key, value)` | `Maybe<Span<u8>>` | Writes one newline-terminated JSON Lines record with `level`, `key`, and `value`. |
|
||||
| `std.log.stringField(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one JSON string field fragment. |
|
||||
| `std.log.messageField(buffer, level, message, field)` | `Maybe<Span<u8>>` | Writes one JSON Lines message record with one field fragment. |
|
||||
| `std.log.redacted(buffer, level, key)` | `Maybe<Span<u8>>` | Writes one JSON Lines record marking a field name as redacted. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: memory; `messageField` also validates JSON field fragments
|
||||
- allocation behavior: writes caller buffer; no hidden heap
|
||||
- target support: target-neutral
|
||||
- error behavior: returns `null` when the buffer is too small or a value cannot be JSON-escaped
|
||||
- ownership notes: borrows returned bytes from caller-owned storage
|
||||
- example: `examples/std-testing-log.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [128]u8 = [0_u8; 128]
|
||||
var field_storage: [64]u8 = [0_u8; 64]
|
||||
let field: Maybe<Span<u8>> = std.log.stringField(field_storage, "event", "startup")
|
||||
if field.has {
|
||||
let entry: Maybe<Span<u8>> = std.log.messageField(storage, std.log.levelInfo(), "started", field.value)
|
||||
if entry.has {
|
||||
check world.out.write(entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```json
|
||||
{"level":"info","message":"started","event":"startup"}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.log` is a formatting surface, not a global logger. The caller owns the
|
||||
storage and chooses where to write the resulting span, such as `World.out`, a
|
||||
file handle, or a test assertion.
|
||||
|
||||
Records use JSON Lines so downstream tools can parse them without guessing at
|
||||
ad hoc separators. The helpers write exactly one record and include a trailing
|
||||
newline.
|
||||
|
||||
`messageField` validates the final JSON object before returning it. Build field
|
||||
fragments with `stringField` unless the field fragment is already known to be
|
||||
valid JSON.
|
||||
|
||||
Use `redacted` for logs that need to state which field was intentionally
|
||||
withheld without writing the sensitive value.
|
||||
@@ -0,0 +1,86 @@
|
||||
## When To Use std.math
|
||||
|
||||
In Zerolang, use `std.math` for pure fixed-width integer helpers, checked/saturating
|
||||
arithmetic, and small number-theory routines.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.math.minU32(left, right)` | `u32` | Returns the smaller unsigned value. |
|
||||
| `std.math.minI32(left, right)` | `i32` | Returns the smaller signed 32-bit value. |
|
||||
| `std.math.minUsize(left, right)` | `usize` | Returns the smaller pointer-width unsigned value. |
|
||||
| `std.math.minI64(left, right)` | `i64` | Returns the smaller signed 64-bit value. |
|
||||
| `std.math.minU64(left, right)` | `u64` | Returns the smaller unsigned 64-bit value. |
|
||||
| `std.math.maxU32(left, right)` | `u32` | Returns the larger unsigned value. |
|
||||
| `std.math.maxI32(left, right)` | `i32` | Returns the larger signed 32-bit value. |
|
||||
| `std.math.maxUsize(left, right)` | `usize` | Returns the larger pointer-width unsigned value. |
|
||||
| `std.math.maxI64(left, right)` | `i64` | Returns the larger signed 64-bit value. |
|
||||
| `std.math.maxU64(left, right)` | `u64` | Returns the larger unsigned 64-bit value. |
|
||||
| `std.math.clampU32(value, low, high)` | `u32` | Clamps between the two bounds; swapped bounds are normalized. |
|
||||
| `std.math.clampI32(value, low, high)` | `i32` | Clamps a signed 32-bit value; swapped bounds are normalized. |
|
||||
| `std.math.clampUsize(value, low, high)` | `usize` | Clamps a pointer-width unsigned value; swapped bounds are normalized. |
|
||||
| `std.math.clampI64(value, low, high)` | `i64` | Clamps a signed 64-bit value; swapped bounds are normalized. |
|
||||
| `std.math.clampU64(value, low, high)` | `u64` | Clamps an unsigned 64-bit value; swapped bounds are normalized. |
|
||||
| `std.math.absI32(value)` | `u32` | Returns the unsigned magnitude of a signed 32-bit value. |
|
||||
| `std.math.absI64(value)` | `u64` | Returns the unsigned magnitude of a signed 64-bit value. |
|
||||
| `std.math.checkedAddU32(left, right)` | `Maybe<u32>` | Adds only when the result fits in `u32`. |
|
||||
| `std.math.checkedSubU32(left, right)` | `Maybe<u32>` | Subtracts only when the result fits in `u32`. |
|
||||
| `std.math.checkedMulU32(left, right)` | `Maybe<u32>` | Multiplies only when the result fits in `u32`. |
|
||||
| `std.math.checkedAddUsize(left, right)` | `Maybe<usize>` | Adds only when the result fits in `usize`. |
|
||||
| `std.math.checkedSubUsize(left, right)` | `Maybe<usize>` | Subtracts only when the result fits in `usize`. |
|
||||
| `std.math.checkedMulUsize(left, right)` | `Maybe<usize>` | Multiplies only when the result fits in `usize`. |
|
||||
| `std.math.checkedAddI32(left, right)` | `Maybe<i32>` | Adds only when the result fits in `i32`. |
|
||||
| `std.math.checkedSubI32(left, right)` | `Maybe<i32>` | Subtracts only when the result fits in `i32`. |
|
||||
| `std.math.checkedMulI32(left, right)` | `Maybe<i32>` | Multiplies only when the result fits in `i32`. |
|
||||
| `std.math.saturatingAddU32(left, right)` | `u32` | Adds and clamps overflow to `u32` max. |
|
||||
| `std.math.saturatingSubU32(left, right)` | `u32` | Subtracts and clamps underflow to `0`. |
|
||||
| `std.math.saturatingMulU32(left, right)` | `u32` | Multiplies and clamps overflow to `u32` max. |
|
||||
| `std.math.saturatingAddUsize(left, right)` | `usize` | Adds and clamps overflow to `usize` max. |
|
||||
| `std.math.saturatingSubUsize(left, right)` | `usize` | Subtracts and clamps underflow to `0`. |
|
||||
| `std.math.saturatingMulUsize(left, right)` | `usize` | Multiplies and clamps overflow to `usize` max. |
|
||||
| `std.math.saturatingAddI32(left, right)` | `i32` | Adds and clamps overflow to the nearest `i32` bound. |
|
||||
| `std.math.saturatingSubI32(left, right)` | `i32` | Subtracts and clamps overflow to the nearest `i32` bound. |
|
||||
| `std.math.saturatingMulI32(left, right)` | `i32` | Multiplies and clamps overflow to the nearest `i32` bound. |
|
||||
| `std.math.gcdU32(left, right)` | `u32` | Euclidean greatest common divisor. |
|
||||
| `std.math.lcmU32(left, right)` | `u32` | Least common multiple; returns `0` when either input is `0`. |
|
||||
| `std.math.checkedLcmU32(left, right)` | `Maybe<u32>` | Least common multiple only when the result fits in `u32`. |
|
||||
| `std.math.powU32(base, exponent)` | `u32` | Fixed-width exponentiation by squaring. |
|
||||
| `std.math.checkedPowU32(base, exponent)` | `Maybe<u32>` | Exponentiation only when the result fits in `u32`. |
|
||||
| `std.math.modPowU32(base, exponent, modulus)` | `u32` | Modular exponentiation; returns `0` for modulus `0`. |
|
||||
| `std.math.isPrimeU32(value)` | `Bool` | Trial division primality for unsigned integers. |
|
||||
| `std.math.isEvenU32(value)` | `Bool` | Reports whether a `u32` value is even. |
|
||||
| `std.math.isOddU32(value)` | `Bool` | Reports whether a `u32` value is odd. |
|
||||
| `std.math.sqrtFloorU32(value)` | `u32` | Integer square root rounded down. |
|
||||
| `std.math.factorialU32(value)` | `Maybe<u32>` | Factorial only when the result fits in `u32`. |
|
||||
| `std.math.binomialU32(n, k)` | `Maybe<u32>` | Binomial coefficient only when the result fits in `u32`. |
|
||||
| `std.math.divisorCountU32(value)` | `u32` | Counts positive divisors; returns `0` for `0`. |
|
||||
| `std.math.properDivisorSumU32(value)` | `u32` | Sums positive divisors smaller than `value`. |
|
||||
|
||||
Current scope:
|
||||
|
||||
- Helpers are pure, target-neutral fixed-width integer operations.
|
||||
- Checked helpers return `Maybe<T>` instead of wrapping or trapping.
|
||||
- Saturating helpers clamp to documented integer bounds.
|
||||
- The module does not provide floating-point math, big integers, or arbitrary-precision number theory.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
if std.math.gcdU32(84, 30) == 6 && std.math.isPrimeU32(31) {
|
||||
check world.out.write("math helper ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.math` does not allocate and does not require a hosted runtime capability.
|
||||
Names include the integer width because Zero does not overload standard-library
|
||||
helpers by argument type.
|
||||
|
||||
Number-theory helpers are intentionally simple and deterministic. They are
|
||||
suitable for small fixed-width tasks, examples, and compiler-portable checks.
|
||||
Large-number algorithms should be added as explicit APIs with documented bounds
|
||||
instead of hidden heap allocation or implicit widening.
|
||||
@@ -0,0 +1,172 @@
|
||||
## When To Use std.mem
|
||||
|
||||
In Zerolang, use `std.mem` for spans, clamped span views, copy/fill,
|
||||
fixed-buffer allocators, explicit byte buffers, and memory-budget-visible
|
||||
collection foundations.
|
||||
|
||||
Runnable today:
|
||||
|
||||
The item-generic helpers currently support these direct-backed scalar element
|
||||
types: `Bool`, `u8`, `u16`, `usize`, `i32`, `u32`, `i64`, and `u64`.
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.mem.copy(dst, src)` | `usize` | Copies from `Span<u8>` into caller-owned `MutSpan<u8>` storage and returns the copied byte count. |
|
||||
| `std.mem.copyItems(dst, src)` | `usize` | Copies matching scalar `Span<T>` items into caller-owned mutable item storage and returns the copied item count. |
|
||||
| `std.mem.fill(dst, value)` | `usize` | Fills caller-owned `MutSpan<u8>` storage with a `u8` byte and returns the filled byte count. |
|
||||
| `std.mem.fillItems(dst, value)` | `usize` | Fills caller-owned mutable scalar item storage with a matching `T` value and returns the filled item count. |
|
||||
| `std.mem.eql(a, b)` | `Bool` | Compares string-backed byte inputs. |
|
||||
| `std.mem.span(value)` | `Span<u8>` | Builds a native `Span<u8>` view over a string literal. |
|
||||
| `std.mem.contains(items, needle)` | `Bool` | Searches readable contiguous non-owned scalar `T` storage for a matching value. |
|
||||
| `std.mem.compareI32(left, right)` | `i32` | Lexicographically compares two `Span<i32>` values and returns `-1`, `0`, or `1`. |
|
||||
| `std.mem.compareU8(left, right)` | `i32` | Lexicographically compares two `Span<u8>` values and returns `-1`, `0`, or `1`. |
|
||||
| `std.mem.compareBytes(left, right)` | `i32` | Alias-style byte lexicographic comparison for `Span<u8>` values. |
|
||||
| `std.mem.compareU32(left, right)` | `i32` | Lexicographically compares two `Span<u32>` values and returns `-1`, `0`, or `1`. |
|
||||
| `std.mem.compareUsize(left, right)` | `i32` | Lexicographically compares two `Span<usize>` values and returns `-1`, `0`, or `1`. |
|
||||
| `std.mem.startsWith(items, prefix)` | `Bool` | Checks whether a scalar span begins with a matching prefix span. |
|
||||
| `std.mem.endsWith(items, suffix)` | `Bool` | Checks whether a scalar span ends with a matching suffix span. |
|
||||
| `std.mem.splitBefore(items, delimiter)` | `Span<T>` | Returns the read-only scalar item prefix before the first delimiter, or the full span when absent. |
|
||||
| `std.mem.splitAfter(items, delimiter)` | `Span<T>` | Returns the read-only scalar item suffix after the first delimiter, or an empty span when absent. |
|
||||
| `std.mem.isEmpty(items)` | `Bool` | Reports whether readable contiguous scalar item storage has zero items. |
|
||||
| `std.mem.chunkCount(items, chunkSize)` | `usize` | Returns the number of non-overlapping chunks needed to cover the span, or `0` when `chunkSize` is zero. |
|
||||
| `std.mem.chunk(items, chunkIndex, chunkSize)` | `Span<T>` | Returns a clamped read-only scalar item chunk by zero-based chunk index. |
|
||||
| `std.mem.windowCount(items, windowSize)` | `usize` | Returns the number of overlapping fixed-size windows available in a span, or `0` when the size is zero or larger than the span. |
|
||||
| `std.mem.window(items, windowIndex, windowSize)` | `Span<T>` | Returns an overlapping read-only scalar item window by zero-based window index. |
|
||||
| `std.mem.advance(items, cursor, count)` | `usize` | Returns a clamped cursor advanced by at most `count` items. |
|
||||
| `std.mem.cursorDone(items, cursor)` | `Bool` | Reports whether a cursor is at or past the end of a span. |
|
||||
| `std.mem.remaining(items, cursor)` | `Span<T>` | Returns the clamped read-only scalar item view from `cursor` to the end. |
|
||||
| `std.mem.cursorChunk(items, cursor, count)` | `Span<T>` | Returns a clamped read-only scalar item window beginning at `cursor`. |
|
||||
| `std.mem.prefix(items, count)` | `Span<T>` | Returns a clamped read-only scalar item prefix view. |
|
||||
| `std.mem.dropPrefix(items, count)` | `Span<T>` | Returns a clamped read-only scalar item view after the first `count` items. |
|
||||
| `std.mem.suffix(items, count)` | `Span<T>` | Returns a clamped read-only scalar item suffix view. |
|
||||
| `std.mem.dropSuffix(items, count)` | `Span<T>` | Returns a clamped read-only scalar item view before the last `count` items. |
|
||||
| `std.mem.slice(items, start, count)` | `Span<T>` | Returns a clamped read-only scalar item window beginning at `start`. |
|
||||
| `std.mem.len(bytes)` | `usize` | Returns the length of a fixed array, `Span<T>`, or `MutSpan<T>`. |
|
||||
| `std.mem.get(bytes, index)` | `Maybe<T>` | Reads one indexed element from an array/span-like value when the index is in bounds. |
|
||||
| `std.mem.eqlBytes(a, b)` | `Bool` | Compares two `Span<T>`/`MutSpan<T>` values with the same element type. |
|
||||
| `std.mem.nullAlloc()` | `NullAlloc` | Creates an allocator that always returns `null`, useful for proving code does not allocate. |
|
||||
| `std.mem.fixedBufAlloc(buffer)` | `FixedBufAlloc` | Creates a mutable fixed-buffer allocator from caller-owned `MutSpan<u8>` bytes. |
|
||||
| `std.mem.arena(buffer)` | `FixedBufAlloc` | Arena-style alias over the fixed-buffer allocator model; `reset` rewinds the caller-owned storage. |
|
||||
| `std.mem.pageAlloc()` | `PageAlloc` | Explicit host allocator handle metadata; it never creates an ambient global allocator. |
|
||||
| `std.mem.generalAlloc()` | `GeneralAlloc` | Explicit general allocator handle metadata; callers still pass allocator state deliberately. |
|
||||
| `std.mem.allocBytes(alloc, len)` | `Maybe<MutSpan<u8>>` | Allocates bytes from `NullAlloc` or a mutable `FixedBufAlloc` binding. |
|
||||
| `std.mem.byteBuf(alloc, len)` | `Maybe<owned<ByteBuf>>` | Creates an owned byte buffer backed by explicit caller-provided allocator storage. |
|
||||
| `std.mem.bufBytes(&buf)` | `MutSpan<u8>` | Borrows writable bytes from an owned `ByteBuf`. |
|
||||
| `std.mem.bufLen(&buf)` | `usize` | Returns the live length of a `ByteBuf`. |
|
||||
| `std.mem.reset(&mut arena)` | `Void` | Resets caller-owned arena/fixed-buffer allocation state. |
|
||||
| `std.mem.capacity(arena)` | `usize` | Reports fixed-buffer capacity. |
|
||||
| `std.mem.vec(storage)` | `Vec` | Monomorphic byte vector over caller-owned mutable storage. |
|
||||
| `std.mem.vecPush(&mut vec, value)` | `Bool` | Appends one byte when capacity remains; returns `false` instead of growing implicitly. |
|
||||
| `std.mem.vecBytes(&vec)` | `Span<u8>` | Borrows the live bytes in the vector without copying. |
|
||||
| `std.mem.vecGet(&vec, index)` | `Maybe<u8>` | Reads one live vector byte when `index` is in bounds. |
|
||||
| `std.mem.vecSet(&mut vec, index, value)` | `Bool` | Replaces one live vector byte when `index` is in bounds; returns `false` out of bounds. |
|
||||
| `std.mem.vecClear(&mut vec)` | `usize` | Resets live length to zero and keeps caller-owned storage available for reuse. |
|
||||
| `std.mem.vecPop(&mut vec)` | `Bool` | Removes one live byte when the vector is non-empty. |
|
||||
| `std.mem.vecTruncate(&mut vec, len)` | `usize` | Shrinks the live length to at most `len` and returns the resulting length. |
|
||||
| `std.mem.vecRemoveSwap(&mut vec, index)` | `Bool` | Removes one live byte by replacing it with the last live byte. Returns `false` out of bounds. |
|
||||
| `std.mem.vecIndex(&vec, value)` | `usize` | Returns the first live index for `value`, or the current live length when absent. |
|
||||
| `std.mem.vecContains(&vec, value)` | `Bool` | Reports whether a byte value is present in the live vector prefix. |
|
||||
| `std.mem.vecInsertUnique(&mut vec, value)` | `Bool` | Appends `value` only when it is absent and capacity remains. |
|
||||
| `std.mem.vecRemoveValue(&mut vec, value)` | `Bool` | Swap-removes the first matching live byte by value. |
|
||||
| `std.mem.vecLen(&vec)` | `usize` | Reports current vector length. |
|
||||
| `std.mem.vecCapacity(&vec)` | `usize` | Reports caller-provided vector capacity. |
|
||||
| `std.mem.vecRemaining(&vec)` | `usize` | Reports remaining byte-vector capacity. Returns `0` when the vector is full. |
|
||||
| `std.mem.vecIsEmpty(&vec)` | `Bool` | Reports whether the vector has no live bytes. |
|
||||
| `std.mem.vecIsFull(&vec)` | `Bool` | Reports whether the vector has no remaining capacity. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
type SliceView {
|
||||
bytes: Span<u8>,
|
||||
values: Span<i32>,
|
||||
}
|
||||
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let bytes: Span<u8> = std.mem.span("zero-memory")
|
||||
let same: Span<u8> = std.mem.span("zero-memory")
|
||||
var scratch: [11]u8 = [0_u8; 11]
|
||||
let copied: usize = std.mem.copy(scratch, bytes)
|
||||
var ints: [3]i32 = [1, 2, 3]
|
||||
let intSpan: MutSpan<i32> = ints
|
||||
let filled: usize = std.mem.fillItems(intSpan, 7)
|
||||
let prefix: Span<i32> = std.mem.prefix(intSpan, 2)
|
||||
let suffix: Span<i32> = std.mem.suffix(intSpan, 2)
|
||||
let middle: Span<i32> = std.mem.slice(intSpan, 1, 1)
|
||||
let before: Span<i32> = std.mem.splitBefore(intSpan, 7)
|
||||
let after: Span<i32> = std.mem.splitAfter(intSpan, 7)
|
||||
let chunk: Span<i32> = std.mem.chunk(intSpan, 1_usize, 2_usize)
|
||||
let sliding: Span<i32> = std.mem.window(intSpan, 1_usize, 2_usize)
|
||||
let cursor: usize = std.mem.advance(intSpan, 0_usize, 2_usize)
|
||||
let rest: Span<i32> = std.mem.remaining(intSpan, cursor)
|
||||
let view: SliceView = SliceView { bytes: bytes, values: intSpan }
|
||||
let ordered: Bool = std.mem.compareI32(prefix, suffix) == 0
|
||||
let starts: Bool = std.mem.startsWith(view.bytes, std.mem.span("zero"))
|
||||
let ends: Bool = std.mem.endsWith(view.bytes, std.mem.span("memory"))
|
||||
if copied == 11 && filled == 3 && ordered && starts && ends && std.mem.len(view.bytes) == 11 && std.mem.eqlBytes(view.bytes, same) && std.mem.len(view.values) == 3 && std.mem.contains(view.values, 7) && std.mem.isEmpty(before) && std.mem.len(after) == 2 && std.mem.len(prefix) == 2 && std.mem.len(suffix) == 2 && std.mem.len(middle) == 1 && std.mem.len(chunk) == 1 && std.mem.len(sliding) == 2 && std.mem.len(rest) == 1 && !std.mem.cursorDone(intSpan, cursor) {
|
||||
check world.out.write("memory type forms runnable\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Allocator Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
var alloc: FixedBufAlloc = std.mem.fixedBufAlloc(storage)
|
||||
let bytes: Maybe<MutSpan<u8>> = std.mem.allocBytes(alloc, 4)
|
||||
if bytes.has {
|
||||
bytes.value[0] = 90
|
||||
check world.out.write("fixed buffer allocated\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none beyond writes performed by caller code.
|
||||
|
||||
Allocation behavior:
|
||||
|
||||
- `NullAlloc` always returns `null`.
|
||||
- `FixedBufAlloc` and `Arena` return `MutSpan<u8>` views into caller-owned
|
||||
storage.
|
||||
- `ByteBuf` owns a slice of explicit allocator storage and never reaches for a
|
||||
global heap.
|
||||
|
||||
Use `std.mem.allocBytes(alloc, capacity)` when a byte-oriented API needs
|
||||
allocator-backed mutable storage directly. `std.collections.fixedSet`,
|
||||
`std.collections.fixedDeque`, and `std.collections.fixedMap` can use those
|
||||
returned spans as caller-owned backing storage.
|
||||
|
||||
Ownership: returned spans borrow from the original fixed buffer; no heap
|
||||
ownership is created.
|
||||
|
||||
Target support: current compiler targets. Direct native builds lower
|
||||
`FixedBufAlloc` locals only; `PageAlloc`, `GeneralAlloc`, and `NullAlloc`
|
||||
locals type-check but fail `zero build` with a `BLD004` diagnostic that points
|
||||
back to `std.mem.fixedBufAlloc`.
|
||||
|
||||
## Reporting Contract
|
||||
|
||||
`zero mem --json [graph-input]` reports the allocator contract in machine-readable form:
|
||||
|
||||
- `memoryBudgets`: stack, static, heap, arena, fixed-buffer, collection-capacity, allocator-capacity, requested-allocation, and linear-memory floor budgets.
|
||||
- `allocatorFacts`: `NullAlloc`, `FixedBufAlloc`, `Arena`, `PageAlloc`, and
|
||||
`GeneralAlloc` usage, capacity, failure behavior, and
|
||||
hidden-global-allocator status.
|
||||
- `allocationInstrumentation`: pay-as-used hooks for attempts, successes, failures, requested bytes, granted bytes, and peak live bytes.
|
||||
- `collectionFacts`: fixed-capacity `Vec`, fixed-storage `std.collections`
|
||||
helpers, and owned `ByteBuf`, including growth/failure/cleanup behavior.
|
||||
- `safetyFacts`: the selected profile plus the current bounds,
|
||||
initialization, aliasing, lifetime, ownership, span, MIR, literal integer
|
||||
range-check, runtime arithmetic, and unchecked-surface facts.
|
||||
|
||||
All heap budgets are explicit. A program that only uses `std.mem` remains at
|
||||
`heapBytes: 0`, `globalHeapBytes: 0`, and `hiddenHeapAllocation: false` unless
|
||||
an allocator API documents otherwise.
|
||||
|
||||
## Design Notes
|
||||
|
||||
No standard collection may silently allocate from a global heap. Heap-owning APIs
|
||||
will require an allocator capability and document ownership, capacity, and
|
||||
cleanup.
|
||||
@@ -0,0 +1,47 @@
|
||||
## When To Use std.net
|
||||
|
||||
In Zerolang, use `std.net` for network capability metadata, local address construction,
|
||||
timeouts, and bootstrap client/listener handles.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.net.host()` | `Net` | Creates the hosted network capability. |
|
||||
| `std.net.address(host, port)` | `Address` | Builds an address value without allocation. |
|
||||
| `std.net.dnsName(address)` | `String` | Reads the address host name. |
|
||||
| `std.net.withTimeout(address, duration)` | `Address` | Returns address metadata with a timeout. |
|
||||
| `std.net.localhost(port)` | `Address` | Builds a `localhost` address with the requested port. |
|
||||
| `std.net.loopback(port)` | `Address` | Builds a `127.0.0.1` loopback address with the requested port. |
|
||||
| `std.net.connect(net, address)` | `Maybe<Conn>` | Returns a bootstrap connection handle when available. |
|
||||
| `std.net.listen(net, address)` | `Maybe<Listener>` | Returns a bootstrap listener handle when available. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: net
|
||||
- allocation behavior: no allocation
|
||||
- target support: address helpers are target-neutral; host/connect/listen require a net-capable target
|
||||
- error behavior: connection helpers return `Maybe`
|
||||
- ownership notes: no stream ownership transfer in the current handle model
|
||||
- example: `conformance/native/pass/std-net-http-breadth.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let net: Net = std.net.host()
|
||||
let addr: Address = std.net.withTimeout(std.net.localhost(8080_u16), std.time.ms(250))
|
||||
let loopback: Address = std.net.loopback(8080_u16)
|
||||
let conn: Maybe<Conn> = std.net.connect(net, addr)
|
||||
if conn.has && std.mem.eql(std.net.dnsName(addr), "localhost") && std.mem.eql(std.net.dnsName(loopback), "127.0.0.1") {
|
||||
check world.out.write("net ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.net` exposes network capability metadata and bootstrap handles. Current
|
||||
fixtures expect connection and listener handles to be absent. It does not
|
||||
provide socket read/write APIs in the current public surface. Outbound HTTP is
|
||||
exposed through `std.http.fetch(...)` rather than through raw sockets.
|
||||
@@ -0,0 +1,78 @@
|
||||
## When To Use std.parse
|
||||
|
||||
In Zerolang, use `std.parse` for allocation-free byte scanners and scalar parsers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.parse.isAsciiDigit(value)` | `Bool` | Checks whether the first byte is `0` through `9`. |
|
||||
| `std.parse.isAsciiAlpha(value)` | `Bool` | Checks whether the first byte is ASCII alphabetic. |
|
||||
| `std.parse.isIdentifierStart(value)` | `Bool` | Checks whether the first byte can start a Zero identifier. |
|
||||
| `std.parse.isWhitespace(value)` | `Bool` | Checks ASCII whitespace. |
|
||||
| `std.parse.scanDigits(value)` | `usize` | Counts a leading run of ASCII digits. |
|
||||
| `std.parse.scanIdentifier(value)` | `usize` | Counts a leading identifier token. |
|
||||
| `std.parse.scanWhitespace(value)` | `usize` | Counts leading spaces, tabs, line feeds, and carriage returns. |
|
||||
| `std.parse.scanUntilByte(value, byte)` | `usize` | Returns the first matching byte index or the input length. |
|
||||
| `std.parse.tokenAscii(value)` | `Span<u8>` | Borrows the first non-whitespace ASCII token. |
|
||||
| `std.parse.parseBool(value)` | `Maybe<Bool>` | Parses `true` or `false`. |
|
||||
| `std.parse.parseDuration(value)` | `Maybe<Duration>` | Parses signed `ns`, `us`, `ms`, `s`, `m`, and `h` duration components such as `1h30m`. |
|
||||
| `std.parse.parseByteSize(value)` | `Maybe<usize>` | Parses bytes plus `KB`, `MB`, `GB`, `KiB`, `MiB`, and `GiB` suffixes. |
|
||||
| `std.parse.parseI32(value)` | `Maybe<i32>` | Parses a full signed 32-bit decimal value. |
|
||||
| `std.parse.parseI32Base(value, base)` | `Maybe<i32>` | Parses a full signed 32-bit value in base 2 through 36. |
|
||||
| `std.parse.parseI32Prefix(value)` | `Maybe<i32>` | Parses signed decimal, `0x`, `0o`, or `0b` input. |
|
||||
| `std.parse.parseI64(value)` | `Maybe<i64>` | Parses a full signed 64-bit decimal value. |
|
||||
| `std.parse.parseI64Base(value, base)` | `Maybe<i64>` | Parses a full signed 64-bit value in base 2 through 36. |
|
||||
| `std.parse.parseI64Prefix(value)` | `Maybe<i64>` | Parses signed decimal, `0x`, `0o`, or `0b` input. |
|
||||
| `std.parse.parseU8(value)` | `Maybe<u8>` | Parses a full decimal byte value. |
|
||||
| `std.parse.parseU16(value)` | `Maybe<u16>` | Parses a full decimal unsigned 16-bit value. |
|
||||
| `std.parse.parseU32(value)` | `Maybe<u32>` | Parses a full decimal unsigned 32-bit value. |
|
||||
| `std.parse.parseU32Base(value, base)` | `Maybe<u32>` | Parses a full unsigned 32-bit value in base 2 through 36. |
|
||||
| `std.parse.parseU32Prefix(value)` | `Maybe<u32>` | Parses unsigned decimal, `0x`, `0o`, or `0b` input. |
|
||||
| `std.parse.parseU64(value)` | `Maybe<u64>` | Parses a full decimal unsigned 64-bit value. |
|
||||
| `std.parse.parseU64Base(value, base)` | `Maybe<u64>` | Parses a full unsigned 64-bit value in base 2 through 36. |
|
||||
| `std.parse.parseU64Prefix(value)` | `Maybe<u64>` | Parses unsigned decimal, `0x`, `0o`, or `0b` input. |
|
||||
| `std.parse.parseUsize(value)` | `Maybe<usize>` | Parses a full decimal `usize` value. |
|
||||
| `std.parse.parseUsizeBase(value, base)` | `Maybe<usize>` | Parses a full `usize` value in base 2 through 36. |
|
||||
| `std.parse.parseUsizePrefix(value)` | `Maybe<usize>` | Parses `usize` decimal, `0x`, `0o`, or `0b` input. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Source position and span types.
|
||||
- Rich cursor objects beyond the current allocation-free scanner primitives.
|
||||
- Token and diagnostic data shared by language and data parsers.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
use std.parse
|
||||
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let digit: Bool = std.parse.isAsciiDigit("7")
|
||||
let ident: Bool = std.parse.isIdentifierStart("_")
|
||||
let scanned: usize = std.parse.scanDigits("123abc")
|
||||
let parsed: Maybe<u16> = std.parse.parseU16("8080")
|
||||
let signed: Maybe<i32> = std.parse.parseI32Prefix("-0x2a")
|
||||
let signed64: Maybe<i64> = std.parse.parseI64("-9223372036854775808")
|
||||
let hex: Maybe<u32> = std.parse.parseU32Base("ff", 16_u32)
|
||||
let hex64: Maybe<u64> = std.parse.parseU64Prefix("0xffffffffffffffff")
|
||||
let duration: Maybe<Duration> = std.parse.parseDuration("1h30m")
|
||||
let size: Maybe<usize> = std.parse.parseByteSize("2MiB")
|
||||
let token: Span<u8> = std.parse.tokenAscii(" zero text")
|
||||
if digit && ident && scanned == 3 && parsed.has && parsed.value == 8080 && signed.has && signed.value == -42 && signed64.has && signed64.value == 0 - 9223372036854775807 - 1 && hex.has && hex.value == 255_u32 && hex64.has && hex64.value == 18446744073709551615_u64 && duration.has && std.time.asSecondsFloor(duration.value) == 5400_i64 && size.has && size.value == 2097152 && std.mem.eql(token, "zero") {
|
||||
check world.out.write("parse primitives ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
The module stays byte-oriented so compiler, config, and codec code can parse
|
||||
without Unicode scalar handling or heap allocation. Public helpers accept byte
|
||||
spans, so callers can parse string literals, fixed arrays, and runtime buffers.
|
||||
|
||||
Integer parsers return `Maybe<T>` instead of allocating diagnostics. Base parsers
|
||||
accept bases 2 through 36, consume the full input, and reject overflow. Prefix
|
||||
parsers recognize decimal by default plus `0x`, `0o`, and `0b` for 32-bit,
|
||||
64-bit, and `usize` widths. Duration and byte-size parsers also consume the
|
||||
full input and reject overflow.
|
||||
@@ -0,0 +1,51 @@
|
||||
## When To Use std.path
|
||||
|
||||
In Zerolang, use `std.path` for lexical path operations that borrow from input
|
||||
paths or write into caller-owned buffers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.path.basename(path)` | `String` | Borrows the final lexical component of `path`. |
|
||||
| `std.path.dirname(path)` | `String` | Borrows or returns the lexical parent portion of `path`. |
|
||||
| `std.path.extension(path)` | `String` | Borrows the suffix after the last `.` in the final component. |
|
||||
| `std.path.stem(path)` | `String` | Borrows the final component without its extension. |
|
||||
| `std.path.splitDir(path)` | `String` | Borrows the directory side of the final split. |
|
||||
| `std.path.splitBase(path)` | `String` | Borrows the basename side of the final split. |
|
||||
| `std.path.isAbs(path)` | `Bool` | Returns true for paths that begin with a path separator. |
|
||||
| `std.path.componentCount(path)` | `usize` | Counts non-empty lexical path components. |
|
||||
| `std.path.component(path, index)` | `Maybe<String>` | Borrows one non-empty lexical component by index. |
|
||||
| `std.path.abs(buffer, base, target)` | `Maybe<String>` | Copies `target` when already absolute, or joins `base` and `target` into caller storage. |
|
||||
| `std.path.join(buffer, left, right)` | `Maybe<String>` | Joins two path fragments into caller-provided fixed buffer storage. |
|
||||
| `std.path.normalize(buffer, path)` | `Maybe<String>` | Collapses repeated `/`, `.`, and lexical `..` segments into caller-provided storage. |
|
||||
| `std.path.relative(buffer, base, target)` | `Maybe<String>` | Produces a target-relative lexical path when possible, or copies `target`. |
|
||||
|
||||
Current scope:
|
||||
|
||||
- Helpers are target-neutral lexical operations over `/` and `\` separators.
|
||||
- Buffer-writing helpers return `null` when caller storage is too small.
|
||||
- The module does not implement platform-specific path rules, drive prefixes, or filesystem access.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [64]u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
let path: Maybe<String> = std.path.join(storage, ".zero", "example.txt")
|
||||
if path.has {
|
||||
check world.out.write(path.value)
|
||||
check world.out.write("\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.path.basename`, `dirname`, `extension`, `stem`, `splitDir`, `splitBase`,
|
||||
and `component` return borrowed views into the input path. `std.path.abs`,
|
||||
`join`, `normalize`, and `relative` write into caller storage and return `null`
|
||||
when the buffer is too small. They do not allocate.
|
||||
|
||||
The current behavior uses `/` as the portable package/example separator. These
|
||||
helpers are lexical string helpers, not target-specific filesystem resolvers.
|
||||
@@ -0,0 +1,122 @@
|
||||
## When To Use std.proc
|
||||
|
||||
In Zerolang, use `std.proc` for hosted process helpers behind explicit process
|
||||
capability boundaries. The surface is host-only and supports both status-style
|
||||
helpers and owned child handles for incremental I/O.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.proc.spawn(command)` | `ProcStatus` | Runs an argv-style command with inherited stdio through the explicit proc capability surface and returns its status. |
|
||||
| `std.proc.spawnInherit(command)` | `ProcStatus` | Runs an argv-style command while inheriting stdin, stdout, and stderr from the parent process. |
|
||||
| `std.proc.spawnInheritArgs(program, args, cwd, env)` | `ProcStatus` | Runs a program path plus newline-separated argv entries while inheriting stdio, using a working directory and newline-separated `KEY=value` environment bindings. |
|
||||
| `std.proc.exitCode(status)` | `i32` | Reads the process status code. |
|
||||
| `std.proc.succeeded(status)` | `Bool` | Reports whether the status exit code is `0`. |
|
||||
| `std.proc.failed(status)` | `Bool` | Reports whether the status exit code is nonzero. |
|
||||
| `std.proc.runOk(command)` | `Bool` | Spawns a hosted command and reports whether the resulting status succeeded. |
|
||||
| `std.proc.runCode(command)` | `i32` | Spawns a hosted command and returns its exit code. |
|
||||
| `std.proc.capture(command, buffer)` | `Maybe<usize>` | Runs an argv-style command and captures stdout into caller storage. Returns `null` on parse failure, spawn failure, nonzero exit, unsupported target, or output truncation. |
|
||||
| `std.proc.captureArgs(program, args, buffer)` | `Maybe<usize>` | Runs a program path plus newline-separated argv entries and captures stdout into caller storage. |
|
||||
| `std.proc.captureFiles(command, stdoutPath, stderrPath)` | `ProcStatus` | Runs an argv-style command and writes stdout and stderr to hosted paths. Returns `127` when the command cannot be parsed, spawned, waited on, or the output files cannot be opened. |
|
||||
| `std.proc.captureFilesArgs(program, args, stdoutPath, stderrPath)` | `ProcStatus` | Runs a program path plus newline-separated argv entries and redirects stdout and stderr to hosted paths. |
|
||||
| `std.proc.spawnChild(command)` | `ProcChild` | Starts a hosted child process with piped stdin, stdout, and stderr. Returns an invalid handle when the process cannot be created. |
|
||||
| `std.proc.spawnChildIn(command, cwd)` | `ProcChild` | Starts a hosted child process in a working directory with piped stdin, stdout, and stderr. Returns an invalid handle when the cwd is invalid or the process cannot be created. |
|
||||
| `std.proc.spawnChildInEnv(command, cwd, env)` | `ProcChild` | Starts a hosted child process in a working directory with piped stdin/stdout/stderr and explicit newline-separated `KEY=value` environment bindings. |
|
||||
| `std.proc.spawnChildArgs(program, args, cwd, env)` | `ProcChild` | Starts a hosted child process from a program path plus newline-separated argv entries, working directory, and newline-separated `KEY=value` environment bindings. |
|
||||
| `std.proc.childValid(child)` | `Bool` | Reports whether the handle currently names an open child slot. |
|
||||
| `std.proc.running(child)` | `Bool` | Polls the child process without blocking. |
|
||||
| `std.proc.wait(child)` | `ProcStatus` | Waits for process exit and returns its status. |
|
||||
| `std.proc.kill(child)` | `Bool` | Sends the child a termination signal on supported hosts. |
|
||||
| `std.proc.interrupt(child)` | `Bool` | Sends the child an interrupt signal on supported hosts. |
|
||||
| `std.proc.close(child)` | `Bool` | Closes the handle and any remaining pipes. |
|
||||
| `std.proc.closeStdin(child)` | `Bool` | Closes the child stdin pipe while keeping stdout, stderr, and status available. |
|
||||
| `std.proc.pid(child)` | `i32` | Returns the hosted process id for a child handle, or `0` when unavailable. |
|
||||
| `std.proc.pidRunning(pid)` | `Bool` | Reports whether a hosted process id appears to be running. |
|
||||
| `std.proc.killPid(pid)` | `Bool` | Sends a termination signal to a hosted process id on supported hosts. |
|
||||
| `std.proc.interruptPid(pid)` | `Bool` | Sends an interrupt signal to a hosted process id on supported hosts. |
|
||||
| `std.proc.killGroupPid(pid)` | `Bool` | Sends a termination signal to the hosted process group whose id is `pid` on supported hosts. |
|
||||
| `std.proc.interruptGroupPid(pid)` | `Bool` | Sends an interrupt signal to the hosted process group whose id is `pid` on supported hosts. |
|
||||
| `std.proc.readStdout(child, buffer)` | `Maybe<usize>` | Nonblocking read from the child's stdout pipe into caller storage. Returns `null` when no bytes are currently available or the stream is closed. |
|
||||
| `std.proc.readStderr(child, buffer)` | `Maybe<usize>` | Nonblocking read from the child's stderr pipe into caller storage. Returns `null` when no bytes are currently available or the stream is closed. |
|
||||
| `std.proc.writeStdin(child, bytes)` | `Maybe<usize>` | Nonblocking write to the child's stdin pipe. Returns `null` when the stream is not writable. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: proc
|
||||
- allocation behavior: child handles may keep runtime-owned pipe buffers while
|
||||
`wait` drains stdout, stderr, or PTY output
|
||||
- target support: hosted targets with the `proc` capability
|
||||
- error behavior: `spawn`, `spawnInherit`, `captureFiles`, and `wait` return `ProcStatus`; `exitCode` is infallible; `capture` and child I/O helpers return `null` when they cannot produce a complete result
|
||||
- ownership notes: `ProcChild` values name runtime-owned process slots; call `wait` when process status matters and `close` when the handle is no longer needed
|
||||
- example: `examples/std-platform.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let status: ProcStatus = std.proc.spawn("zero-noop")
|
||||
var storage: [64]u8 = [0_u8; 64]
|
||||
let captured: Maybe<usize> = std.proc.capture("printf proc-capture", storage)
|
||||
let files: ProcStatus = std.proc.captureFiles("sh -c 'printf proc-out; printf proc-err >&2'", ".zero/proc.out", ".zero/proc.err")
|
||||
if std.proc.succeeded(status) && std.proc.succeeded(files) && std.proc.runOk("sh -c true") && std.proc.runCode("sh -c true") == 0 && captured.has {
|
||||
check world.out.write("proc ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Incremental child I/O uses caller-owned buffers:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let child: ProcChild = std.proc.spawnChild("zero-noop")
|
||||
var stdoutStorage: [64]u8 = [0_u8; 64]
|
||||
let out: Maybe<usize> = std.proc.readStdout(child, stdoutStorage)
|
||||
if out.has {
|
||||
check world.out.write("child stdout available\n")
|
||||
}
|
||||
let pid: i32 = std.proc.pid(child)
|
||||
let status: ProcStatus = std.proc.wait(child)
|
||||
let closed: Bool = std.proc.close(child)
|
||||
if pid > 0 && std.proc.succeeded(status) && closed {
|
||||
check world.out.write("child ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.proc` requires a hosted target that advertises the `proc` capability.
|
||||
Targets without process support, including Windows hosts until their process
|
||||
runtime is implemented, must reject process helpers before code generation.
|
||||
|
||||
They should not compile a placeholder process implementation.
|
||||
|
||||
`capture` and `captureFiles` do not invoke a shell. They split simple
|
||||
argv-style command text and support quoted arguments. `capture` captures stdout
|
||||
only into caller storage. `captureFiles` redirects stdout and stderr to
|
||||
separate hosted paths.
|
||||
|
||||
`spawnInherit` uses the same argv-style parser and leaves stdin, stdout, and
|
||||
stderr connected to the parent process. `spawnInheritArgs` uses an explicit
|
||||
program plus newline-separated argv entries, working directory, and environment
|
||||
block. Use inherited stdio for editor, pager, and terminal program launches
|
||||
where captured pipes would be the wrong interface.
|
||||
|
||||
Child handles use the same command parser when created from command text. The
|
||||
`*Args` helpers avoid command-text parsing: `program` is argv[0], and each
|
||||
non-empty line in `args` becomes one following argv entry with spaces preserved.
|
||||
`captureArgs` captures stdout into caller storage, `captureFilesArgs` redirects
|
||||
stdout and stderr to hosted paths, and `spawnChildArgs` returns nonblocking
|
||||
pipes so event loops can poll process state and terminal input without owning
|
||||
threads. `wait` drains child output into the handle before reaping so post-wait
|
||||
`readStdout` and `readStderr` calls can still consume buffered data.
|
||||
|
||||
Hosted child helpers start children in their own process group where the target
|
||||
platform supports it. Use `killGroupPid` or `interruptGroupPid` when a stored
|
||||
pid should stop a command tree instead of only the direct parent process.
|
||||
|
||||
`spawnChildInEnv`, `spawnInheritArgs`, and `spawnChildArgs` accept a
|
||||
newline-separated env block such as `"TOKEN=...\nMODE=batch"`. Empty lines are
|
||||
ignored. Invalid entries or oversized env blocks make the helper fail: status
|
||||
helpers return an error status and child helpers return an invalid handle.
|
||||
@@ -0,0 +1,78 @@
|
||||
## When To Use std.pty
|
||||
|
||||
In Zerolang, use `std.pty` for hosted child processes that need terminal
|
||||
semantics instead of plain pipes. PTY children are useful for interactive CLIs,
|
||||
REPLs, shells, editors, pagers, and tools that change behavior when stdout is a
|
||||
terminal.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.pty.spawn(command)` | `ProcChild` | Starts an argv-style command attached to a pseudoterminal. |
|
||||
| `std.pty.spawnIn(command, cwd)` | `ProcChild` | Starts a PTY child in a hosted working directory. |
|
||||
| `std.pty.spawnInEnv(command, cwd, env)` | `ProcChild` | Starts a PTY child with a working directory and newline-separated `KEY=value` environment bindings. |
|
||||
| `std.pty.spawnArgs(program, args, cwd, env)` | `ProcChild` | Starts a program path plus newline-separated argv entries attached to a pseudoterminal. |
|
||||
| `std.pty.valid(child)` | `Bool` | Reports whether the handle currently names an open child slot. |
|
||||
| `std.pty.childValid(child)` | `Bool` | Alias for `std.pty.valid`. |
|
||||
| `std.pty.running(child)` | `Bool` | Polls the PTY child without blocking. |
|
||||
| `std.pty.wait(child)` | `ProcStatus` | Waits for process exit and returns its status. |
|
||||
| `std.pty.kill(child)` | `Bool` | Sends the child a termination signal on supported hosts. |
|
||||
| `std.pty.interrupt(child)` | `Bool` | Sends the child an interrupt signal on supported hosts. |
|
||||
| `std.pty.close(child)` | `Bool` | Closes the handle and remaining terminal resources. |
|
||||
| `std.pty.pid(child)` | `i32` | Returns the hosted process id for the child, or `0` when unavailable. |
|
||||
| `std.pty.read(child, buffer)` | `Maybe<usize>` | Nonblocking read from the PTY master into caller storage. |
|
||||
| `std.pty.write(child, bytes)` | `Maybe<usize>` | Nonblocking write to the PTY master. |
|
||||
| `std.pty.resize(child, columns, rows)` | `Bool` | Sets the child terminal size on supported hosts. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: proc
|
||||
- allocation behavior: child handles may keep runtime-owned PTY output buffers while `wait` drains terminal output
|
||||
- target support: hosted targets with the `proc` capability
|
||||
- error behavior: spawn helpers return an invalid `ProcChild` handle on failure; `read` and `write` return `null` when no bytes can be transferred
|
||||
- ownership notes: `ProcChild` values name runtime-owned process slots; call `wait` when process status matters and `close` when the handle is no longer needed
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let child: ProcChild = std.pty.spawnArgs("printf", "hello pty\n", ".", "")
|
||||
let resized: Bool = std.pty.resize(child, 80_usize, 24_usize)
|
||||
|
||||
var storage: [64]u8 = [0_u8; 64]
|
||||
var saw_output: Bool = false
|
||||
var attempts: usize = 0
|
||||
while attempts < 20_usize && !saw_output {
|
||||
let read: Maybe<usize> = std.pty.read(child, storage)
|
||||
if read.has {
|
||||
let bytes: Span<u8> = std.io.written(storage, read.value)
|
||||
saw_output = std.str.contains(bytes, "hello pty")
|
||||
}
|
||||
if !saw_output {
|
||||
let slept: Bool = std.time.sleep(std.time.ms(10))
|
||||
}
|
||||
attempts = attempts + 1_usize
|
||||
}
|
||||
|
||||
let status: ProcStatus = std.pty.wait(child)
|
||||
if resized && saw_output && std.proc.succeeded(status) {
|
||||
check world.out.write("pty ok\n")
|
||||
}
|
||||
let closed: Bool = std.pty.close(child)
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.pty` returns the same `ProcChild` handle shape used by `std.proc`, but the
|
||||
underlying child is connected to a pseudoterminal master. PTY output is a single
|
||||
terminal byte stream: stderr is merged by the terminal, and `std.pty.read`
|
||||
reads from that stream.
|
||||
Targets without process support, including Windows hosts until their process
|
||||
runtime is implemented, reject PTY helpers before code generation.
|
||||
For short-lived programs, drain the PTY with `std.pty.read` before `wait`; some
|
||||
hosts report the terminal as closed once the child exits.
|
||||
|
||||
Use `std.proc.spawnChild*` for programs where separate stdout and stderr pipes
|
||||
matter. Use `std.pty.spawn*` for programs where terminal behavior matters.
|
||||
@@ -0,0 +1,50 @@
|
||||
## When To Use std.rand
|
||||
|
||||
In Zerolang, use `std.rand` for deterministic random sources and target-gated entropy.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.rand.seed(value)` | `RandSource` | Creates a deterministic test source. |
|
||||
| `std.rand.nextU32(&mut source)` | `u32` | Advances an explicit random source. |
|
||||
| `std.rand.nextBool(&mut source)` | `Bool` | Advances an explicit random source and returns one random bit. |
|
||||
| `std.rand.nextBelow(&mut source, bound)` | `Maybe<u32>` | Returns a value in `[0, bound)` using rejection sampling, or null when bound is zero. |
|
||||
| `std.rand.rangeU32(&mut source, low, high)` | `Maybe<u32>` | Returns a value in `[low, high)` using rejection sampling, or null for an empty range. |
|
||||
| `std.rand.entropyU32()` | `u32` | Reads target entropy where the target provides it. |
|
||||
| `std.rand.entropySeed()` | `RandSource` | Creates a `RandSource` from target entropy where available. |
|
||||
| `std.rand.entropyHex32(buffer)` | `Maybe<Span<u8>>` | Writes an 8-byte lowercase entropy ID into caller storage. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: rand
|
||||
- allocation behavior: no allocation; `entropyHex32` writes caller-provided storage
|
||||
- target support: deterministic source is target-neutral; entropy requires a rand-capable target
|
||||
- error behavior: bounded helpers return null for invalid bounds; `entropyHex32` returns null when storage is too small
|
||||
- ownership notes: deterministic helpers mutate the caller-owned source
|
||||
- example: `examples/std-platform.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var rng: RandSource = std.rand.seed(7_u32)
|
||||
let first: u32 = std.rand.nextU32(&mut rng)
|
||||
let second: Bool = std.rand.nextBool(&mut rng)
|
||||
let bounded: Maybe<u32> = std.rand.nextBelow(&mut rng, 10_u32)
|
||||
let ranged: Maybe<u32> = std.rand.rangeU32(&mut rng, 40_u32, 50_u32)
|
||||
var id_buf: [8]u8 = [0_u8; 8]
|
||||
let entropy_id: Maybe<Span<u8>> = std.rand.entropyHex32(id_buf)
|
||||
if first == 1025555898_u32 && second && bounded.has && ranged.has && entropy_id.has {
|
||||
check world.out.write("rand ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
Zero keeps random sources explicit. Deterministic tests use `std.rand.seed`;
|
||||
bounded helpers use rejection sampling so the range is not modulo-biased.
|
||||
Caller-facing IDs use `entropyHex32` when target entropy is available.
|
||||
|
||||
Production entropy stays target-capability-gated.
|
||||
@@ -0,0 +1,100 @@
|
||||
## When To Use std.regex
|
||||
|
||||
In Zerolang, use `std.regex` to match text against a documented ECMA-262-leaning
|
||||
regular expression subset, such as JSON Schema `pattern` checks.
|
||||
|
||||
Supported syntax: literals, `.`, character classes with negation, ranges, and
|
||||
`\d \D \w \W \s \S`, anchors `^` `$` and word boundaries `\b` `\B`, greedy
|
||||
quantifiers `*` `+` `?` `{m}` `{m,}` `{m,n}`, alternation `|`, and capturing or
|
||||
`(?:...)` non-capturing groups (matching only; no capture extraction). Matching
|
||||
is by Unicode codepoint over UTF-8 text and searches anywhere in the text unless
|
||||
the pattern is anchored, like ECMAScript `RegExp.prototype.test`. When multiple
|
||||
matches start at the same byte, span-returning helpers use the longest end
|
||||
position, so `a|ab` finds `ab` in `ab`.
|
||||
|
||||
Unsupported constructs never misparse silently. Compilation fails with a
|
||||
structured status code: `1` backreference, `2` lookahead, `3` lookbehind,
|
||||
`4` named group, `5` lazy quantifier, `6` group modifier or inline flags,
|
||||
`7` unicode property escape, `8` invalid syntax, `9` invalid quantifier range,
|
||||
`10` program over the buffer or 2048-byte limit, `11` pattern is not valid
|
||||
UTF-8, `12` group nesting over depth 32.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.regex.compile(buffer, pattern)` | `Maybe<Span<u8>>` | Compiles a pattern into a caller-owned buffer; returns the compiled program span or `null` on any compile failure. |
|
||||
| `std.regex.compileStatus(buffer, pattern)` | `u32` | Compiles and returns `0` or the structured status code for diagnostics. |
|
||||
| `std.regex.compileErrorOffset(buffer, pattern)` | `Maybe<usize>` | Returns the pattern byte offset for a compile failure, or `null` when the pattern compiles. |
|
||||
| `std.regex.statusName(status)` | `String` | Names a status code, such as `unsupported backreference`. |
|
||||
| `std.regex.isMatch(program, text)` | `Bool` | Tests text against a compiled program. Compile once, then match many times. |
|
||||
| `std.regex.matches(pattern, text)` | `Maybe<Bool>` | One-shot compile and match with an internal 1024-byte program buffer; returns `null` when the pattern does not compile. |
|
||||
| `std.regex.contains(pattern, text)` | `Maybe<Bool>` | Alias-shaped one-shot search helper; returns `null` when the pattern does not compile. |
|
||||
| `std.regex.findIndex(pattern, text)` | `Maybe<usize>` | Returns the first matching byte index, the input length when absent, or `null` when the pattern does not compile. |
|
||||
| `std.regex.find(pattern, text)` | `Maybe<Span<u8>>` | Borrows the first matching span, or returns `null` when absent or invalid. |
|
||||
| `std.regex.findCount(pattern, text)` | `Maybe<usize>` | Counts non-overlapping matches, or returns `null` when the pattern does not compile. |
|
||||
| `std.regex.findNth(pattern, text, index)` | `Maybe<Span<u8>>` | Borrows the zero-based non-overlapping match at `index`, or returns `null` when absent or invalid. |
|
||||
| `std.regex.findNthIndex(pattern, text, index)` | `Maybe<usize>` | Returns the byte index of the zero-based non-overlapping match, the input length when absent, or `null` when invalid. |
|
||||
| `std.regex.replace(buffer, pattern, text, replacement)` | `Maybe<Span<u8>>` | Replaces non-overlapping matches with literal replacement bytes into caller storage. |
|
||||
| `std.regex.splitCount(pattern, text)` | `Maybe<usize>` | Counts fields separated by non-empty regex matches, or returns `null` when the pattern does not compile. |
|
||||
| `std.regex.split(pattern, text, index)` | `Maybe<Span<u8>>` | Borrows the zero-based field separated by non-empty regex matches, or returns `null` when absent or invalid. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [512]u8 = [0; 512]
|
||||
let buffer: MutSpan<u8> = storage
|
||||
let compiled: Maybe<Span<u8>> = std.regex.compile(buffer, "^[a-z]+-\\d{2,4}$")
|
||||
if !compiled.has {
|
||||
return
|
||||
}
|
||||
let program: Span<u8> = compiled.value
|
||||
let quick: Maybe<Bool> = std.regex.matches("^(cat|dog)s?$", "dogs")
|
||||
let first: Maybe<Span<u8>> = std.regex.find("\\d+", "build-2048")
|
||||
let second: Maybe<Span<u8>> = std.regex.findNth("\\d+", "a1 b22 c333", 1)
|
||||
var replaced_storage: [16]u8 = [0; 16]
|
||||
let replaced: Maybe<Span<u8>> = std.regex.replace(replaced_storage, "\\d+", "a1 b22", "#")
|
||||
let fields: Maybe<usize> = std.regex.splitCount("[,;]", "red,green;blue")
|
||||
let middle: Maybe<Span<u8>> = std.regex.split("[,;]", "red,green;blue", 1)
|
||||
if std.regex.isMatch(program, "build-2048") && !std.regex.isMatch(program, "build-1") && (quick.has && quick.value) && first.has && std.mem.eql(first.value, "2048") && second.has && std.mem.eql(second.value, "22") && replaced.has && std.mem.eql(replaced.value, "a# b#") && fields.has && fields.value == 3 && middle.has && std.mem.eql(middle.value, "green") {
|
||||
check world.out.write("regex ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diagnosing a rejected pattern:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [128]u8 = [0; 128]
|
||||
let buffer: MutSpan<u8> = storage
|
||||
let status: u32 = std.regex.compileStatus(buffer, "(?=lookahead)")
|
||||
let offset: Maybe<usize> = std.regex.compileErrorOffset(buffer, "(?=lookahead)")
|
||||
if status != 0 {
|
||||
check world.out.write(std.regex.statusName(status))
|
||||
check world.out.write("\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: writes to caller-provided mutable storage for `compile`,
|
||||
`compileStatus`, `compileErrorOffset`, and `replace`; other helpers only borrow
|
||||
input spans or return scalar results.
|
||||
|
||||
Allocation behavior: `compile`, `compileStatus`, and `compileErrorOffset` write
|
||||
the caller program buffer. `replace` writes the caller output buffer. One-shot
|
||||
search, split, and match helpers use fixed internal program storage and allocate
|
||||
nothing on the heap.
|
||||
|
||||
Error behavior: `compile` returns `null`, `compileStatus` returns a status code
|
||||
naming the construct, and `compileErrorOffset` returns the byte offset for a
|
||||
failed compile. One-shot helpers return `null` for invalid patterns; `isMatch`
|
||||
returns `false` for malformed program spans or invalid UTF-8 text.
|
||||
|
||||
`find`, `findNth`, `replace`, `split`, and their index/count variants use the
|
||||
leftmost start and longest end for each match. `split` and `splitCount` use
|
||||
non-empty regex matches as separators. Zero-length matches are ignored as
|
||||
separators so callers get deterministic field traversal without a cursor object.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,104 @@
|
||||
## When To Use std.search
|
||||
|
||||
In Zerolang, use `std.search` for scalar span lookup and binary-search helpers
|
||||
over sorted caller-owned data.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.search.indexOf(items, needle)` | `usize` | Returns the first matching index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.lastIndexOf(items, needle)` | `usize` | Returns the last matching index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.lowerBoundI32(items, needle)` | `usize` | Returns the insertion point in sorted `Span<i32>` input. |
|
||||
| `std.search.upperBoundI32(items, needle)` | `usize` | Returns the insertion point after existing equal values in sorted `Span<i32>` input. |
|
||||
| `std.search.binaryI32(items, needle)` | `usize` | Returns the matching sorted `Span<i32>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedI32(items, needle)` | `Bool` | Checks whether sorted `Span<i32>` input contains the needle. |
|
||||
| `std.search.countSortedI32(items, needle)` | `usize` | Counts equal values in sorted `Span<i32>` input. |
|
||||
| `std.search.equalRangeI32(items, needle)` | `Span<i32>` | Borrows the equal-value range in sorted `Span<i32>` input. |
|
||||
| `std.search.partitionPointI32(items, pivot)` | `usize` | Returns the split index where values stop being below `pivot`. |
|
||||
| `std.search.lowerBoundDescI32(items, needle)` | `usize` | Returns the insertion point before equal values in descending sorted `Span<i32>` input. |
|
||||
| `std.search.upperBoundDescI32(items, needle)` | `usize` | Returns the insertion point after equal values in descending sorted `Span<i32>` input. |
|
||||
| `std.search.binaryDescI32(items, needle)` | `usize` | Returns the matching descending sorted `Span<i32>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedDescI32(items, needle)` | `Bool` | Checks whether descending sorted `Span<i32>` input contains the needle. |
|
||||
| `std.search.countSortedDescI32(items, needle)` | `usize` | Counts equal values in descending sorted `Span<i32>` input. |
|
||||
| `std.search.equalRangeDescI32(items, needle)` | `Span<i32>` | Borrows the equal-value range in descending sorted `Span<i32>` input. |
|
||||
| `std.search.partitionPointDescI32(items, pivot)` | `usize` | Returns the split index where descending values stop being above `pivot`. |
|
||||
| `std.search.minI32(items)` | `Maybe<i32>` | Returns the minimum value in `Span<i32>` input or `null` for an empty span. |
|
||||
| `std.search.maxI32(items)` | `Maybe<i32>` | Returns the maximum value in `Span<i32>` input or `null` for an empty span. |
|
||||
| `std.search.minIndexI32(items)` | `usize` | Returns the first minimum-value index in `Span<i32>` input or `std.mem.len(items)` for an empty span. |
|
||||
| `std.search.maxIndexI32(items)` | `usize` | Returns the first maximum-value index in `Span<i32>` input or `std.mem.len(items)` for an empty span. |
|
||||
| `std.search.lowerBoundU32(items, needle)` | `usize` | Returns the insertion point in sorted `Span<u32>` input. |
|
||||
| `std.search.upperBoundU32(items, needle)` | `usize` | Returns the insertion point after existing equal values in sorted `Span<u32>` input. |
|
||||
| `std.search.binaryU32(items, needle)` | `usize` | Returns the matching sorted `Span<u32>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedU32(items, needle)` | `Bool` | Checks whether sorted `Span<u32>` input contains the needle. |
|
||||
| `std.search.countSortedU32(items, needle)` | `usize` | Counts equal values in sorted `Span<u32>` input. |
|
||||
| `std.search.equalRangeU32(items, needle)` | `Span<u32>` | Borrows the equal-value range in sorted `Span<u32>` input. |
|
||||
| `std.search.partitionPointU32(items, pivot)` | `usize` | Returns the split index where values stop being below `pivot`. |
|
||||
| `std.search.lowerBoundDescU32(items, needle)` | `usize` | Returns the insertion point before equal values in descending sorted `Span<u32>` input. |
|
||||
| `std.search.upperBoundDescU32(items, needle)` | `usize` | Returns the insertion point after equal values in descending sorted `Span<u32>` input. |
|
||||
| `std.search.binaryDescU32(items, needle)` | `usize` | Returns the matching descending sorted `Span<u32>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedDescU32(items, needle)` | `Bool` | Checks whether descending sorted `Span<u32>` input contains the needle. |
|
||||
| `std.search.countSortedDescU32(items, needle)` | `usize` | Counts equal values in descending sorted `Span<u32>` input. |
|
||||
| `std.search.equalRangeDescU32(items, needle)` | `Span<u32>` | Borrows the equal-value range in descending sorted `Span<u32>` input. |
|
||||
| `std.search.partitionPointDescU32(items, pivot)` | `usize` | Returns the split index where descending values stop being above `pivot`. |
|
||||
| `std.search.minU32(items)` | `Maybe<u32>` | Returns the minimum value in `Span<u32>` input or `null` for an empty span. |
|
||||
| `std.search.maxU32(items)` | `Maybe<u32>` | Returns the maximum value in `Span<u32>` input or `null` for an empty span. |
|
||||
| `std.search.minIndexU32(items)` | `usize` | Returns the first minimum-value index in `Span<u32>` input or `std.mem.len(items)` for an empty span. |
|
||||
| `std.search.maxIndexU32(items)` | `usize` | Returns the first maximum-value index in `Span<u32>` input or `std.mem.len(items)` for an empty span. |
|
||||
| `std.search.lowerBoundUsize(items, needle)` | `usize` | Returns the insertion point in sorted `Span<usize>` input. |
|
||||
| `std.search.upperBoundUsize(items, needle)` | `usize` | Returns the insertion point after existing equal values in sorted `Span<usize>` input. |
|
||||
| `std.search.binaryUsize(items, needle)` | `usize` | Returns the matching sorted `Span<usize>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedUsize(items, needle)` | `Bool` | Checks whether sorted `Span<usize>` input contains the needle. |
|
||||
| `std.search.countSortedUsize(items, needle)` | `usize` | Counts equal values in sorted `Span<usize>` input. |
|
||||
| `std.search.equalRangeUsize(items, needle)` | `Span<usize>` | Borrows the equal-value range in sorted `Span<usize>` input. |
|
||||
| `std.search.partitionPointUsize(items, pivot)` | `usize` | Returns the split index where values stop being below `pivot`. |
|
||||
| `std.search.lowerBoundDescUsize(items, needle)` | `usize` | Returns the insertion point before equal values in descending sorted `Span<usize>` input. |
|
||||
| `std.search.upperBoundDescUsize(items, needle)` | `usize` | Returns the insertion point after equal values in descending sorted `Span<usize>` input. |
|
||||
| `std.search.binaryDescUsize(items, needle)` | `usize` | Returns the matching descending sorted `Span<usize>` index or `std.mem.len(items)` when absent. |
|
||||
| `std.search.containsSortedDescUsize(items, needle)` | `Bool` | Checks whether descending sorted `Span<usize>` input contains the needle. |
|
||||
| `std.search.countSortedDescUsize(items, needle)` | `usize` | Counts equal values in descending sorted `Span<usize>` input. |
|
||||
| `std.search.equalRangeDescUsize(items, needle)` | `Span<usize>` | Borrows the equal-value range in descending sorted `Span<usize>` input. |
|
||||
| `std.search.partitionPointDescUsize(items, pivot)` | `usize` | Returns the split index where descending values stop being above `pivot`. |
|
||||
| `std.search.minUsize(items)` | `Maybe<usize>` | Returns the minimum value in `Span<usize>` input or `null` for an empty span. |
|
||||
| `std.search.maxUsize(items)` | `Maybe<usize>` | Returns the maximum value in `Span<usize>` input or `null` for an empty span. |
|
||||
| `std.search.minIndexUsize(items)` | `usize` | Returns the first minimum-value index in `Span<usize>` input or `std.mem.len(items)` for an empty span. |
|
||||
| `std.search.maxIndexUsize(items)` | `usize` | Returns the first maximum-value index in `Span<usize>` input or `std.mem.len(items)` for an empty span. |
|
||||
|
||||
Generic equality search supports the same non-owned scalar item types as
|
||||
`std.mem.contains`.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let values: [5]i32 = [1, 2, 3, 5, 8]
|
||||
let found: usize = std.search.binaryI32(values, 5)
|
||||
let present: Bool = std.search.containsSortedI32(values, 5)
|
||||
let after_five: usize = std.search.upperBoundI32(values, 5)
|
||||
let fives: usize = std.search.countSortedI32(values, 5)
|
||||
let five_range: Span<i32> = std.search.equalRangeI32(values, 5)
|
||||
let partition_point: usize = std.search.partitionPointI32(values, 5)
|
||||
let missing: usize = std.search.indexOf(values, 13)
|
||||
let minimum: Maybe<i32> = std.search.minI32(values)
|
||||
let maximum: Maybe<i32> = std.search.maxI32(values)
|
||||
let max_index: usize = std.search.maxIndexI32(values)
|
||||
let descending: [5]i32 = [9, 5, 5, 3, 1]
|
||||
let descending_found: usize = std.search.binaryDescI32(descending, 5)
|
||||
let descending_count: usize = std.search.countSortedDescI32(descending, 5)
|
||||
if found == 3 && present && after_five == 4 && fives == 1 && std.mem.len(five_range) == 1 && partition_point == 3 && missing == std.mem.len(values) && minimum.has && minimum.value == 1 && maximum.has && maximum.value == 8 && max_index == 4 && descending_found == 1 && descending_count == 2 {
|
||||
check world.out.write("search ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: absent values return the input length; sorted-count helpers
|
||||
return `0`; min/max helpers return `null` for empty input; min/max index
|
||||
helpers return the input length for empty input.
|
||||
|
||||
Ownership: generic equality search rejects owned item elements.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,114 @@
|
||||
## When To Use std.sort
|
||||
|
||||
In Zerolang, use `std.sort` for in-place sorting and scalar ordering helpers
|
||||
over caller-owned storage.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.sort.insertionI32(items)` | `Void` | Sorts caller-owned mutable `i32` storage in ascending order. |
|
||||
| `std.sort.insertionDescI32(items)` | `Void` | Sorts caller-owned mutable `i32` storage in descending order. |
|
||||
| `std.sort.stableI32(items)` | `Void` | Stable ascending sort over caller-owned mutable `i32` storage. |
|
||||
| `std.sort.unstableI32(items)` | `Void` | Unstable ascending sort over caller-owned mutable `i32` storage. |
|
||||
| `std.sort.stableDescI32(items)` | `Void` | Stable descending sort over caller-owned mutable `i32` storage. |
|
||||
| `std.sort.unstableDescI32(items)` | `Void` | Unstable descending sort over caller-owned mutable `i32` storage. |
|
||||
| `std.sort.reverseI32(items)` | `Void` | Reverses caller-owned mutable `i32` storage in place. |
|
||||
| `std.sort.swapI32(items, left, right)` | `Bool` | Swaps two in-bounds `i32` elements and returns `false` for invalid indexes. |
|
||||
| `std.sort.rotateLeftI32(items, amount)` | `Void` | Rotates caller-owned mutable `i32` storage left by `amount` positions. |
|
||||
| `std.sort.rotateRightI32(items, amount)` | `Void` | Rotates caller-owned mutable `i32` storage right by `amount` positions. |
|
||||
| `std.sort.isSortedI32(items)` | `Bool` | Checks whether `Span<i32>` input is sorted ascending. |
|
||||
| `std.sort.isSortedDescI32(items)` | `Bool` | Checks whether `Span<i32>` input is sorted descending. |
|
||||
| `std.sort.partitionI32(items, pivot)` | `usize` | Moves values below `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.partitionDescI32(items, pivot)` | `usize` | Moves values above `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.isPartitionedI32(items, pivot)` | `Bool` | Checks whether all values below `pivot` are before the remaining `i32` values. |
|
||||
| `std.sort.isPartitionedDescI32(items, pivot)` | `Bool` | Checks whether all values above `pivot` are before the remaining `i32` values. |
|
||||
| `std.sort.dedupeSortedI32(items)` | `usize` | Compacts sorted mutable `i32` storage in place and returns the unique prefix length. |
|
||||
| `std.sort.selectNthI32(items, index)` | `Bool` | Partially reorders mutable `i32` storage so `items[index]` contains the nth ascending value. |
|
||||
| `std.sort.selectNthDescI32(items, index)` | `Bool` | Partially reorders mutable `i32` storage so `items[index]` contains the nth descending value. |
|
||||
| `std.sort.mergeSortedI32(dst, left, right)` | `usize` | Merges ascending sorted `i32` inputs into non-overlapping caller storage and returns the written count. |
|
||||
| `std.sort.mergeSortedDescI32(dst, left, right)` | `usize` | Merges descending sorted `i32` inputs into non-overlapping caller storage and returns the written count. |
|
||||
| `std.sort.insertionU32(items)` | `Void` | Sorts caller-owned mutable `u32` storage in ascending order. |
|
||||
| `std.sort.insertionDescU32(items)` | `Void` | Sorts caller-owned mutable `u32` storage in descending order. |
|
||||
| `std.sort.stableU32(items)` | `Void` | Stable ascending sort over caller-owned mutable `u32` storage. |
|
||||
| `std.sort.unstableU32(items)` | `Void` | Unstable ascending sort over caller-owned mutable `u32` storage. |
|
||||
| `std.sort.stableDescU32(items)` | `Void` | Stable descending sort over caller-owned mutable `u32` storage. |
|
||||
| `std.sort.unstableDescU32(items)` | `Void` | Unstable descending sort over caller-owned mutable `u32` storage. |
|
||||
| `std.sort.reverseU32(items)` | `Void` | Reverses caller-owned mutable `u32` storage in place. |
|
||||
| `std.sort.swapU32(items, left, right)` | `Bool` | Swaps two in-bounds `u32` elements and returns `false` for invalid indexes. |
|
||||
| `std.sort.rotateLeftU32(items, amount)` | `Void` | Rotates caller-owned mutable `u32` storage left by `amount` positions. |
|
||||
| `std.sort.rotateRightU32(items, amount)` | `Void` | Rotates caller-owned mutable `u32` storage right by `amount` positions. |
|
||||
| `std.sort.isSortedU32(items)` | `Bool` | Checks whether `Span<u32>` input is sorted ascending. |
|
||||
| `std.sort.isSortedDescU32(items)` | `Bool` | Checks whether `Span<u32>` input is sorted descending. |
|
||||
| `std.sort.partitionU32(items, pivot)` | `usize` | Moves values below `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.partitionDescU32(items, pivot)` | `usize` | Moves values above `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.isPartitionedU32(items, pivot)` | `Bool` | Checks whether all values below `pivot` are before the remaining `u32` values. |
|
||||
| `std.sort.isPartitionedDescU32(items, pivot)` | `Bool` | Checks whether all values above `pivot` are before the remaining `u32` values. |
|
||||
| `std.sort.dedupeSortedU32(items)` | `usize` | Compacts sorted mutable `u32` storage in place and returns the unique prefix length. |
|
||||
| `std.sort.selectNthU32(items, index)` | `Bool` | Partially reorders mutable `u32` storage so `items[index]` contains the nth ascending value. |
|
||||
| `std.sort.selectNthDescU32(items, index)` | `Bool` | Partially reorders mutable `u32` storage so `items[index]` contains the nth descending value. |
|
||||
| `std.sort.mergeSortedU32(dst, left, right)` | `usize` | Merges ascending sorted `u32` inputs into non-overlapping caller storage and returns the written count. |
|
||||
| `std.sort.mergeSortedDescU32(dst, left, right)` | `usize` | Merges descending sorted `u32` inputs into non-overlapping caller storage and returns the written count. |
|
||||
| `std.sort.insertionUsize(items)` | `Void` | Sorts caller-owned mutable `usize` storage in ascending order. |
|
||||
| `std.sort.insertionDescUsize(items)` | `Void` | Sorts caller-owned mutable `usize` storage in descending order. |
|
||||
| `std.sort.stableUsize(items)` | `Void` | Stable ascending sort over caller-owned mutable `usize` storage. |
|
||||
| `std.sort.unstableUsize(items)` | `Void` | Unstable ascending sort over caller-owned mutable `usize` storage. |
|
||||
| `std.sort.stableDescUsize(items)` | `Void` | Stable descending sort over caller-owned mutable `usize` storage. |
|
||||
| `std.sort.unstableDescUsize(items)` | `Void` | Unstable descending sort over caller-owned mutable `usize` storage. |
|
||||
| `std.sort.reverseUsize(items)` | `Void` | Reverses caller-owned mutable `usize` storage in place. |
|
||||
| `std.sort.swapUsize(items, left, right)` | `Bool` | Swaps two in-bounds `usize` elements and returns `false` for invalid indexes. |
|
||||
| `std.sort.rotateLeftUsize(items, amount)` | `Void` | Rotates caller-owned mutable `usize` storage left by `amount` positions. |
|
||||
| `std.sort.rotateRightUsize(items, amount)` | `Void` | Rotates caller-owned mutable `usize` storage right by `amount` positions. |
|
||||
| `std.sort.isSortedUsize(items)` | `Bool` | Checks whether `Span<usize>` input is sorted ascending. |
|
||||
| `std.sort.isSortedDescUsize(items)` | `Bool` | Checks whether `Span<usize>` input is sorted descending. |
|
||||
| `std.sort.partitionUsize(items, pivot)` | `usize` | Moves values below `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.partitionDescUsize(items, pivot)` | `usize` | Moves values above `pivot` before the rest and returns the split index. |
|
||||
| `std.sort.isPartitionedUsize(items, pivot)` | `Bool` | Checks whether all values below `pivot` are before the remaining `usize` values. |
|
||||
| `std.sort.isPartitionedDescUsize(items, pivot)` | `Bool` | Checks whether all values above `pivot` are before the remaining `usize` values. |
|
||||
| `std.sort.dedupeSortedUsize(items)` | `usize` | Compacts sorted mutable `usize` storage in place and returns the unique prefix length. |
|
||||
| `std.sort.selectNthUsize(items, index)` | `Bool` | Partially reorders mutable `usize` storage so `items[index]` contains the nth ascending value. |
|
||||
| `std.sort.selectNthDescUsize(items, index)` | `Bool` | Partially reorders mutable `usize` storage so `items[index]` contains the nth descending value. |
|
||||
| `std.sort.mergeSortedUsize(dst, left, right)` | `usize` | Merges ascending sorted `usize` inputs into non-overlapping caller storage and returns the written count. |
|
||||
| `std.sort.mergeSortedDescUsize(dst, left, right)` | `usize` | Merges descending sorted `usize` inputs into non-overlapping caller storage and returns the written count. |
|
||||
|
||||
The first sort surface is deliberately small and typed. Generic comparator
|
||||
sorting should wait for stronger comparator contracts instead of smuggling an
|
||||
untyped callback convention into the standard library.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var values: [5]i32 = [5, 1, 4, 2, 3]
|
||||
std.sort.stableI32(values)
|
||||
let unique_len: usize = std.sort.dedupeSortedI32(values)
|
||||
std.sort.unstableI32(values)
|
||||
std.sort.reverseI32(values)
|
||||
let swapped: Bool = std.sort.swapI32(values, 0_usize, 4_usize)
|
||||
std.sort.rotateLeftI32(values, 2_usize)
|
||||
std.sort.rotateRightI32(values, 2_usize)
|
||||
let high_len: usize = std.sort.partitionDescI32(values, 2)
|
||||
let high_partitioned: Bool = std.sort.isPartitionedDescI32(values, 2)
|
||||
std.sort.stableDescI32(values)
|
||||
std.sort.unstableDescI32(values)
|
||||
var selected: [5]i32 = [9, 1, 4, 7, 2]
|
||||
let selected_ok: Bool = std.sort.selectNthI32(selected, 2_usize)
|
||||
let left_sorted: [2]i32 = [1, 3]
|
||||
let right_sorted: [3]i32 = [2, 4, 5]
|
||||
var merged: [5]i32 = [0, 0, 0, 0, 0]
|
||||
let merged_len: usize = std.sort.mergeSortedI32(merged, left_sorted, right_sorted)
|
||||
if std.sort.isSortedDescI32(values) && swapped && unique_len == 5 && high_len == 3 && high_partitioned && selected_ok && selected[2] == 4 && merged_len == 5 && values[0] == 5 && values[4] == 1 {
|
||||
check world.out.write("sort ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: writes to caller-provided mutable storage.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: none.
|
||||
|
||||
Ownership: sort helpers are typed scalar helpers and do not move owned values.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,73 @@
|
||||
## When To Use std.str
|
||||
|
||||
In Zerolang, use `std.str` for allocation-free byte-string helpers over spans and
|
||||
caller-owned storage.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.str.copy(buffer, text)` | `Maybe<Span<u8>>` | Copies `text` into caller storage. |
|
||||
| `std.str.concat(buffer, left, right)` | `Maybe<Span<u8>>` | Writes `left` followed by `right`. |
|
||||
| `std.str.repeat(buffer, text, count)` | `Maybe<Span<u8>>` | Repeats `text` into caller storage. |
|
||||
| `std.str.replace(buffer, text, old, replacement)` | `Maybe<Span<u8>>` | Replaces non-overlapping `old` byte substrings into caller storage; empty `old` returns `null`. |
|
||||
| `std.str.reverse(buffer, text)` | `Maybe<Span<u8>>` | Writes reversed bytes into non-overlapping caller-provided storage. |
|
||||
| `std.str.countByte(text, byte)` | `usize` | Counts exact byte matches. |
|
||||
| `std.str.count(text, needle)` | `usize` | Counts non-overlapping byte substring matches; the empty needle returns `len + 1`. |
|
||||
| `std.str.splitCount(text, separator)` | `usize` | Counts byte-separator split parts; an empty separator returns `0`. |
|
||||
| `std.str.split(text, separator, index)` | `Maybe<Span<u8>>` | Borrows a zero-based split part. |
|
||||
| `std.str.fieldCountAscii(text)` | `usize` | Counts non-empty ASCII-whitespace separated fields. |
|
||||
| `std.str.fieldAscii(text, index)` | `Maybe<Span<u8>>` | Borrows a zero-based ASCII-whitespace field. |
|
||||
| `std.str.lineCount(text)` | `usize` | Counts LF-delimited lines; a trailing LF does not add a final empty line. |
|
||||
| `std.str.line(text, index)` | `Maybe<Span<u8>>` | Borrows a zero-based line and strips `\r` before `\n`. |
|
||||
| `std.str.indexOf(text, needle)` / `std.str.lastIndexOf(text, needle)` | `usize` | Returns a matching byte index or the input length when absent. |
|
||||
| `std.str.startsWith(text, prefix)` | `Bool` | Checks a byte prefix. |
|
||||
| `std.str.endsWith(text, suffix)` | `Bool` | Checks a byte suffix. |
|
||||
| `std.str.contains(text, needle)` | `Bool` | Checks for a byte substring; the empty needle is present. |
|
||||
| `std.str.trimAscii(text)` | `Span<u8>` | Borrows `text` without leading or trailing ASCII space bytes. |
|
||||
| `std.str.trimStartAscii(text)` / `std.str.trimEndAscii(text)` | `Span<u8>` | Borrows one-sided trimmed views. |
|
||||
| `std.str.toLowerAscii(buffer, text)` / `std.str.toUpperAscii(buffer, text)` | `Maybe<Span<u8>>` | Writes ASCII case-converted bytes into caller storage. |
|
||||
| `std.str.eqlIgnoreAsciiCase(left, right)` | `Bool` | Compares ASCII case-insensitively. |
|
||||
| `std.str.wordCountAscii(text)` | `usize` | Counts non-empty runs separated by ASCII space bytes. |
|
||||
|
||||
Current scope:
|
||||
|
||||
- Helpers operate on byte spans and ASCII delimiter rules for space, tab, line feed, and carriage return.
|
||||
- `reverse`, `repeat`, `replace`, `copy`, and `concat` write into caller storage and return `null` when the buffer is too small. The destination buffer must not overlap the input.
|
||||
- The module does not implement Unicode case mapping, grapheme segmentation, or locale-aware text rules.
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
var storage: [6]u8 = [0_u8; 6]
|
||||
let reversed: Maybe<Span<u8>> = std.str.reverse(storage, "drawer")
|
||||
var repeated_storage: [8]u8 = [0_u8; 8]
|
||||
let repeated: Maybe<Span<u8>> = std.str.repeat(repeated_storage, "ha", 3)
|
||||
var lower_storage: [4]u8 = [0_u8; 4]
|
||||
let lower: Maybe<Span<u8>> = std.str.toLowerAscii(lower_storage, "ZERO")
|
||||
let field: Maybe<Span<u8>> = std.str.fieldAscii("zero text", 1)
|
||||
if reversed.has && repeated.has && (lower.has && field.has) {
|
||||
if std.mem.eql(reversed.value, "reward") && std.mem.eql(repeated.value, "hahaha") && (std.mem.eql(lower.value, "zero") && std.mem.eql(field.value, "text")) {
|
||||
check world.out.write("string helper ok\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.str` is allocation-free. Functions that create new byte sequences use
|
||||
caller-provided storage, and functions that return spans borrow from an input or
|
||||
that caller-provided storage.
|
||||
|
||||
`reverse` is a copy helper, not an in-place reversal primitive. Pass separate
|
||||
destination storage when the source text comes from mutable bytes.
|
||||
|
||||
String literals can be passed directly to these helpers; fixed arrays and
|
||||
mutable buffers can be passed as spans when the caller needs non-literal input.
|
||||
|
||||
The current helpers are byte-string primitives. They are suitable for protocol
|
||||
tokens, Rosetta-style ASCII examples, and fixed-buffer tools. Unicode text
|
||||
algorithms should be added as explicit APIs with documented behavior instead of
|
||||
being implied by these byte-span helpers.
|
||||
@@ -0,0 +1,162 @@
|
||||
## When To Use std.term
|
||||
|
||||
In Zerolang, use `std.term` when terminal code needs ANSI output sequences,
|
||||
hosted terminal metadata, nonblocking input reads, or key decoding for bytes
|
||||
already read from input.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| Helper | Returns | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.term.reset()` | `String` | ANSI SGR reset. |
|
||||
| `std.term.bold()` | `String` | ANSI SGR bold style. |
|
||||
| `std.term.dim()` | `String` | ANSI SGR dim style. |
|
||||
| `std.term.underline()` | `String` | ANSI SGR underline style. |
|
||||
| `std.term.inverse()` | `String` | ANSI SGR inverse style. |
|
||||
| `std.term.fgDefault()` | `String` | Reset foreground color. |
|
||||
| `std.term.fgBlack()` | `String` | Black foreground color. |
|
||||
| `std.term.fgRed()` | `String` | Red foreground color. |
|
||||
| `std.term.fgGreen()` | `String` | Green foreground color. |
|
||||
| `std.term.fgYellow()` | `String` | Yellow foreground color. |
|
||||
| `std.term.fgBlue()` | `String` | Blue foreground color. |
|
||||
| `std.term.fgMagenta()` | `String` | Magenta foreground color. |
|
||||
| `std.term.fgCyan()` | `String` | Cyan foreground color. |
|
||||
| `std.term.fgWhite()` | `String` | White foreground color. |
|
||||
| `std.term.bgDefault()` | `String` | Reset background color. |
|
||||
| `std.term.bgBlack()` | `String` | Black background color. |
|
||||
| `std.term.bgRed()` | `String` | Red background color. |
|
||||
| `std.term.bgGreen()` | `String` | Green background color. |
|
||||
| `std.term.bgYellow()` | `String` | Yellow background color. |
|
||||
| `std.term.bgBlue()` | `String` | Blue background color. |
|
||||
| `std.term.bgMagenta()` | `String` | Magenta background color. |
|
||||
| `std.term.bgCyan()` | `String` | Cyan background color. |
|
||||
| `std.term.bgWhite()` | `String` | White background color. |
|
||||
| `std.term.clearScreen()` | `String` | Clear the full terminal screen. |
|
||||
| `std.term.clearScreenDown()` | `String` | Clear from the cursor through the end of the screen. |
|
||||
| `std.term.clearScreenUp()` | `String` | Clear from the cursor through the start of the screen. |
|
||||
| `std.term.clearLine()` | `String` | Clear the current terminal line. |
|
||||
| `std.term.clearLineRight()` | `String` | Clear from the cursor through the end of the line. |
|
||||
| `std.term.clearLineLeft()` | `String` | Clear from the cursor through the start of the line. |
|
||||
| `std.term.cursorHome()` | `String` | Move the cursor to row 1, column 1. |
|
||||
| `std.term.cursorTo(buffer, row, column)` | `Maybe<Span<u8>>` | Writes a 1-based ANSI cursor-position sequence into caller storage. |
|
||||
| `std.term.cursorUp(buffer, count)` | `Maybe<Span<u8>>` | Writes an ANSI cursor-up sequence into caller storage; count `0` writes an empty span. |
|
||||
| `std.term.cursorDown(buffer, count)` | `Maybe<Span<u8>>` | Writes an ANSI cursor-down sequence into caller storage; count `0` writes an empty span. |
|
||||
| `std.term.cursorRight(buffer, count)` | `Maybe<Span<u8>>` | Writes an ANSI cursor-right sequence into caller storage; count `0` writes an empty span. |
|
||||
| `std.term.cursorLeft(buffer, count)` | `Maybe<Span<u8>>` | Writes an ANSI cursor-left sequence into caller storage; count `0` writes an empty span. |
|
||||
| `std.term.saveCursor()` | `String` | Save the current cursor position. |
|
||||
| `std.term.restoreCursor()` | `String` | Restore the saved cursor position. |
|
||||
| `std.term.hideCursor()` | `String` | Hide the terminal cursor. |
|
||||
| `std.term.showCursor()` | `String` | Show the terminal cursor. |
|
||||
| `std.term.enterAltScreen()` | `String` | Enter the alternate screen buffer. |
|
||||
| `std.term.leaveAltScreen()` | `String` | Leave the alternate screen buffer. |
|
||||
| `std.term.enterBracketedPaste()` | `String` | Enable bracketed paste markers in supporting terminals. |
|
||||
| `std.term.leaveBracketedPaste()` | `String` | Disable bracketed paste markers in supporting terminals. |
|
||||
| `std.term.enterMouseCapture()` | `String` | Enable SGR mouse tracking and drag/wheel capture in supporting terminals. |
|
||||
| `std.term.leaveMouseCapture()` | `String` | Disable the SGR mouse tracking modes enabled by `enterMouseCapture`. |
|
||||
| `std.term.keyNone()` | `u32` | Sentinel returned for incomplete or unsupported key bytes. |
|
||||
| `std.term.keyEscape()` | `u32` | Escape key code. |
|
||||
| `std.term.keyEnter()` | `u32` | Enter key code for `\r` or `\n`. |
|
||||
| `std.term.keyTab()` | `u32` | Tab key code. |
|
||||
| `std.term.keyBackspace()` | `u32` | Backspace key code for `0x7f` or `0x08`. |
|
||||
| `std.term.keyCtrlA()` | `u32` | Ctrl-A key code. |
|
||||
| `std.term.keyCtrlC()` | `u32` | Ctrl-C key code. |
|
||||
| `std.term.keyCtrlD()` | `u32` | Ctrl-D key code. |
|
||||
| `std.term.keyCtrlE()` | `u32` | Ctrl-E key code. |
|
||||
| `std.term.keyCtrlK()` | `u32` | Ctrl-K key code. |
|
||||
| `std.term.keyCtrlL()` | `u32` | Ctrl-L key code. |
|
||||
| `std.term.keyCtrlN()` | `u32` | Ctrl-N key code. |
|
||||
| `std.term.keyCtrlP()` | `u32` | Ctrl-P key code. |
|
||||
| `std.term.keyCtrlR()` | `u32` | Ctrl-R key code. |
|
||||
| `std.term.keyCtrlU()` | `u32` | Ctrl-U key code. |
|
||||
| `std.term.keyCtrlW()` | `u32` | Ctrl-W key code. |
|
||||
| `std.term.keyArrowUp()` | `u32` | Up-arrow key code above the Unicode scalar range. |
|
||||
| `std.term.keyArrowDown()` | `u32` | Down-arrow key code above the Unicode scalar range. |
|
||||
| `std.term.keyArrowRight()` | `u32` | Right-arrow key code above the Unicode scalar range. |
|
||||
| `std.term.keyArrowLeft()` | `u32` | Left-arrow key code above the Unicode scalar range. |
|
||||
| `std.term.keyDelete()` | `u32` | Delete key code above the Unicode scalar range. |
|
||||
| `std.term.keyHome()` | `u32` | Home key code above the Unicode scalar range. |
|
||||
| `std.term.keyEnd()` | `u32` | End key code above the Unicode scalar range. |
|
||||
| `std.term.keyPageUp()` | `u32` | Page Up key code above the Unicode scalar range. |
|
||||
| `std.term.keyPageDown()` | `u32` | Page Down key code above the Unicode scalar range. |
|
||||
| `std.term.keyInsert()` | `u32` | Insert key code above the Unicode scalar range. |
|
||||
| `std.term.keyShiftTab()` | `u32` | Shift-Tab key code above the Unicode scalar range. |
|
||||
| `std.term.keyF1()` | `u32` | F1 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF2()` | `u32` | F2 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF3()` | `u32` | F3 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF4()` | `u32` | F4 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF5()` | `u32` | F5 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF6()` | `u32` | F6 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF7()` | `u32` | F7 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF8()` | `u32` | F8 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF9()` | `u32` | F9 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF10()` | `u32` | F10 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF11()` | `u32` | F11 key code above the Unicode scalar range. |
|
||||
| `std.term.keyF12()` | `u32` | F12 key code above the Unicode scalar range. |
|
||||
| `std.term.keyPasteStart()` | `u32` | Bracketed paste start marker code above the Unicode scalar range. |
|
||||
| `std.term.keyPasteEnd()` | `u32` | Bracketed paste end marker code above the Unicode scalar range. |
|
||||
| `std.term.keyCode(bytes)` | `u32` | Decodes one key from caller-provided bytes, returning Unicode scalar values for printable UTF-8 and named constants for control keys. |
|
||||
| `std.term.keyByteLen(bytes)` | `usize` | Returns the decoded key width in bytes, or `0` for incomplete or unsupported input. |
|
||||
| `std.term.stdinIsTty()` | `Bool` | Reports whether standard input is attached to a terminal. |
|
||||
| `std.term.stdoutIsTty()` | `Bool` | Reports whether standard output is attached to a terminal. |
|
||||
| `std.term.widthOr(fallback)` | `usize` | Returns terminal columns, or `fallback` when unavailable. |
|
||||
| `std.term.heightOr(fallback)` | `usize` | Returns terminal rows, or `fallback` when unavailable. |
|
||||
| `std.term.enterRawMode()` | `Bool` | Puts standard input into raw, nonblocking terminal mode when supported. |
|
||||
| `std.term.leaveRawMode()` | `Bool` | Restores the terminal mode saved by `enterRawMode()`. |
|
||||
| `std.term.readInput(buffer)` | `Maybe<usize>` | Fills the caller buffer with currently available stdin bytes without blocking; returns `null` when no bytes are available or the input source is unsupported. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: ANSI/key helpers are pure; TTY/size helpers read hosted terminal metadata; raw-mode helpers update the hosted terminal; `readInput` reads from hosted stdin
|
||||
- allocation behavior: no allocation
|
||||
- target support: ANSI/key helpers are target-neutral; TTY/size/raw-mode/input helpers require hosted runtime support
|
||||
- error behavior: ANSI/key helpers are infallible; hosted helpers return fallbacks, `false`, or `null` when unavailable
|
||||
- ownership notes: ANSI sequences are borrowed static byte views
|
||||
- example: `conformance/native/pass/std-term-ansi.graph`
|
||||
|
||||
Example:
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
check world.out.write(std.term.enterAltScreen())
|
||||
check world.out.write(std.term.enterMouseCapture())
|
||||
check world.out.write(std.term.clearScreen())
|
||||
check world.out.write(std.term.cursorHome())
|
||||
var cursor: [24]u8 = [0_u8; 24]
|
||||
let top: Maybe<Span<u8>> = std.term.cursorTo(cursor, 1_usize, 1_usize)
|
||||
if top.has {
|
||||
check world.out.write(top.value)
|
||||
}
|
||||
check world.out.write(std.term.bold())
|
||||
check world.out.write(std.term.fgCyan())
|
||||
let width: usize = std.term.widthOr(80_usize)
|
||||
let height: usize = std.term.heightOr(24_usize)
|
||||
let raw: Bool = std.term.enterRawMode()
|
||||
var input: [16]u8 = [0_u8; 16]
|
||||
let pending: Maybe<usize> = std.term.readInput(input)
|
||||
if pending.has {
|
||||
let bytes: Span<u8> = std.mem.prefix(input, pending.value)
|
||||
let key: u32 = std.term.keyCode(bytes)
|
||||
if key == std.term.keyCtrlC() {
|
||||
check world.out.write("cancel")
|
||||
}
|
||||
}
|
||||
check world.out.write("ready")
|
||||
if raw {
|
||||
let restored: Bool = std.term.leaveRawMode()
|
||||
if !restored {
|
||||
return
|
||||
}
|
||||
}
|
||||
check world.out.write(std.term.reset())
|
||||
check world.out.write(std.term.leaveMouseCapture())
|
||||
check world.out.write(std.term.leaveAltScreen())
|
||||
}
|
||||
```
|
||||
|
||||
Key decoding is target-neutral: it parses bytes the caller already has. TTY and
|
||||
size helpers are hosted metadata calls and return caller fallbacks when a
|
||||
terminal size is unavailable. Raw mode is a hosted terminal capability: call
|
||||
`leaveRawMode()` before returning to normal line-oriented terminal input.
|
||||
`readInput()` is nonblocking; in raw mode it can be polled by interactive
|
||||
programs, and on noninteractive stdin it returns available piped bytes when the
|
||||
host exposes them.
|
||||
@@ -0,0 +1,59 @@
|
||||
## When To Use std.testing
|
||||
|
||||
In Zerolang, use `std.testing` inside test blocks for output checks and small boolean
|
||||
assertion helpers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.testing.isTrue(value)` | `Bool` | Passes through a `Bool` for readable `expect` statements. |
|
||||
| `std.testing.isFalse(value)` | `Bool` | Returns true when the value is false. |
|
||||
| `std.testing.equalBool(actual, expected)` | `Bool` | Compares booleans explicitly. |
|
||||
| `std.testing.equalUsize(actual, expected)` | `Bool` | Compares `usize` values explicitly. |
|
||||
| `std.testing.equalU32(actual, expected)` | `Bool` | Compares `u32` values explicitly. |
|
||||
| `std.testing.equalI32(actual, expected)` | `Bool` | Compares `i32` values explicitly. |
|
||||
| `std.testing.equalBytes(actual, expected)` | `Bool` | Compares byte spans by value. |
|
||||
| `std.testing.containsBytes(actual, needle)` | `Bool` | Checks whether a byte span contains a byte substring. |
|
||||
| `std.testing.startsWith(actual, prefix)` | `Bool` | Checks a byte prefix. |
|
||||
| `std.testing.endsWith(actual, suffix)` | `Bool` | Checks a byte suffix. |
|
||||
| `std.testing.notEqualBytes(actual, expected)` | `Bool` | Checks byte-span inequality. |
|
||||
| `std.testing.diffIndexBytes(actual, expected)` | `Maybe<usize>` | Returns the first differing byte index, or `null` when spans are equal. |
|
||||
| `std.testing.jsonFieldEquals(actual, key, expected)` | `Bool` | Compares a raw top-level JSON field value. |
|
||||
| `std.testing.jsonPathEquals(actual, path, expected)` | `Bool` | Compares a raw dotted JSON path value. |
|
||||
| `std.testing.caseName(buffer, suite, index)` | `Maybe<Span<u8>>` | Writes a stable table-case name like `suite[3]` into caller storage. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: none for scalar comparisons; memory for byte-span/case-name checks; parse for JSON checks
|
||||
- allocation behavior: no allocation
|
||||
- target support: target-neutral
|
||||
- error behavior: infallible
|
||||
- ownership notes: no ownership transfer
|
||||
- example: `examples/std-testing-log.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
test "testing helpers support direct test blocks" {
|
||||
let diff: Maybe<usize> = std.testing.diffIndexBytes("zero", "zeta")
|
||||
expect std.testing.equalU32(42_u32, 42_u32)
|
||||
expect std.testing.equalBytes("zero", "zero")
|
||||
expect std.testing.containsBytes("zerolang", "lang")
|
||||
expect diff.has && diff.value == 2
|
||||
expect std.testing.jsonPathEquals("{\"user\":{\"name\":\"zero\"}}", "user.name", "\"zero\"")
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
`std.testing` helpers return `Bool`; they do not register tests, hide failures,
|
||||
allocate output, or produce process I/O. Use them inside ordinary `expect`
|
||||
statements so the compiler and `zero test` keep one visible test model.
|
||||
|
||||
The byte helpers are byte-span predicates. They are suitable for output checks,
|
||||
protocol fixtures, and small examples where a full parser would be more complex
|
||||
than the assertion.
|
||||
|
||||
JSON helpers compare raw JSON values, so string expectations include their JSON
|
||||
quotes, such as `"\"zero\""`.
|
||||
@@ -0,0 +1,36 @@
|
||||
## When To Use std.text
|
||||
|
||||
In Zerolang, use `std.text` for ASCII and UTF-8 byte-backed validation.
|
||||
|
||||
Runnable today:
|
||||
|
||||
`std.text` is for byte-backed text validation and counting. It does not imply
|
||||
locale-aware case mapping, grapheme segmentation, normalization, or display-width
|
||||
rules.
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.text.isAscii(text)` | `Bool` | Checks that every byte is below `0x80`. |
|
||||
| `std.text.utf8Valid(text)` | `Bool` | Validates UTF-8 byte structure, rejecting overlong encodings, surrogate code points, and values above `U+10FFFF`. |
|
||||
| `std.text.utf8Len(text)` | `Maybe<usize>` | Counts Unicode scalar values when UTF-8 is valid; returns `null` on invalid input. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let valid: [2]u8 = [195_u8, 169_u8]
|
||||
let invalid: [1]u8 = [128_u8]
|
||||
let len: Maybe<usize> = std.text.utf8Len(valid)
|
||||
if !std.text.isAscii(valid) && std.text.utf8Valid(valid) && !std.text.utf8Valid(invalid) && len.has && len.value == 1 {
|
||||
check world.out.write("text ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none.
|
||||
|
||||
Allocation behavior: no allocation.
|
||||
|
||||
Error behavior: `utf8Len` returns `null` for invalid UTF-8.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,102 @@
|
||||
## When To Use std.time
|
||||
|
||||
In Zerolang, use `std.time` for duration math, RFC 3339 date and time validation
|
||||
and parsing, and target-gated monotonic or wall-clock helpers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.time.ns(value)` | `Duration` | Builds a nanosecond duration. |
|
||||
| `std.time.us(value)` | `Duration` | Builds a microsecond duration. |
|
||||
| `std.time.ms(value)` | `Duration` | Builds a millisecond duration. |
|
||||
| `std.time.seconds(value)` | `Duration` | Builds a second duration. |
|
||||
| `std.time.minutes(value)` | `Duration` | Builds a minute duration. |
|
||||
| `std.time.hours(value)` | `Duration` | Builds an hour duration. |
|
||||
| `std.time.zero()` | `Duration` | Returns a zero duration. |
|
||||
| `std.time.add(a, b)` | `Duration` | Adds two durations. |
|
||||
| `std.time.sub(a, b)` | `Duration` | Subtracts one duration from another. |
|
||||
| `std.time.min(a, b)` | `Duration` | Returns the smaller duration. |
|
||||
| `std.time.max(a, b)` | `Duration` | Returns the larger duration. |
|
||||
| `std.time.clamp(value, low, high)` | `Duration` | Clamps a duration between normalized bounds. |
|
||||
| `std.time.abs(value)` | `Duration` | Returns a non-negative duration magnitude. |
|
||||
| `std.time.between(start, end)` | `Duration` | Returns the non-negative duration between two values. |
|
||||
| `std.time.hasElapsed(start, now, timeout)` | `Bool` | Reports whether a timeout window has elapsed. |
|
||||
| `std.time.deadlineAfter(start, timeout)` | `Duration` | Builds a deadline by adding a timeout to a start instant. |
|
||||
| `std.time.remainingUntil(deadline, now)` | `Duration` | Returns remaining time or zero once the deadline has passed. |
|
||||
| `std.time.deadlineExpired(deadline, now)` | `Bool` | Reports whether `now` is at or past `deadline`. |
|
||||
| `std.time.sleep(duration)` | `Bool` | Sleeps for a hosted non-negative duration; returns `false` on host failure. |
|
||||
| `std.time.asNs(value)` | `i64` | Converts to nanoseconds. |
|
||||
| `std.time.asUsFloor(value)` | `i64` | Converts to whole microseconds. |
|
||||
| `std.time.asMsFloor(value)` | `i32` | Converts to whole milliseconds. |
|
||||
| `std.time.asSecondsFloor(value)` | `i64` | Converts to whole seconds. |
|
||||
| `std.time.lessThan(a, b)` | `Bool` | Compares two durations. |
|
||||
| `std.time.isZero(value)` | `Bool` | Reports whether a duration is zero. |
|
||||
| `std.time.monotonic()` | `Duration` | Reads a monotonic target clock where available. |
|
||||
| `std.time.wallSeconds()` | `i64` | Reads target wall-clock seconds where available. |
|
||||
| `std.time.isRfc3339Date(text)` | `Bool` | Validates an RFC 3339 full-date with leap years and days-in-month. |
|
||||
| `std.time.isRfc3339Time(text)` | `Bool` | Validates an RFC 3339 full-time with fractional seconds, numeric offsets, and the leap-second rule. |
|
||||
| `std.time.isRfc3339DateTime(text)` | `Bool` | Validates an RFC 3339 date-time joined by `T` or `t`. |
|
||||
| `std.time.parseRfc3339DateTimeOr(text, fallback)` | `i64` | Parses a date-time into UTC epoch seconds; returns the fallback when invalid. Fractional seconds truncate; a valid leap second maps to the same epoch second as `:59`. |
|
||||
| `std.time.isLeapYear(year)` | `Bool` | Gregorian leap-year predicate. |
|
||||
| `std.time.daysInMonth(year, month)` | `u32` | Days in a month; returns `0` for invalid months. |
|
||||
| `std.time.writeDurationNs(buffer, value)` | `Maybe<Span<u8>>` | Writes nanoseconds with an `ns` suffix into caller storage. |
|
||||
| `std.time.writeDurationMs(buffer, value)` | `Maybe<Span<u8>>` | Writes whole milliseconds with an `ms` suffix into caller storage. |
|
||||
| `std.time.writeDurationSeconds(buffer, value)` | `Maybe<Span<u8>>` | Writes whole seconds with an `s` suffix into caller storage. |
|
||||
|
||||
Current limits:
|
||||
|
||||
- Target-specific clock availability diagnostics.
|
||||
- Timer handles and fake-clock handles are not public APIs.
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: time
|
||||
- allocation behavior: no allocation
|
||||
- target support: duration math is target-neutral; clock reads and sleep require a time-capable target
|
||||
- error behavior: infallible helpers; RFC 3339 validators return `Bool` and the epoch parser returns its fallback for invalid text
|
||||
- ownership notes: no ownership transfer
|
||||
- example: `examples/std-platform.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let a: Duration = std.time.ms(250)
|
||||
let b: Duration = std.time.seconds(1)
|
||||
let total: Duration = std.time.add(a, b)
|
||||
let span: Duration = std.time.between(std.time.seconds(2), std.time.ms(250))
|
||||
let deadline: Duration = std.time.deadlineAfter(std.time.seconds(10), std.time.ms(500))
|
||||
let remaining: Duration = std.time.remainingUntil(deadline, std.time.seconds(10))
|
||||
let slept: Bool = std.time.sleep(std.time.zero())
|
||||
var text_storage: [32]u8 = [0_u8; 32]
|
||||
let text: Maybe<Span<u8>> = std.time.writeDurationMs(text_storage, total)
|
||||
if slept && std.time.asMsFloor(total) == 1250 && std.time.asMsFloor(span) == 1750 && (std.time.asMsFloor(remaining) == 500 && text.has) {
|
||||
check world.out.write("duration ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
RFC 3339 validation includes the exact leap-second rule: `seconds == 60` is
|
||||
valid only when the time normalized by its numeric offset equals `23:59:60`
|
||||
UTC, wrapping modulo 24 hours. `00:29:60+00:30` is valid because it normalizes
|
||||
to `23:59:60` UTC on the previous day, while `23:59:60-01:00` is invalid
|
||||
because it normalizes to `00:59:60` UTC.
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let wrapped: Bool = std.time.isRfc3339Time("00:29:60+00:30")
|
||||
let not_leap: Bool = std.time.isRfc3339Time("23:59:60-01:00")
|
||||
let epoch: i64 = std.time.parseRfc3339DateTimeOr("2000-01-01T00:00:00Z", -1)
|
||||
if wrapped && !not_leap && epoch == 946684800 && std.time.daysInMonth(2024, 2) == 29 {
|
||||
check world.out.write("rfc3339 ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
Time is an effect when it observes or waits on the outside world.
|
||||
|
||||
Pure duration math can stay allocation-free and target-independent.
|
||||
Timer and fake-clock APIs are not exposed in the current public surface.
|
||||
@@ -0,0 +1,67 @@
|
||||
## When To Use std.toml
|
||||
|
||||
In Zerolang, use `std.toml` for TOML validation, shallow field lookup, and typed scalar
|
||||
decode helpers.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.toml.validate(text)` | `Bool` | Checks the current TOML subset without allocation. |
|
||||
| `std.toml.validateBytes(bytes)` | `Bool` | Checks a `Span<u8>` TOML payload without allocation. |
|
||||
| `std.toml.field(bytes, key)` | `Maybe<Span<u8>>` | Returns the raw value for a direct, dotted, or shallow table field. |
|
||||
| `std.toml.stringDecode(buffer, value)` | `Maybe<Span<u8>>` | Decodes a TOML string value into caller storage. |
|
||||
| `std.toml.string(buffer, bytes, key)` | `Maybe<Span<u8>>` | Looks up and decodes a TOML string field. |
|
||||
| `std.toml.u32(bytes, key)` | `Maybe<u32>` | Looks up and decodes an unsigned integer field. |
|
||||
| `std.toml.i32(bytes, key)` | `Maybe<i32>` | Looks up and decodes a signed integer field. |
|
||||
| `std.toml.bool(bytes, key)` | `Maybe<Bool>` | Looks up and decodes a boolean field. |
|
||||
| `std.toml.arrayCount(value)` | `Maybe<usize>` | Counts items in a raw array value. |
|
||||
| `std.toml.arrayValue(value, index)` | `Maybe<Span<u8>>` | Borrows a raw array item by ordinal. |
|
||||
| `std.toml.arrayString(buffer, value, index)` | `Maybe<Span<u8>>` | Decodes a string array item into caller storage. |
|
||||
| `std.toml.arrayU32(value, index)` | `Maybe<u32>` | Decodes an unsigned integer array item. |
|
||||
| `std.toml.arrayI32(value, index)` | `Maybe<i32>` | Decodes a signed integer array item. |
|
||||
| `std.toml.arrayBool(value, index)` | `Maybe<Bool>` | Decodes a boolean array item. |
|
||||
| `std.toml.writeKeyValueString(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one string key/value line. |
|
||||
| `std.toml.writeKeyValueU32(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one unsigned integer key/value line. |
|
||||
| `std.toml.writeKeyValueBool(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one boolean key/value line. |
|
||||
| `std.toml.writeTableHeader(buffer, table)` | `Maybe<Span<u8>>` | Writes one table header line. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: parse
|
||||
- allocation behavior: allocation-free; decoded strings and writer output use caller storage
|
||||
- target support: target-neutral
|
||||
- error behavior: `Maybe` helpers return null on malformed or missing fields
|
||||
- ownership notes: returned raw fields borrow from the input span; decoded strings borrow from the caller buffer
|
||||
- examples: `conformance/native/pass/std-toml-basic.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let input: Span<u8> = "[package]\nname = \"demo\"\n\n[features]\ngraph = true\nlevels = [1, 2, 3]\n"
|
||||
var name_buffer: [16]u8 = [0_u8; 16]
|
||||
let name: Maybe<Span<u8>> = std.toml.string(name_buffer, input, "package.name")
|
||||
let graph: Maybe<Bool> = std.toml.bool(input, "features.graph")
|
||||
let levels: Maybe<Span<u8>> = std.toml.field(input, "features.levels")
|
||||
var count: Maybe<usize> = null
|
||||
if levels.has {
|
||||
count = std.toml.arrayCount(levels.value)
|
||||
}
|
||||
if std.toml.validateBytes(input) && name.has && graph.has && graph.value && count.has && count.value == 3 {
|
||||
check world.out.write("toml ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
The current TOML helper surface is deliberately narrow. It supports the package
|
||||
manifest subset used by Zero packages: tables, dotted keys, strings, booleans,
|
||||
integers, scalar arrays, and small writer helpers. Field lookup is shallow and table-aware, so
|
||||
`std.toml.string(buffer, input, "package.name")` can read `name` inside a
|
||||
`[package]` table.
|
||||
|
||||
The helpers avoid hidden allocation. Use `field` when a raw value slice is
|
||||
enough, and use `string` or `stringDecode` when escape decoding into explicit
|
||||
caller storage is required.
|
||||
@@ -0,0 +1,60 @@
|
||||
## When To Use std.unicode
|
||||
|
||||
In Zerolang, use `std.unicode` for UTF-8 codepoint decode/encode iteration and
|
||||
codepoint-class checks. For whole-span validation and codepoint counting, use
|
||||
the existing `std.text.utf8Valid` and `std.text.utf8Len` helpers; `std.unicode`
|
||||
extends them with per-codepoint access.
|
||||
|
||||
Decoding is strict UTF-8: overlong encodings, surrogate codepoints, values
|
||||
above `U+10FFFF`, and truncated sequences all return `null`.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.unicode.decodeAt(text, index)` | `Maybe<u32>` | Decodes the codepoint starting at a byte index; `null` for invalid or out-of-range positions. |
|
||||
| `std.unicode.widthAt(text, index)` | `Maybe<usize>` | Byte width of the sequence at a byte index; advance `index` by this to iterate codepoints. |
|
||||
| `std.unicode.nextIndex(text, index)` | `Maybe<usize>` | Next byte index after the codepoint at `index`; `null` for invalid input. |
|
||||
| `std.unicode.invalidIndex(text)` | `usize` | First invalid UTF-8 byte index, or the input length when valid. |
|
||||
| `std.unicode.decodeStatusAt(text, index)` | `u32` | Strict UTF-8 status code at a byte index. |
|
||||
| `std.unicode.statusName(status)` | `String` | Names a status code such as `truncated sequence`. |
|
||||
| `std.unicode.encode(buffer, cp)` | `Maybe<Span<u8>>` | Encodes a codepoint as UTF-8 into a caller buffer; `null` for surrogates, values above `U+10FFFF`, or a too-small buffer. |
|
||||
| `std.unicode.encodedWidth(cp)` | `Maybe<usize>` | UTF-8 byte width a codepoint needs (1-4); `null` for invalid codepoints. |
|
||||
| `std.unicode.isDigit(cp)` | `Bool` | ASCII digit class, matching regex `\d` semantics by codepoint. |
|
||||
| `std.unicode.isWord(cp)` | `Bool` | ASCII word class `[A-Za-z0-9_]`, matching regex `\w` semantics. |
|
||||
| `std.unicode.isSpace(cp)` | `Bool` | ECMA-262 whitespace plus line terminators, matching regex `\s` semantics. |
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let text: Span<u8> = std.mem.span("aé💯")
|
||||
var index: usize = 0
|
||||
var count: usize = 0
|
||||
while index < std.mem.len(text) {
|
||||
let next: Maybe<usize> = std.unicode.nextIndex(text, index)
|
||||
if !next.has {
|
||||
return
|
||||
}
|
||||
index = next.value
|
||||
count = count + 1
|
||||
}
|
||||
var storage: [4]u8 = [0; 4]
|
||||
let buffer: MutSpan<u8> = storage
|
||||
let encoded: Maybe<Span<u8>> = std.unicode.encode(buffer, 233)
|
||||
if count == 3 && std.unicode.invalidIndex(text) == std.mem.len(text) && (encoded.has && std.mem.len(encoded.value) == 2) {
|
||||
check world.out.write("unicode ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Effects: none.
|
||||
|
||||
Allocation behavior: `encode` writes the caller buffer; all other helpers
|
||||
allocate nothing.
|
||||
|
||||
Error behavior: decode/encode and cursor helpers return `null` for invalid
|
||||
input. `decodeStatusAt` and `statusName` provide allocation-free status details;
|
||||
class helpers are infallible.
|
||||
|
||||
Target support: current compiler targets.
|
||||
@@ -0,0 +1,71 @@
|
||||
## When To Use std.url
|
||||
|
||||
In Zerolang, use `std.url` for lexical URL splitting, percent encoding, decoded
|
||||
query lookup, form-urlencoded bodies, and query appending.
|
||||
|
||||
Runnable today:
|
||||
|
||||
| API | Return | Notes |
|
||||
| --- | --- | --- |
|
||||
| `std.url.percentEncode(buffer, bytes)` | `Maybe<Span<u8>>` | Percent-encodes bytes into caller storage. |
|
||||
| `std.url.percentDecode(buffer, bytes)` | `Maybe<Span<u8>>` | Percent-decodes bytes into caller storage. |
|
||||
| `std.url.queryEscape(buffer, bytes)` | `Maybe<Span<u8>>` | Query-escapes bytes, using `+` for spaces. |
|
||||
| `std.url.queryUnescape(buffer, bytes)` | `Maybe<Span<u8>>` | Query-unescapes bytes, converting `+` back to space. |
|
||||
| `std.url.scheme(url)` | `Maybe<Span<u8>>` | Borrows the URL scheme if present. |
|
||||
| `std.url.authority(url)` | `Maybe<Span<u8>>` | Borrows the URL authority if present. |
|
||||
| `std.url.host(url)` | `Maybe<Span<u8>>` | Borrows the host from the URL authority. |
|
||||
| `std.url.path(url)` | `Span<u8>` | Borrows the path or an empty suffix. |
|
||||
| `std.url.query(url)` | `Maybe<Span<u8>>` | Borrows the raw query string if present. |
|
||||
| `std.url.fragment(url)` | `Maybe<Span<u8>>` | Borrows the raw fragment if present. |
|
||||
| `std.url.queryValue(query, key)` | `Maybe<Span<u8>>` | Borrows a raw query parameter value by key. |
|
||||
| `std.url.queryValueDecoded(buffer, query, key)` | `Maybe<Span<u8>>` | Looks up a raw or escaped query key and writes the decoded value. |
|
||||
| `std.url.writeQueryParam(buffer, key, value)` | `Maybe<Span<u8>>` | Writes an escaped `key=value` query parameter. |
|
||||
| `std.url.writeFormField(buffer, key, value)` | `Maybe<Span<u8>>` | Writes one application/x-www-form-urlencoded field. |
|
||||
| `std.url.appendFormField(buffer, form, field)` | `Maybe<Span<u8>>` | Appends one encoded field to an existing form body. |
|
||||
| `std.url.formValue(buffer, form, key)` | `Maybe<Span<u8>>` | Looks up a form field by raw or escaped key and writes the decoded value. |
|
||||
| `std.url.appendQuery(buffer, base, query)` | `Maybe<Span<u8>>` | Writes a URL with an appended raw query segment. |
|
||||
| `std.url.writeUrl(buffer, scheme, host, path)` | `Maybe<Span<u8>>` | Writes a `scheme://host/path` URL. |
|
||||
| `std.url.appendFragment(buffer, base, fragment)` | `Maybe<Span<u8>>` | Writes a URL with an appended raw fragment. |
|
||||
|
||||
Metadata labels:
|
||||
|
||||
- effects: parse
|
||||
- allocation behavior: no allocation; writers use caller storage
|
||||
- target support: target-neutral
|
||||
- error behavior: `Maybe` helpers return null on malformed input or insufficient storage
|
||||
- ownership notes: borrowed slices point into the input; encoded output points into caller storage
|
||||
- examples: `conformance/native/pass/std-codec-json-url.graph`
|
||||
|
||||
## Example
|
||||
|
||||
```zero
|
||||
pub fn main(world: World) -> Void raises {
|
||||
let url: Span<u8> = "https://example.com/path?q=zero%20lang#part"
|
||||
let host: Maybe<Span<u8>> = std.url.host(url)
|
||||
let query: Maybe<Span<u8>> = std.url.query(url)
|
||||
let fragment: Maybe<Span<u8>> = std.url.fragment(url)
|
||||
var out: [48]u8 = [0_u8; 48]
|
||||
var param_buf: [16]u8 = [0_u8; 16]
|
||||
let param: Maybe<Span<u8>> = std.url.writeQueryParam(param_buf, "q", "zero lang")
|
||||
var decoded_buf: [16]u8 = [0_u8; 16]
|
||||
var decoded: Maybe<Span<u8>> = null
|
||||
var next: Maybe<Span<u8>> = null
|
||||
if param.has {
|
||||
next = std.url.appendQuery(out, "https://example.com/path", param.value)
|
||||
}
|
||||
if query.has {
|
||||
decoded = std.url.queryValueDecoded(decoded_buf, query.value, "q")
|
||||
}
|
||||
if host.has && query.has && fragment.has && decoded.has && next.has && std.mem.eql(host.value, "example.com") && std.mem.eql(decoded.value, "zero lang") {
|
||||
check world.out.write("url ok\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
||||
URL helpers are lexical and byte-oriented. They do not resolve DNS, normalize
|
||||
paths, or allocate. Decoding rejects malformed percent escapes. Form helpers use
|
||||
the same encoding as query strings: spaces become `+`, and other non-unreserved
|
||||
bytes are percent-escaped. URL builders expect path, query, and fragment bytes
|
||||
that are already escaped for their position.
|
||||
Reference in New Issue
Block a user