use std::ops::Range;
use std::str::FromStr;
use crate::decimal::Coefficient;
use crate::lazy::bytes_ref::BytesRef;
use crate::lazy::decoder::{Decoder, LazyRawFieldExpr, LazyRawValueExpr};
use crate::lazy::span::Span;
use crate::lazy::str_ref::StrRef;
use crate::lazy::text::as_utf8::AsUtf8;
use crate::lazy::text::buffer::TextBuffer;
use crate::result::{DecodingError, IonFailure};
use crate::{
Decimal, Int, IonError, IonResult, IonType, RawSymbolRef, Timestamp, TimestampPrecision, UInt,
};
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump as BumpAllocator;
use ice_code::ice as cold_path;
use num_bigint::BigUint;
use smallvec::SmallVec;
use winnow::combinator::alt;
use winnow::combinator::preceded;
use winnow::stream::{AsChar, Stream};
use winnow::Parser;
#[derive(Clone, Copy, Debug)]
pub enum MatchedValue<'top, D: Decoder> {
Null(IonType),
Bool(bool),
Int(MatchedInt),
Float(MatchedFloat),
Decimal(MatchedDecimal),
Timestamp(MatchedTimestamp),
String(MatchedString),
Symbol(MatchedSymbol),
Blob(MatchedBlob),
Clob(MatchedClob),
List(&'top [LazyRawValueExpr<'top, D>]),
SExp(&'top [LazyRawValueExpr<'top, D>]),
Struct(&'top [LazyRawFieldExpr<'top, D>]),
}
impl<D: Decoder> PartialEq for MatchedValue<'_, D> {
fn eq(&self, other: &Self) -> bool {
use MatchedValue::*;
match (self, other) {
(Null(n1), Null(n2)) => n1 == n2,
(Bool(b1), Bool(b2)) => b1 == b2,
(Int(i1), Int(i2)) => i1 == i2,
(Float(f1), Float(f2)) => f1 == f2,
(Decimal(d1), Decimal(d2)) => d1 == d2,
(Timestamp(t1), Timestamp(t2)) => t1 == t2,
(String(s1), String(s2)) => s1 == s2,
(Symbol(s1), Symbol(s2)) => s1 == s2,
(Blob(b1), Blob(b2)) => b1 == b2,
(Clob(c1), Clob(c2)) => c1 == c2,
_ => false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MatchedFieldNameSyntax {
Symbol(MatchedSymbol),
String(MatchedString),
}
impl MatchedFieldNameSyntax {
pub fn read<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
match self {
MatchedFieldNameSyntax::Symbol(matched_symbol) => {
matched_symbol.read(allocator, matched_input)
}
MatchedFieldNameSyntax::String(matched_string) => matched_string
.read(allocator, matched_input)
.map(|s| s.into()),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MatchedFieldName<'top> {
input: TextBuffer<'top>,
syntax: MatchedFieldNameSyntax,
}
impl<'top> MatchedFieldName<'top> {
pub fn new(input: TextBuffer<'top>, syntax: MatchedFieldNameSyntax) -> Self {
Self { input, syntax }
}
pub fn syntax(&self) -> MatchedFieldNameSyntax {
self.syntax
}
pub fn read(&self) -> IonResult<RawSymbolRef<'top>> {
self.syntax.read(self.input.context.allocator(), self.input)
}
pub fn range(&self) -> Range<usize> {
self.input.range()
}
pub fn span(&self) -> Span<'top> {
Span::with_offset(self.input.offset(), self.input.bytes())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MatchedInt {
radix: u8, digits_offset: u8,
is_negative: bool,
}
impl MatchedInt {
const STACK_ALLOC_BUFFER_CAPACITY: usize = 32;
pub fn new(radix: u8, is_negative: bool, digits_offset: usize) -> Self {
debug_assert!(
digits_offset < u8::MAX as usize,
"digits offset can only be 0-3 to accommodate a sign and/or leading radix like `0x`"
);
Self {
radix,
digits_offset: digits_offset as u8,
is_negative,
}
}
pub fn is_negative(&self) -> bool {
self.is_negative
}
pub fn radix(&self) -> u32 {
self.radix as u32
}
pub fn read(&self, matched_input: TextBuffer<'_>) -> IonResult<Int> {
let digits = matched_input.slice_to_end(self.digits_offset as usize);
let mut sanitized: SmallVec<[u8; Self::STACK_ALLOC_BUFFER_CAPACITY]> =
SmallVec::with_capacity(Self::STACK_ALLOC_BUFFER_CAPACITY);
sanitized.extend(digits.bytes().iter().copied().filter(|b| *b != b'_'));
let text = sanitized.as_utf8(matched_input.offset())?;
let magnitude: Int = UInt::from_str_radix(text, self.radix())?.into();
let signed = if self.is_negative {
magnitude.neg()
} else {
magnitude
};
Ok(signed)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MatchedFloat {
PositiveInfinity,
NegativeInfinity,
NotANumber,
Numeric,
}
impl MatchedFloat {
const STACK_ALLOC_BUFFER_CAPACITY: usize = 32;
pub fn read(&self, matched_input: TextBuffer<'_>) -> IonResult<f64> {
match self {
MatchedFloat::PositiveInfinity => return Ok(f64::INFINITY),
MatchedFloat::NegativeInfinity => return Ok(f64::NEG_INFINITY),
MatchedFloat::NotANumber => return Ok(f64::NAN),
MatchedFloat::Numeric => {} };
let mut sanitized: SmallVec<[u8; Self::STACK_ALLOC_BUFFER_CAPACITY]> =
SmallVec::with_capacity(Self::STACK_ALLOC_BUFFER_CAPACITY);
sanitized.extend(matched_input.bytes().iter().copied().filter(|b| *b != b'_'));
let text = sanitized.as_utf8(matched_input.offset())?;
let float = f64::from_str(text).map_err(|e| {
matched_input
.invalid(format!("encountered an unexpected error ({e:?})"))
.context("reading a float")
})?;
Ok(float)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MatchedDecimal {
is_negative: bool,
digits_offset: u16,
digits_length: u16,
num_trailing_digits: u16,
exponent_is_negative: bool,
exponent_digits_offset: u16,
exponent_digits_length: u16,
}
impl MatchedDecimal {
const STACK_ALLOC_BUFFER_CAPACITY: usize = 32;
pub fn new(
is_negative: bool,
digits_offset: u16,
digits_length: u16,
num_trailing_digits: u16,
exponent_is_negative: bool,
exponent_offset: u16,
exponent_length: u16,
) -> Self {
Self {
is_negative,
digits_offset,
digits_length,
num_trailing_digits,
exponent_is_negative,
exponent_digits_offset: exponent_offset,
exponent_digits_length: exponent_length,
}
}
pub fn read(&self, matched_input: TextBuffer<'_>) -> IonResult<Decimal> {
let mut sanitized: SmallVec<[u8; Self::STACK_ALLOC_BUFFER_CAPACITY]> =
SmallVec::with_capacity(Self::STACK_ALLOC_BUFFER_CAPACITY);
let digits = matched_input.slice(self.digits_offset as usize, self.digits_length as usize);
sanitized.extend(
digits
.bytes()
.iter()
.copied()
.filter(|b| b.is_ascii_digit()),
);
let digits_text = sanitized.as_utf8(digits.offset())?;
let magnitude: Int = UInt::from_str_radix(digits_text, 10)?.into();
let coefficient = if self.is_negative {
if magnitude.is_zero() {
Coefficient::negative_zero()
} else {
Coefficient::new(magnitude.neg())
}
} else {
Coefficient::new(magnitude)
};
let mut exponent: i64 = match self.exponent_digits_length {
0 => 0,
_ => {
sanitized.clear();
let exponent_digits = matched_input.slice(
self.exponent_digits_offset as usize,
self.exponent_digits_length as usize,
);
sanitized.extend(
exponent_digits
.bytes()
.iter()
.copied()
.filter(|b| b.is_ascii_digit()),
);
let exponent_text = sanitized
.as_utf8(matched_input.offset() + self.exponent_digits_offset as usize)?;
let exponent_magnitude = i64::from_str(exponent_text).map_err(|e| {
IonError::decoding_error(format!(
"failed to parse decimal exponent '{exponent_text}': {e:?}"
))
})?;
if self.exponent_is_negative {
-exponent_magnitude
} else {
exponent_magnitude
}
}
};
exponent -= self.num_trailing_digits as i64;
Ok(Decimal::new(coefficient, exponent))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchedString {
ShortWithoutEscapes,
ShortWithEscapes,
Long,
LongSingleSegmentWithEscapes,
LongSingleSegmentWithoutEscapes,
}
impl MatchedString {
pub fn read<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
match self {
MatchedString::ShortWithoutEscapes => {
self.read_short_string_without_escapes(matched_input)
}
MatchedString::ShortWithEscapes => {
self.read_short_string_with_escapes(allocator, matched_input)
}
MatchedString::LongSingleSegmentWithoutEscapes => {
self.read_long_string_single_segment_without_escapes(matched_input)
}
MatchedString::LongSingleSegmentWithEscapes => {
self.read_long_string_single_segment_with_escapes(allocator, matched_input)
}
MatchedString::Long => self.read_long_string(allocator, matched_input),
}
}
fn read_long_string_single_segment_without_escapes<'data>(
&self,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
let body = matched_input.slice(3, matched_input.len() - 6);
let text = body.as_text()?;
let str_ref = StrRef::from(text);
Ok(str_ref)
}
fn read_long_string_single_segment_with_escapes<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
let body = matched_input.slice(3, matched_input.len() - 6);
let mut sanitized = BumpVec::with_capacity_in(matched_input.len(), allocator);
replace_escapes_with_byte_values(
body,
&mut sanitized,
true,
true,
)?;
let text = std::str::from_utf8(sanitized.into_bump_slice()).unwrap();
Ok(StrRef::from(text))
}
fn read_long_string<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
let mut sanitized = BumpVec::with_capacity_in(matched_input.len(), allocator);
let mut remaining = matched_input;
while let Ok((segment_body, _has_escapes)) = preceded(
TextBuffer::match_optional_comments_and_whitespace,
TextBuffer::match_long_string_segment,
)
.parse_next(&mut remaining)
{
replace_escapes_with_byte_values(
segment_body,
&mut sanitized,
true,
true,
)?;
}
let text = std::str::from_utf8(sanitized.into_bump_slice()).unwrap();
Ok(StrRef::from(text))
}
fn read_short_string_without_escapes<'data>(
&self,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
let body = matched_input.slice(1, matched_input.len() - 2);
let text = body.as_text()?;
let str_ref = StrRef::from(text);
Ok(str_ref)
}
fn read_short_string_with_escapes<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<StrRef<'data>> {
let body = matched_input.slice(1, matched_input.len() - 2);
let mut sanitized = BumpVec::with_capacity_in(matched_input.len(), allocator);
replace_escapes_with_byte_values(
body,
&mut sanitized,
false,
true,
)?;
let text = std::str::from_utf8(sanitized.into_bump_slice()).unwrap();
Ok(StrRef::from(text))
}
}
fn replace_escapes_with_byte_values(
matched_input: TextBuffer<'_>,
sanitized: &mut BumpVec<'_, u8>,
normalize_newlines: bool,
support_unicode_escapes: bool,
) -> IonResult<()> {
let mut remaining = matched_input;
let match_byte = |byte: &u8| *byte == b'\\' || *byte == b'\r';
while !remaining.is_empty() {
let next_index_to_inspect = remaining.bytes().iter().position(match_byte);
remaining = match next_index_to_inspect {
Some(carriage_return_offset)
if remaining.bytes().get(carriage_return_offset) == Some(&b'\r') =>
{
sanitized.extend_from_slice(remaining.slice(0, carriage_return_offset).bytes());
if normalize_newlines {
normalize_newline(remaining, sanitized, carriage_return_offset)
} else {
sanitized.push(b'\r');
remaining.slice_to_end(carriage_return_offset + 1)
}
}
Some(escape_offset) => {
sanitized.extend_from_slice(remaining.slice(0, escape_offset).bytes());
let contains_escapes = remaining.slice_to_end(escape_offset);
decode_escape_into_bytes(contains_escapes, sanitized, support_unicode_escapes)?
}
None => {
sanitized.extend_from_slice(remaining.bytes());
remaining.slice_to_end(remaining.len())
}
}
}
Ok(())
}
#[cold]
fn normalize_newline<'data>(
remaining: TextBuffer<'data>,
sanitized: &mut BumpVec<'_, u8>,
escape_offset: usize,
) -> TextBuffer<'data> {
sanitized.push(b'\n');
if remaining.bytes().get(escape_offset + 1).copied() == Some(b'\n') {
remaining.slice_to_end(escape_offset + 2)
} else {
remaining.slice_to_end(escape_offset + 1)
}
}
fn decode_escape_into_bytes<'data>(
input: TextBuffer<'data>,
sanitized: &mut BumpVec<'_, u8>,
support_unicode_escapes: bool,
) -> IonResult<TextBuffer<'data>> {
debug_assert!(!input.is_empty());
debug_assert!(input.bytes()[0] == b'\\');
if input.len() == 1 {
return Err(IonError::Decoding(
DecodingError::new("found an escape ('\\') with no subsequent character")
.with_position(input.offset()),
));
}
let input_after_escape = input.slice_to_end(2); let escape_id = input.bytes()[1];
let substitute = match escape_id {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'/' => b'/',
b'"' => b'"',
b'\'' => b'\'',
b'?' => b'?',
b'0' => 0x00u8, b'a' => 0x07u8, b'b' => 0x08u8, b'v' => 0x0Bu8, b'f' => 0x0Cu8, b'\r' if input_after_escape.bytes().first() == Some(&b'\n') => {
return Ok(input_after_escape.slice_to_end(1))
}
b'\r' | b'\n' => return Ok(input_after_escape),
b'x' => {
return decode_hex_digits_escape(
2,
input_after_escape,
sanitized,
support_unicode_escapes,
)
}
b'u' => {
return decode_hex_digits_escape(
4,
input_after_escape,
sanitized,
support_unicode_escapes,
)
}
b'U' => {
return decode_hex_digits_escape(
8,
input_after_escape,
sanitized,
support_unicode_escapes,
)
}
_ => {
return Err(IonError::Decoding(
DecodingError::new(format!("invalid escape sequence '\\{escape_id}"))
.with_position(input.offset()),
))
}
};
sanitized.push(substitute);
Ok(input_after_escape)
}
fn decode_hex_digits_escape<'data>(
num_digits: usize,
input: TextBuffer<'data>,
sanitized: &mut BumpVec<'_, u8>,
support_unicode_escapes: bool,
) -> IonResult<TextBuffer<'data>> {
if input.len() < num_digits {
return Err(IonError::Decoding(
DecodingError::new(format!(
"found a {}-hex-digit escape sequence with only {} digits",
num_digits,
input.len()
))
.with_position(input.offset()),
));
}
if num_digits != 2 && !support_unicode_escapes {
return Err(IonError::Decoding(
DecodingError::new("Unicode escape sequences (\\u, \\U) are not legal in this context")
.with_position(input.offset()),
));
}
let hex_digit_bytes = &input.bytes()[..num_digits];
let all_are_hex_digits = hex_digit_bytes
.iter()
.take(num_digits)
.copied()
.all(AsChar::is_hex_digit);
if !all_are_hex_digits {
return Err(IonError::Decoding(
DecodingError::new(format!(
"found a {num_digits}-hex-digit escape sequence that contained an invalid hex digit",
))
.with_position(input.offset()),
));
}
let remaining_input = input.slice_to_end(num_digits);
let hex_digits = std::str::from_utf8(hex_digit_bytes).unwrap();
if !support_unicode_escapes {
let byte_literal = u8::from_str_radix(hex_digits, 16).unwrap();
sanitized.push(byte_literal);
return Ok(remaining_input);
}
let code_point = u32::from_str_radix(hex_digits, 16).unwrap();
if code_point_is_a_high_surrogate(code_point) {
return complete_surrogate_pair(sanitized, code_point, remaining_input);
}
let character = char::from_u32(code_point).unwrap();
let utf8_buffer: &mut [u8; 4] = &mut [0; 4];
let utf8_encoded = character.encode_utf8(utf8_buffer);
sanitized.extend_from_slice(utf8_encoded.as_bytes());
Ok(remaining_input)
}
fn complete_surrogate_pair<'data>(
sanitized: &mut BumpVec<'_, u8>,
high_surrogate: u32,
input: TextBuffer<'data>,
) -> IonResult<TextBuffer<'data>> {
let mut match_next_codepoint = preceded(
"\\",
alt((
preceded("x", TextBuffer::match_n_hex_digits(2)),
preceded("u", TextBuffer::match_n_hex_digits(4)),
preceded("U", TextBuffer::match_n_hex_digits(8)),
)),
);
let (remaining, hex_digits) = match match_next_codepoint.parse_peek(input) {
Ok(hex_digits) => hex_digits,
Err(_) => {
return {
let error =
DecodingError::new("found a high surrogate not followed by a low surrogate")
.with_position(input.offset());
Err(IonError::Decoding(error))
}
}
};
let high_surrogate = high_surrogate as u16;
let hex_digits = std::str::from_utf8(hex_digits.bytes()).unwrap();
let low_surrogate = u16::from_str_radix(hex_digits, 16).map_err(|_| {
let error =
DecodingError::new("low surrogate did not fit in a u16").with_position(input.offset());
IonError::Decoding(error)
})?;
let character = char::decode_utf16([high_surrogate, low_surrogate])
.next()
.unwrap()
.map_err(|_| {
let error = DecodingError::new("encountered invalid surrogate pair")
.with_position(input.offset());
IonError::Decoding(error)
})?;
let utf8_buffer: &mut [u8; 4] = &mut [0; 4];
let utf8_encoded = character.encode_utf8(utf8_buffer);
sanitized.extend_from_slice(utf8_encoded.as_bytes());
Ok(remaining)
}
fn code_point_is_a_high_surrogate(value: u32) -> bool {
(0xD800..=0xDFFF).contains(&value)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchedSymbol {
SymbolId,
Identifier,
QuotedWithoutEscapes,
QuotedWithEscapes,
Operator,
}
impl MatchedSymbol {
pub fn read<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
use MatchedSymbol::*;
match self {
SymbolId => self.read_symbol_id(matched_input),
Identifier | Operator => self.read_unquoted(matched_input),
QuotedWithEscapes => self.read_quoted_with_escapes(allocator, matched_input),
QuotedWithoutEscapes => self.read_quoted_without_escapes(matched_input),
}
}
pub(crate) fn read_quoted_without_escapes<'data>(
&self,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
let body = matched_input.slice(1, matched_input.len() - 2);
let text = body
.as_text()
.expect("successfully lexed symbol later found to be invalid UTF-8");
let str_ref = RawSymbolRef::Text(text);
Ok(str_ref)
}
pub(crate) fn read_quoted_with_escapes<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
let body = matched_input.slice(1, matched_input.len() - 2);
let mut sanitized = BumpVec::with_capacity_in(matched_input.len(), allocator);
replace_escapes_with_byte_values(body, &mut sanitized, false, true)?;
let text = std::str::from_utf8(sanitized.into_bump_slice()).unwrap();
Ok(RawSymbolRef::Text(text))
}
pub(crate) fn read_unquoted<'data>(
&self,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
matched_input.as_text().map(RawSymbolRef::Text)
}
fn read_symbol_id<'data>(
&self,
matched_input: TextBuffer<'data>,
) -> IonResult<RawSymbolRef<'data>> {
let text = matched_input.slice_to_end(1).as_text()?;
let sid = usize::from_str(text).expect("loading symbol ID as usize");
Ok(RawSymbolRef::SymbolId(sid))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MatchedTimestamp {
precision: TimestampPrecision,
offset: MatchedTimestampOffset,
}
impl MatchedTimestamp {
pub fn new(precision: TimestampPrecision) -> Self {
Self {
precision,
offset: MatchedTimestampOffset::Unknown,
}
}
}
impl MatchedTimestamp {
pub fn with_offset(mut self, offset: MatchedTimestampOffset) -> Self {
self.offset = offset;
self
}
pub(crate) fn read(&self, matched_input: TextBuffer<'_>) -> IonResult<Timestamp> {
let year_text = matched_input.slice(0, 4).as_text().unwrap();
let year = u32::from_str(year_text).unwrap();
let timestamp = Timestamp::with_year(year);
if self.precision == TimestampPrecision::Year {
return timestamp.build();
}
let month_text = matched_input.slice(5, 2).as_text().unwrap();
let month = u32::from_str(month_text).unwrap();
let timestamp = timestamp.with_month(month);
if self.precision == TimestampPrecision::Month {
return timestamp.build();
}
let day_text = matched_input.slice(8, 2).as_text().unwrap();
let day = u32::from_str(day_text).unwrap();
let timestamp = timestamp.with_day(day);
if self.precision == TimestampPrecision::Day {
return timestamp.build();
}
let offset_minutes = match self.offset {
MatchedTimestampOffset::Zulu => Some(0),
MatchedTimestampOffset::Unknown => None,
MatchedTimestampOffset::PositiveHoursAndMinutes
| MatchedTimestampOffset::NegativeHoursAndMinutes => {
let hours_start = &matched_input.bytes()[10..]
.iter()
.position(|b| *b == b'-' || *b == b'+')
.expect("the parser reported that this timestamp had an HH:MM component")
+ 11; let hours_text = matched_input.slice(hours_start, 2).as_text().unwrap();
let hours = i32::from_str(hours_text).unwrap();
let minutes_start = hours_start + 3;
let minutes_text = matched_input.slice(minutes_start, 2).as_text().unwrap();
let minutes = i32::from_str(minutes_text).unwrap();
let offset_magnitude_minutes = (hours * 60) + minutes;
if self.offset == MatchedTimestampOffset::NegativeHoursAndMinutes {
Some(-offset_magnitude_minutes)
} else {
Some(offset_magnitude_minutes)
}
}
};
let hour_text = matched_input.slice(11, 2).as_text().unwrap();
let hour = u32::from_str(hour_text).unwrap();
let minute_text = matched_input.slice(14, 2).as_text().unwrap();
let minute = u32::from_str(minute_text).unwrap();
let timestamp = timestamp.with_hour_and_minute(hour, minute);
if self.precision == TimestampPrecision::HourAndMinute {
if let Some(offset) = offset_minutes {
return timestamp.with_offset(offset).build();
} else {
return timestamp.build();
}
}
let second_text = matched_input.slice(17, 2).as_text().unwrap();
let seconds = u32::from_str(second_text).unwrap();
let timestamp = timestamp.with_second(seconds);
if matched_input.bytes()[19] != b'.' {
if let Some(offset) = offset_minutes {
return timestamp.with_offset(offset).build();
} else {
return timestamp.build();
}
}
let fractional_start = 20;
let mut fractional_end = fractional_start;
for byte in matched_input.slice_to_end(fractional_start).bytes() {
if !byte.is_dec_digit() {
break;
}
fractional_end += 1;
}
let fractional_text = matched_input
.slice(fractional_start, fractional_end - fractional_start)
.as_text()
.unwrap();
let timestamp = match fractional_text.len() {
len if len <= 9 => {
let fraction = u32::from_str(fractional_text).unwrap();
let multiplier = 10u32.pow(9 - len as u32);
let nanoseconds = fraction * multiplier;
timestamp.with_nanoseconds_and_precision(nanoseconds, len as u32)
}
_ => {
cold_path! {{
let big = BigUint::parse_bytes(fractional_text.as_bytes(), 10).unwrap();
let mut le = big.to_bytes_le();
le.push(0);
let coefficient = Int::from_le_signed_bytes(&le);
let decimal = Decimal::new(coefficient, -(fractional_text.len() as i64));
timestamp.with_fractional_seconds(decimal)
}}
}
};
if let Some(offset) = offset_minutes {
timestamp.with_offset(offset).build()
} else {
timestamp.build()
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchedTimestampOffset {
Zulu,
PositiveHoursAndMinutes,
NegativeHoursAndMinutes,
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MatchedBlob {
content_offset: usize,
content_length: usize,
}
impl MatchedBlob {
pub fn new(content_offset: usize, content_length: usize) -> Self {
Self {
content_offset,
content_length,
}
}
pub(crate) fn read<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<BytesRef<'data>> {
let base64_text = matched_input.slice(self.content_offset, self.content_length);
let matched_bytes = base64_text.bytes();
let contains_whitespace = matched_bytes.iter().any(|b| b.is_ascii_whitespace());
let max_decoded_size = matched_bytes.len().div_ceil(4) * 3;
let mut decoding_buffer = BumpVec::with_capacity_in(max_decoded_size, allocator);
decoding_buffer.resize(max_decoded_size, 0u8);
let decode_result = if contains_whitespace {
let mut sanitized_base64_text =
BumpVec::with_capacity_in(matched_bytes.len(), allocator);
let non_whitespaces_bytes = matched_bytes
.iter()
.copied()
.filter(|b| !b.is_ascii_whitespace());
sanitized_base64_text.extend(non_whitespaces_bytes);
base64::decode_config_slice(
sanitized_base64_text.as_slice(),
base64::STANDARD,
decoding_buffer.as_mut_slice(),
)
} else {
base64::decode_config_slice(
matched_bytes,
base64::STANDARD,
decoding_buffer.as_mut_slice(),
)
};
let decoded_size = match decode_result {
Ok(size) => size,
Err(e) => {
return IonResult::decoding_error(format!(
"failed to parse blob with invalid base64 data:\n'{:?}'\n{e:?}:",
matched_input.bytes()
))
}
};
let decoded_bytes = decoding_buffer
.into_bump_slice()
.get(..decoded_size)
.expect("decoding buffer was shorter than indicated");
Ok(BytesRef::from(decoded_bytes))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchedClob {
Short,
Long,
}
impl MatchedClob {
pub(crate) fn read<'data>(
&self,
allocator: &'data BumpAllocator,
matched_input: TextBuffer<'data>,
) -> IonResult<BytesRef<'data>> {
let matched_inside_braces = matched_input.slice(2, matched_input.len() - 4);
match self {
MatchedClob::Short => self.read_short_clob(allocator, matched_inside_braces),
MatchedClob::Long => self.read_long_clob(allocator, matched_inside_braces),
}
}
fn read_short_clob<'data>(
&self,
allocator: &'data BumpAllocator,
matched_inside_braces: TextBuffer<'data>,
) -> IonResult<BytesRef<'data>> {
let open_quote_position = matched_inside_braces
.bytes()
.iter()
.position(|b| *b == b'"')
.unwrap();
let remaining = matched_inside_braces.slice_to_end(open_quote_position + 1);
let (body, _has_escapes) = remaining.checkpoint().match_short_string_body().unwrap();
let mut sanitized = BumpVec::with_capacity_in(body.len(), allocator);
replace_escapes_with_byte_values(
body,
&mut sanitized,
false,
false,
)?;
Ok(BytesRef::from(sanitized.into_bump_slice()))
}
fn read_long_clob<'data>(
&self,
allocator: &'data BumpAllocator,
matched_inside_braces: TextBuffer<'data>,
) -> IonResult<BytesRef<'data>> {
let mut sanitized = BumpVec::with_capacity_in(matched_inside_braces.len(), allocator);
let mut remaining = matched_inside_braces;
while let Ok((remaining_after_match, (segment_body, _has_escapes))) = preceded(
TextBuffer::match_whitespace0,
TextBuffer::match_long_string_segment,
)
.parse_peek(remaining)
{
remaining = remaining_after_match;
replace_escapes_with_byte_values(
segment_body,
&mut sanitized,
true,
false,
)?;
}
Ok(BytesRef::from(sanitized.into_bump_slice()))
}
}
#[cfg(test)]
mod tests {
use crate::lazy::bytes_ref::BytesRef;
use crate::lazy::expanded::{EncodingContext, EncodingContextRef};
use crate::lazy::text::buffer::TextBuffer;
use crate::{Decimal, Int, IonResult, Timestamp};
use winnow::combinator::peek;
use winnow::Parser;
#[test]
fn read_ints() -> IonResult<()> {
fn expect_int(data: &str, expected: impl Into<Int>) {
let expected: Int = expected.into();
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_int).parse_next(&mut buffer).unwrap();
let actual = matched.read(buffer).unwrap();
assert_eq!(
actual, expected,
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
}
let tests = [
("-5", Int::from(-5)),
("0", Int::from(0)),
(
"1234567890_1234567890_1234567890",
Int::from(1234567890_1234567890_1234567890i128),
),
(
"-1234567890_1234567890_1234567890",
Int::from(-1234567890_1234567890_1234567890i128),
),
("2147483647", Int::from(i32::MAX)),
("-2147483648", Int::from(i32::MIN)),
("9223372036854775807", Int::from(i64::MAX)),
("-9223372036854775808", Int::from(i64::MIN)),
(
"170141183460469231731687303715884105727",
Int::from(i128::MAX),
),
(
"-170141183460469231731687303715884105728",
Int::from(i128::MIN),
),
];
for (input, expected) in tests {
expect_int(input, expected);
}
Ok(())
}
#[test]
fn read_ints_arbitrary_precision() {
fn expect_big_int(data: &str, expected_decimal_str: &str) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_int).parse_next(&mut buffer).unwrap();
let int = matched.read(buffer).unwrap_or_else(|e| {
panic!("Expected parse of '{data}' to succeed, got error: {e:?}")
});
assert_eq!(
int.to_string(),
expected_decimal_str,
"round-trip mismatch for '{data}': got {int}"
);
}
let tests = [
(
"170141183460469231731687303715884105728",
"170141183460469231731687303715884105728",
),
(
"-170141183460469231731687303715884105729",
"-170141183460469231731687303715884105729",
),
(
"14259999999999999342747969421907328069210052490234375",
"14259999999999999342747969421907328069210052490234375",
),
(
"-14259999999999999342747969421907328069210052490234375",
"-14259999999999999342747969421907328069210052490234375",
),
(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"87112285931760246646623899502532662132735",
),
(
"-0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"-87112285931760246646623899502532662132735",
),
];
for (input, expected_str) in tests {
expect_big_int(input, expected_str);
}
}
#[test]
fn read_decimals_arbitrary_precision() {
fn expect_big_decimal(data: &str, expected_display: &str) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_decimal)
.parse_next(&mut buffer)
.unwrap_or_else(|e| panic!("failed to match '{data}': {e:?}"));
let decimal = matched
.read(buffer)
.unwrap_or_else(|e| panic!("failed to read '{data}': {e:?}"));
assert_eq!(
decimal.to_string(),
expected_display,
"round-trip mismatch for '{data}'"
);
}
let tests = [
(
"14259999999999999342747969421907328069210052490234375d0",
"1.4259999999999999342747969421907328069210052490234375d52",
),
(
"14259999999999999342747969421907328069210052490234375d-3",
"14259999999999999342747969421907328069210052490234.375",
),
(
"-14259999999999999342747969421907328069210052490234375d0",
"-1.4259999999999999342747969421907328069210052490234375d52",
),
(
"14259999999999999342747969421907328069210052490234375.",
"1.4259999999999999342747969421907328069210052490234375d52",
),
(
"14259999999999999342747969.421907328069210052490234375d27",
"1.4259999999999999342747969421907328069210052490234375d52",
),
];
for (input, expected) in tests {
expect_big_decimal(input, expected);
}
}
#[test]
fn read_timestamps() -> IonResult<()> {
fn expect_timestamp(data: &str, expected: Timestamp) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_timestamp)
.parse_next(&mut buffer)
.unwrap();
let actual = matched.read(buffer).unwrap();
assert_eq!(
actual, expected,
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
}
let tests = [
("2023T", Timestamp::with_year(2023).build()?),
(
"2023-08T",
Timestamp::with_year(2023).with_month(8).build()?,
),
("2023-08-13", Timestamp::with_ymd(2023, 8, 13).build()?),
("2023-08-13T", Timestamp::with_ymd(2023, 8, 13).build()?),
(
"2023-08-13T10:30-00:00",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.build()?,
),
(
"2023-08-13T10:30Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_offset(0)
.build()?,
),
(
"2023-08-13T10:30-05:00",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_offset(-300)
.build()?,
),
(
"2023-08-13T10:30+05:00",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_offset(300)
.build()?,
),
(
"2023-08-13T10:30:45Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_offset(0)
.build()?,
),
(
"2023-08-13T10:30:45.226Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_milliseconds(226)
.with_offset(0)
.build()?,
),
(
"2023-08-13T10:30:45.226226Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_microseconds(226226)
.with_offset(0)
.build()?,
),
(
"2023-08-13T10:30:45.226226226Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_nanoseconds(226226226)
.with_offset(0)
.build()?,
),
(
"2023-08-13T10:30:45.226226226337337Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_fractional_seconds(Decimal::new(226_226_226_337_337_i64, -15))
.with_offset(0)
.build()?,
),
];
for (input, expected) in tests {
expect_timestamp(input, expected);
}
Ok(())
}
#[test]
fn read_timestamps_arbitrary_precision() -> IonResult<()> {
fn expect_timestamp(data: &str, expected: Timestamp) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_timestamp)
.parse_next(&mut buffer)
.unwrap();
let actual = matched.read(buffer).unwrap();
assert_eq!(
actual, expected,
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
}
expect_timestamp(
"2023-08-13T10:30:45.727885129180488904360266563744972327436484050815Z",
Timestamp::with_ymd(2023, 8, 13)
.with_hour_and_minute(10, 30)
.with_second(45)
.with_fractional_seconds(Decimal::new(Int::from_le_signed_bytes(&[0x7F; 20]), -48))
.with_offset(0)
.build()?,
);
Ok(())
}
#[test]
fn read_decimals() -> IonResult<()> {
fn expect_decimal(data: &str, expected: Decimal) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let result = peek(TextBuffer::match_decimal).parse_next(&mut buffer);
assert!(
result.is_ok(),
"Unexpected match error for input: '{data}': {result:?}",
);
let result = result.unwrap().read(buffer);
assert!(
result.is_ok(),
"Unexpected read error for input '{data}': {result:?}",
);
let actual = result.unwrap();
assert_eq!(
actual, expected,
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
assert_eq!(
actual.coefficient(),
expected.coefficient(),
"Actual coefficient didn't match expected coefficient for input '{data}' .\n{actual:?}\n!=\n{expected:?}",
);
assert_eq!(
actual.exponent(),
expected.exponent(),
"Actual exponent didn't match expected exponent for input '{data}' .\n{actual:?}\n!=\n{expected:?}",
);
}
let tests = [
("0.", Decimal::new(0, 0)),
("-0.", Decimal::negative_zero()),
("0.0", Decimal::new(0, -1)),
("0.00", Decimal::new(0, -2)),
("0d-1", Decimal::new(0, -1)),
("0d0", Decimal::new(0, 0)),
("0d1", Decimal::new(0, 1)),
("0d2", Decimal::new(0, 2)),
("0.0d1", Decimal::new(0, 0)),
("0.0d0", Decimal::new(0, -1)),
("0.0d-1", Decimal::new(0, -2)),
("5.", Decimal::new(5, 0)),
("-5.", Decimal::new(-5, 0)),
("5.d0", Decimal::new(5, 0)),
("-5.d0", Decimal::new(-5, 0)),
("5.0", Decimal::new(50, -1)),
("5.0d1", Decimal::new(50, 0)),
("5.0d0", Decimal::new(50, -1)),
("5.0d-1", Decimal::new(50, -2)),
("-5.0", Decimal::new(-50, -1)),
("500d0", Decimal::new(500, 0)),
("-500d0", Decimal::new(-500, 0)),
("0.005", Decimal::new(5, -3)),
("0.0050", Decimal::new(50, -4)),
("-0.005", Decimal::new(-5, -3)),
("0.005D2", Decimal::new(5, -1)),
("-0.005D2", Decimal::new(-5, -1)),
("0.005d+2", Decimal::new(5, -1)),
("-0.005d+2", Decimal::new(-5, -1)),
("0.005D-2", Decimal::new(5, -5)),
("-0.005D-2", Decimal::new(-5, -5)),
];
for (input, expected) in tests {
expect_decimal(input, expected);
}
Ok(())
}
#[test]
fn read_blobs() -> IonResult<()> {
fn expect_blob(data: &str, expected: &str) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_blob)
.parse_next(&mut buffer)
.unwrap();
let actual = matched.read(context.allocator(), buffer).unwrap();
assert_eq!(
actual,
expected.as_ref(),
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
}
let tests = [
("{{TWVyY3VyeQ==}}", "Mercury"),
("{{VmVudXM=}}", "Venus"),
("{{RWFydGg=}}", "Earth"),
("{{TWFycw==}}", "Mars"),
("{{ TWFycw== }}", "Mars"),
("{{\nTWFycw==\t\t }}", "Mars"),
];
for (input, expected) in tests {
expect_blob(input, expected);
}
Ok(())
}
#[test]
fn read_strings() -> IonResult<()> {
fn expect_string(data: &str, expected: &str) {
let encoding_context = EncodingContext::empty();
let context = encoding_context.get_ref();
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_string)
.parse_next(&mut buffer)
.unwrap();
let actual = matched.read(context.allocator(), buffer).unwrap();
assert_eq!(
actual, expected,
"Actual didn't match expected for input '{data}'.\n{actual:?}\n!=\n{expected:?}",
);
}
let tests = [
(r#""hello""#, "hello"),
(r"'''hello'''", "hello"),
(r"'''he''' '''llo'''", "hello"),
(r"'''he''' '''llo'''", "hello"),
(r#""😎🙂🙃""#, "😎🙂🙃"),
(r"'''😎🙂''' '''🙃'''", "😎🙂🙃"),
(r"'''\u2764\uFE0F'''", "❤️"),
(r"'''\U00002764\U0000FE0F'''", "❤️"),
("\"foo\rbar\rbaz\"", "foo\rbar\rbaz"),
("'''foo\rbar\r\nbaz'''", "foo\nbar\nbaz"),
];
for (input, expected) in tests {
expect_string(input, expected);
}
Ok(())
}
#[test]
fn read_clobs() -> IonResult<()> {
fn read_clob<'a>(
context: EncodingContextRef<'a>,
data: &'a str,
) -> IonResult<BytesRef<'a>> {
let mut buffer = TextBuffer::new(context, data.as_bytes());
let matched = peek(TextBuffer::match_clob)
.parse_next(&mut buffer)
.unwrap();
matched.read(context.allocator(), buffer)
}
fn expect_clob_error(context: EncodingContextRef<'_>, data: &str) {
let actual = read_clob(context, data);
assert!(
actual.is_err(),
"Successfully read a clob from illegal input."
);
}
fn expect_clob(context: EncodingContextRef<'_>, data: &str, expected: &str) {
let result = read_clob(context, data);
assert!(
result.is_ok(),
"Unexpected read failure for input '{data}': {result:?}",
);
let actual = result.unwrap();
assert_eq!(
actual,
expected.as_ref(),
"Actual didn't match expected for input '{}'.\n{:?} ({})\n!=\n{:?} ({:0x?})",
data,
actual,
std::str::from_utf8(actual.data()).unwrap(),
expected,
expected.as_bytes()
);
}
let tests = [
(r#"{{""}}"#, ""),
(r#"{{''''''}}"#, ""),
(r#"{{'''''' '''''' ''''''}}"#, ""),
(r#"{{"hello"}}"#, "hello"),
(r#"{{"\x4D"}}"#, "M"),
(r#"{{"\x4d \x4d \x4d"}}"#, "M M M"),
(r"{{'''\x4d''' '''\x4d''' '''\x4d'''}}", "MMM"),
(r#"{{"\xe2\x9d\xa4\xef\xb8\x8f"}}"#, "❤️"),
(r#"{{'''hel''' '''lo'''}}"#, "hello"),
(
r"{{
'''\xe2'''
'''\x9d\xa4'''
'''\xef\xb8\x8f'''
}}
",
"❤️",
),
("{{'''foo\rbar\r\nbaz'''}}", "foo\nbar\nbaz"),
("{{\"foo\rbar\rbaz\"}}", "foo\rbar\rbaz"),
];
let empty_context = EncodingContext::empty();
let context = empty_context.get_ref();
for (input, expected) in tests {
expect_clob(context, input, expected);
}
let illegal_inputs = [
r#"{{"\u004D" }}"#,
r#"{{"\U0000004D" }}"#,
r#"{{"\x4"}}"#,
r"{{
'''\xe'''
'''2\x9d\xa'''
'''4\xef\xb8\x8f'''
}}
",
];
for input in illegal_inputs {
expect_clob_error(context, input);
}
Ok(())
}
}