/// Parsing of bare items (RFC 9651 §4.2.3.1) and each scalar type:
/// integer, decimal, string, token, byte sequence, boolean, date, and
/// display string (§4.2.4 through §4.2.10).
///|
/// Parses a bare item, dispatching on the first byte.
pub fn parse_bare_item_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[BareItem, SfError] {
let b = match cursor.peek() {
None => return Err(err_end(cursor))
Some(v) => v
}
if b == b'-' || is_digit(b) {
return parse_integer_or_decimal_cursor(cursor, limits)
}
if b == b'"' {
return parse_string_cursor(cursor, limits).map(s => StringItem(s))
}
if is_alpha(b) || b == b'*' {
return parse_token_cursor(cursor, limits).map(s => Token(s))
}
if b == b':' {
return parse_byte_sequence_cursor(cursor, limits).map(bs => ByteSequence(bs))
}
if b == b'?' {
return parse_boolean_cursor(cursor).map(v => Boolean(v))
}
if b == b'@' {
return parse_date_cursor(cursor, limits).map(v => Date(v))
}
if b == b'%' {
return parse_display_string_cursor(cursor, limits).map(s => DisplayString(s))
}
Err(err_at(cursor, InvalidTopLevelType))
}
///|
/// Parses an Integer or Decimal (RFC 9651 §4.2.4). Returns an
/// [`BareItem`] carrying the parsed value.
pub fn parse_integer_or_decimal_cursor(
cursor : Cursor,
_limits : ParseLimits,
) -> Result[BareItem, SfError] {
let mut sign : Int64 = 1L
if cursor.consume_if(b'-') {
sign = -1L
}
let fb = match cursor.peek() {
None => return Err(err_at(cursor, InvalidInteger))
Some(v) => v
}
if !is_digit(fb) {
return Err(err_at(cursor, InvalidInteger))
}
let int_digits : Array[Byte] = []
let frac_digits : Array[Byte] = []
let mut seen_dot = false
while true {
let next = cursor.peek()
match next {
None => break
Some(b2) =>
if is_digit(b2) {
let _ = cursor.consume()
if seen_dot {
frac_digits.push(b2)
if frac_digits.length() > 3 {
return Err(err_at(cursor, InvalidDecimal))
}
} else {
int_digits.push(b2)
if int_digits.length() > 15 {
return Err(err_at(cursor, InvalidInteger))
}
}
} else if !seen_dot && b2 == b'.' {
if int_digits.length() > 12 {
return Err(err_at(cursor, InvalidDecimal))
}
let _ = cursor.consume()
seen_dot = true
} else {
break
}
}
}
if seen_dot {
if frac_digits.is_empty() {
return Err(err_at(cursor, InvalidDecimal))
}
let scale = frac_digits.length()
let int_part = digits_to_int64(int_digits)
let frac_part = digits_to_int64(frac_digits)
let pow : Int64 = pow10_i64(scale)
let coefficient = (int_part * pow + frac_part) * sign
Ok(Decimal(SfDecimal::new(coefficient, scale)))
} else {
let n = digits_to_int64(int_digits) * sign
Ok(Integer(n))
}
}
///|
/// Parses a quoted String (RFC 9651 §4.2.5). Only printable ASCII and the
/// two permitted escapes (`\"` and `\\`) are accepted.
pub fn parse_string_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[String, SfError] {
if !cursor.consume_if(b'"') {
return Err(err_at(cursor, InvalidString))
}
let buf = @buffer.Buffer(size_hint=16)
while true {
let ch = cursor.consume()
match ch {
None => return Err(err_end(cursor))
Some(b) => {
if b == b'\\' {
match cursor.consume() {
None => return Err(err_end(cursor))
Some(nxt) =>
if nxt == b'"' || nxt == b'\\' {
buf.write_byte(nxt)
} else {
return Err(err_at(cursor, InvalidEscape))
}
}
} else if b == b'"' {
return Ok(bytes_to_ascii(buf.to_bytes()))
} else if !is_visible_ascii(b) {
return Err(err_at(cursor, InvalidString))
} else {
buf.write_byte(b)
}
if buf.length() > limits.max_string_bytes {
return Err(
err_with(
cursor,
InputTooLarge,
cursor.position(),
"string exceeds max_string_bytes",
),
)
}
}
}
}
Err(err_end(cursor))
}
///|
/// Parses a Token (RFC 9651 §4.2.6). The first character must be ALPHA or
/// `*`; subsequent characters are `tchar` / `:` / `/`.
pub fn parse_token_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[String, SfError] {
let first = cursor.peek()
match first {
None => Err(err_end(cursor))
Some(b) =>
if !(is_alpha(b) || b == b'*') {
Err(err_at(cursor, InvalidToken))
} else {
let _ = cursor.consume()
let buf = @buffer.Buffer(size_hint=8)
buf.write_byte(b)
while true {
let next = cursor.peek()
match next {
None => break
Some(b2) => {
if !is_token_char(b2) {
break
}
let _ = cursor.consume()
buf.write_byte(b2)
if buf.length() > limits.max_string_bytes {
return Err(
err_with(
cursor,
InputTooLarge,
cursor.position(),
"token exceeds max_string_bytes",
),
)
}
}
}
}
Ok(bytes_to_ascii(buf.to_bytes()))
}
}
}
///|
/// Parses a Byte Sequence (RFC 9651 §4.2.7), delimited by colons and
/// base64-encoded. Padding is synthesized when missing, matching the RFC's
/// "SHOULD NOT fail" guidance for both missing padding and non-zero pad
/// bits.
pub fn parse_byte_sequence_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[Bytes, SfError] {
if !cursor.consume_if(b':') {
return Err(err_at(cursor, InvalidByteSequence))
}
let content_start = cursor.position()
// Find the closing ':'.
let mut end = content_start
let mut closed = false
while end < cursor.input_length() {
if cursor.at(end) == Some(b':') {
closed = true
break
}
end = end + 1
}
if !closed {
return Err(err_at(cursor, InvalidByteSequence))
}
let content = cursor.slice(content_start, end)
cursor.restore(end + 1)
// Validate the base64 alphabet.
for i in 0.. {
if out.length() > limits.max_string_bytes {
return Err(
err_with(
cursor,
InputTooLarge,
cursor.position(),
"byte sequence exceeds max_string_bytes",
),
)
}
Ok(out)
}
Err(e) => Err(e)
}
}
///|
/// Parses a Boolean (RFC 9651 §4.2.8): exactly `?0` or `?1`.
pub fn parse_boolean_cursor(cursor : Cursor) -> Result[Bool, SfError] {
if !cursor.consume_if(b'?') {
return Err(err_at(cursor, InvalidBoolean))
}
if cursor.consume_if(b'1') {
return Ok(true)
}
if cursor.consume_if(b'0') {
return Ok(false)
}
Err(err_at(cursor, InvalidBoolean))
}
///|
/// Parses a Date (RFC 9651 §4.2.9): `@` followed by an Integer. The value
/// is kept as a UTC seconds delta; no timezone conversion is performed.
pub fn parse_date_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[Int64, SfError] {
if !cursor.consume_if(b'@') {
return Err(err_at(cursor, InvalidDate))
}
match parse_integer_or_decimal_cursor(cursor, limits) {
Err(e) => Err(e)
Ok(Integer(v)) => Ok(v)
Ok(Decimal(_)) => Err(err_at(cursor, InvalidDate))
Ok(_) => Err(err_at(cursor, InvalidDate))
}
}
///|
/// Parses a Display String (RFC 9651 §4.2.10): `%"..."` with percent-encoded
/// UTF-8 bytes. Percent hex digits must be lowercase.
pub fn parse_display_string_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[String, SfError] {
if !cursor.consume_if(b'%') {
return Err(err_at(cursor, InvalidDisplayString))
}
if !cursor.consume_if(b'"') {
return Err(err_at(cursor, InvalidDisplayString))
}
let buf = @buffer.Buffer(size_hint=16)
while true {
let ch = cursor.consume()
match ch {
None => return Err(err_end(cursor))
Some(b) => {
if !is_visible_ascii(b) {
return Err(err_at(cursor, InvalidDisplayString))
}
if b == b'%' {
let h1 = cursor.consume()
let h2 = cursor.consume()
match (h1, h2) {
(Some(a), Some(c)) => {
if !is_lower_hex(a) || !is_lower_hex(c) {
return Err(err_at(cursor, InvalidPercentEncoding))
}
let octet = ((hex_value(a) << 4) | hex_value(c)).to_byte()
buf.write_byte(octet)
}
_ => return Err(err_at(cursor, InvalidPercentEncoding))
}
} else if b == b'"' {
return decode_utf8_buffer(buf, cursor)
} else {
buf.write_byte(b)
}
if buf.length() > limits.max_string_bytes {
return Err(
err_with(
cursor,
InputTooLarge,
cursor.position(),
"display string exceeds max_string_bytes",
),
)
}
}
}
}
Err(err_end(cursor))
}
///|
/// Decodes a byte buffer as UTF-8, mapping decode failures to an
/// [`InvalidUtf8`] error at the given position.
fn decode_utf8_buffer(
buf : @buffer.Buffer,
cursor : Cursor,
) -> Result[String, SfError] {
let bytes = buf.to_bytes()
let decoded : String = @utf8.decode(bytes) catch {
_ => return Err(err_at(cursor, InvalidUtf8))
}
Ok(decoded)
}
///|
/// Converts an array of ASCII digit bytes into an `Int64` value. The array
/// is never longer than 15 digits, so no overflow is possible.
fn digits_to_int64(ds : Array[Byte]) -> Int64 {
let mut acc : Int64 = 0L
for d in ds {
acc = acc * 10L + (d - b'0').to_int64()
}
acc
}
///|
/// `10^n` for `n ≤ 15` as an `Int64`.
fn pow10_i64(n : Int) -> Int64 {
let mut v : Int64 = 1L
let mut k = n
while k > 0 {
v = v * 10L
k = k - 1
}
v
}