KeySpan
struct KeySpanWhere an object key's bytes are, and whether they need expanding.
Implements Copyable, ImplicitlyCopyable, Movable
- Fields
start: Intend: Intescaped: Bool
json_mlake ยท API
json.readerKeySpanstruct KeySpanWhere an object key's bytes are, and whether they need expanding.
Implements Copyable, ImplicitlyCopyable, Movable
start: Intend: Intescaped: BoolJsonReaderstruct JsonReader[origin: ImmOrigin]A forward cursor over JSON bytes.
Borrows its input rather than owning it, so decoding a document does not begin by copying it.
Implements Movable
data: Span[UInt8, origin]pos: Intdepth: Intmax_depth: Intscratch: List[UInt8]: Reused buffer for keys and strings that contain escapes.__init__def __init__(out self, data: Span[UInt8, origin], *, max_depth: Int = Int(1024)) errordef error(self, message: String) -> Error A located error at the cursor.
error_atdef error_at(self, message: String, at: Int) -> Error skip_wsdef skip_ws(mut self) Advance past whitespace.
A hand-written inline check for "the next byte is not whitespace" before this call was measured and was slower on every shape: the shared scan already opens with a scalar prelude for exactly that case, and the extra branch only added work.
peekdef peek(mut self) -> UInt8 The next significant byte, or 0 at end of input.
Zero is safe as the end marker: a NUL outside a string is not in the grammar, so every caller that compares against a real byte rejects it anyway.
at_enddef at_end(mut self) -> Bool expect_enddef expect_end(mut self) Nothing but whitespace may follow the top-level value.
expect_object_begindef expect_object_begin(mut self) expect_array_begindef expect_array_begin(mut self) next_memberdef next_member(mut self, first: Bool) -> Bool Advance to the next object member. False at the closing brace.
Each byte is looked at once. The previous shape called peek
three times per member, and every call re-ran the whitespace
scan from a position where nothing had been consumed since the
last one; the byte it returned was already in a register.
next_elementdef next_element(mut self, first: Bool) -> Bool Advance to the next array element. False at the closing bracket.
read_stringdef read_string(mut self) -> String read_keydef read_key(mut self) -> KeySpan Consume an object key and its colon, without materializing it.
A key is compared against a field name far more often than it
is kept, and building a String for each comparison is what
made the old read path allocate on the order of the square of
the field count per record.
key_matchesdef key_matches[name: StringSpan[ImmStaticOrigin]](self, key: KeySpan) -> Bool Whether an unescaped key spells name.
Takes self immutably and the name as a parameter, both for
the same reason: this runs inside a comptime-unrolled loop over
a struct's fields, and a method that could mutate the reader
forces the compiler to reload its span and position after every
call in that loop. With the name known at compile time the
length test is a comparison against a constant, which settles
most fields without looking at a byte.
key_equalsdef key_equals(mut self, key: KeySpan, name: StringSpan[ImmStaticOrigin]) -> Bool Whether a key spells name, expanding escapes if it has any.
The escaped case is why this takes self mutably: the key is
unescaped into the reader's scratch buffer before comparing, so
that a key written "a" still matches a field named a.
Callers on the hot path go through key_matches and only reach
here when the key actually carries an escape.
key_textdef key_text(mut self, key: KeySpan) -> String A key as a String, for the cases that must keep it.
read_booldef read_bool(mut self) -> Bool read_nulldef read_null(mut self) try_nulldef try_null(mut self) -> Bool Consume null if that is what comes next.
read_intdef read_int[dtype: DType](mut self) -> Scalar[dtype] Read an integer field.
Most integers in a document are a handful of digits, and reaching them through the full number scanner costs four outlined calls: the scanner walks the digits once to check the grammar and again to accumulate, peeks for an eight-digit SWAR block that a short integer never has, and hands back a forty-eight byte token that the range check then unpacks.
The path below commits only when it has itself established the
whole token: an optional minus, a leading digit in 1-9 so that
anything starting 0 defers, at most eighteen digits so the
accumulator cannot overflow, and a terminator that cannot
continue a number. Everything else -- 0, -0, a fraction, an
exponent, a leading zero, nineteen digits or more -- falls
through to the scanner, which stays the only authority on the
grammar, the messages and the offsets they carry.
read_floatdef read_float[dtype: DType](mut self) -> Scalar[dtype] skip_valuedef skip_value(mut self) -> Tuple[Int, Int] Consume one value without binding it. Returns its byte range.
Used for a member no field wants, and to hand a subtree to a type that parses itself. Validating rather than merely counting brackets matters: an unknown field is still part of the document, and accepting rubbish inside it would mean two readers of the same bytes disagree about whether they are JSON.
comptime DEFAULT_MAX_DEPTH = 1024Nesting past this is refused. Deep input is otherwise a stack hazard.