diff --git a/ion-tests b/ion-tests index 7e3bca5e..8d18f2f6 160000 --- a/ion-tests +++ b/ion-tests @@ -1 +1 @@ -Subproject commit 7e3bca5eaef90a9a81c04a302e389ad5593b7471 +Subproject commit 8d18f2f66aa7bd43149fe8f11965c1135d39c476 diff --git a/src/element/mod.rs b/src/element/mod.rs index 063025ab..dbc34f3a 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -522,6 +522,21 @@ impl Element { } } + pub fn as_usize(&self) -> Option { + match &self.value { + Value::Int(i) => i.as_usize(), + _ => None, + } + } + + pub fn expect_usize(&self) -> IonResult { + self.expected(self.as_usize()) + } + + pub fn try_into_usize(self) -> ConversionOperationResult { + self.as_usize().ok_or_else(|| self.into()) + } + pub fn as_float(&self) -> Option { match &self.value { Value::Float(f) => Some(*f), diff --git a/src/element/sequence.rs b/src/element/sequence.rs index 7a09cbd2..90d20ddb 100644 --- a/src/element/sequence.rs +++ b/src/element/sequence.rs @@ -138,6 +138,12 @@ impl AsRef<[Element]> for Sequence { } } +impl From for Vec { + fn from(value: Sequence) -> Self { + value.elements + } +} + // This is more efficient than Sequence::new(), which will iterate over and convert each value to // an Element for better ergonomics. impl From> for Sequence { diff --git a/src/lazy/encoder/annotation_seq.rs b/src/lazy/encoder/annotation_seq.rs index 8276e060..ffe766ba 100644 --- a/src/lazy/encoder/annotation_seq.rs +++ b/src/lazy/encoder/annotation_seq.rs @@ -1,7 +1,7 @@ use smallvec::SmallVec; use crate::raw_symbol_ref::SystemSymbol_1_1; -use crate::{RawSymbolRef, SymbolId}; +use crate::{Annotations, RawSymbolRef, SymbolId}; /// A sequence of annotations. /// @@ -129,3 +129,13 @@ where annotations } } + +impl<'a> AnnotationSeq<'a> for &'a Annotations { + fn into_annotations_vec(self) -> AnnotationsVec<'a> { + let mut annotations = AnnotationsVec::new(); + for token in self { + annotations.push(token.into()); + } + annotations + } +} diff --git a/src/lazy/encoder/binary/v1_0/container_writers.rs b/src/lazy/encoder/binary/v1_0/container_writers.rs index 784860f7..33437f35 100644 --- a/src/lazy/encoder/binary/v1_0/container_writers.rs +++ b/src/lazy/encoder/binary/v1_0/container_writers.rs @@ -66,7 +66,13 @@ impl<'value, 'top> BinaryContainerWriter_1_0<'value, 'top> { }; symbol_ids.push(symbol_address); } - self.annotations = Some(symbol_ids); + // If this was called with an empty iterator, act as though it was never called at all. + // This prevents writing out an empty annotations sequence in binary, which is illegal. + self.annotations = if !symbol_ids.is_empty() { + Some(symbol_ids) + } else { + None + }; Ok(self) } diff --git a/src/lib.rs b/src/lib.rs index 0ce2be12..b88629ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,7 +294,7 @@ macro_rules! v1_x_tooling_apis { LazyExpandedField, LazyExpandedFieldName }, - lazy::expanded::e_expression::{EExpression, EExpressionArgsIterator}, + lazy::expanded::e_expression::{EExpression, EExpressionArgsIterator, EExpArgGroup, EExpArgGroupIterator}, lazy::expanded::sequence::{Environment, ExpandedListSource, ExpandedSExpSource, LazyExpandedList, LazyExpandedSExp}, lazy::expanded::{ExpandedStreamItem, LazyExpandedValue, ExpandingReader, ExpandedValueSource, ExpandedAnnotationsSource, ExpandedValueRef}, lazy::system_stream_item::SystemStreamItem, diff --git a/src/result/conversion.rs b/src/result/conversion.rs index 737a73f6..8f507981 100644 --- a/src/result/conversion.rs +++ b/src/result/conversion.rs @@ -100,6 +100,7 @@ macro_rules! impl_type_and_ref_expectation { impl_type_and_ref_expectation!(Int, IonType::Int); impl_type_expectation!(i64, "i64 value"); +impl_type_expectation!(usize, "usize value"); impl_type_expectation!(f64, IonType::Float); impl_type_and_ref_expectation!(Decimal, IonType::Decimal); impl_type_and_ref_expectation!(Timestamp, IonType::Timestamp); diff --git a/tests/conformance_dsl/clause.rs b/tests/conformance_dsl/clause.rs index 0d878582..9f0c51e3 100644 --- a/tests/conformance_dsl/clause.rs +++ b/tests/conformance_dsl/clause.rs @@ -26,7 +26,7 @@ pub(crate) enum ClauseType { Text, /// Provide a sequence of bytes that is interpreted as binary ion, that will be inserted into /// the document. - Bytes, + Binary, /// Provide a major and minor version that will be emitted into the document as an IVM. Ivm, /// Specify a ion data to be inserted into the document, using inline ion syntax. @@ -72,7 +72,7 @@ impl FromStr for ClauseType { "produces" => Ok(Produces), "denotes" => Ok(Denotes), "text" => Ok(Text), - "bytes" => Ok(Bytes), + "binary" => Ok(Binary), "and" => Ok(And), "not" => Ok(Not), "then" => Ok(Then), @@ -91,7 +91,7 @@ impl ClauseType { /// Utility function to test if the Clause is a fragment node. pub fn is_fragment(&self) -> bool { use ClauseType::*; - matches!(self, Text | Bytes | Ivm | TopLevel | Encoding | MacTab) + matches!(self, Text | Binary | Ivm | TopLevel | Encoding | MacTab) } /// Utility function to test if the Clause is an expectation node. diff --git a/tests/conformance_dsl/continuation.rs b/tests/conformance_dsl/continuation.rs index ad9bfd76..1572de90 100644 --- a/tests/conformance_dsl/continuation.rs +++ b/tests/conformance_dsl/continuation.rs @@ -2,11 +2,11 @@ //! of the test document when read) and Extensions (clauses that allow the chaining, or //! permutations for document creation). -use super::*; use super::context::Context; use super::model::{compare_values, ModelValue}; +use super::*; -use ion_rs::{Element, Sequence}; +use ion_rs::{Element, ElementReader, Sequence}; #[derive(Clone, Debug)] pub(crate) enum Continuation { @@ -60,39 +60,36 @@ impl Continuation { } Continuation::Each(branches, continuation) => { for branch in branches { - let frags = vec!(branch.fragment.clone()); + let frags = vec![branch.fragment.clone()]; let mut new_context = Context::extend(ctx, &frags); new_context.set_encoding(branch.fragment.required_encoding()); continuation.evaluate(&new_context)?; } Ok(()) } - Continuation::Signals(msg) => { - match ctx.read_all(ctx.encoding()) { - Err(_e) => Ok(()), - Ok(_) => Err(ConformanceErrorKind::ExpectedSignal(msg.to_owned()))?, - } - } + Continuation::Signals(msg) => match ctx.read_all(ctx.encoding()) { + Err(_e) => Ok(()), + Ok(_) => Err(ConformanceErrorKind::ExpectedSignal(msg.to_owned()))?, + }, } } - } impl Default for Continuation { fn default() -> Self { - Continuation::Produces(Produces { elems: vec!() }) + Continuation::Produces(Produces { elems: vec![] }) } } /// Parses a clause known to be a continuation into a proper Continuation instance. pub fn parse_continuation(clause: Clause) -> InnerResult { let continuation = match clause.tpe { - ClauseType::Produces => { - Continuation::Produces(Produces { elems: clause.body.clone() }) - } + ClauseType::Produces => Continuation::Produces(Produces { + elems: clause.body.clone(), + }), ClauseType::And => { if !clause.body.is_empty() { - let mut args = vec!(); + let mut args = vec![]; for elem in clause.body { if let Some(seq) = elem.as_sequence() { let clause = Clause::try_from(seq)?; @@ -108,7 +105,7 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { } Continuation::And(args) } else { - return Err(ConformanceErrorKind::ExpectedExpectation) + return Err(ConformanceErrorKind::ExpectedExpectation); } } ClauseType::Not => { @@ -129,21 +126,17 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { Continuation::Then(Box::new(then)) } ClauseType::Denotes => { - let mut values: Vec = vec!(); + let mut values: Vec = vec![]; for elem in clause.body { - if let Some(seq) = elem.as_sequence() { - let model_value = ModelValue::try_from(seq)?; - values.push(model_value); - } else { - return Err(ConformanceErrorKind::ExpectedModelValue); - } + let model_value = ModelValue::try_from(&elem)?; + values.push(model_value); } Continuation::Denotes(Denotes { model: values }) } ClauseType::Each => { let mut parsing_branches = true; let mut sequence_idx = 0; - let mut branches: Vec = vec!(); + let mut branches: Vec = vec![]; loop { if sequence_idx >= clause.body.len() { return Err(ConformanceErrorKind::ExpectedClause); @@ -152,12 +145,18 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { let mut name: Option = None; // Branch: name-string? fragment // Check for name-string.. - if let Some(elem) = clause.body.get(sequence_idx).filter(|e| e.ion_type() == IonType::String) { + if let Some(elem) = clause + .body + .get(sequence_idx) + .filter(|e| e.ion_type() == IonType::String) + { name = elem.as_string().map(|s| s.to_string()); sequence_idx += 1; } - let seq = clause.body.get(sequence_idx) + let seq = clause + .body + .get(sequence_idx) .and_then(|e| e.as_sequence()) .ok_or(ConformanceErrorKind::ExpectedModelValue)?; let seq_iter = seq.iter().peekable(); @@ -170,12 +169,11 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { } Err(x) => return Err(x), }; - branches.push(EachBranch { - name, - fragment, - }); + branches.push(EachBranch { name, fragment }); } else { - let seq = clause.body.get(sequence_idx) + let seq = clause + .body + .get(sequence_idx) .and_then(|e| e.as_sequence()) .ok_or(ConformanceErrorKind::ExpectedModelValue)?; let clause = Clause::try_from(seq.clone())?; @@ -188,7 +186,9 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { } } ClauseType::Signals => { - let msg = clause.body.first() + let msg = clause + .body + .first() .and_then(|e| e.as_string()) .ok_or(ConformanceErrorKind::ExpectedString)? .to_string(); @@ -197,7 +197,6 @@ pub fn parse_continuation(clause: Clause) -> InnerResult { _ => unreachable!(), }; - Ok(continuation) } @@ -219,7 +218,7 @@ impl Produces { /// Creates a reader using the provided context, and compares the read values from the input /// document with the elements specified in the associated Produces clause for equality. pub fn evaluate(&self, ctx: &Context) -> InnerResult<()> { - use ion_rs::{Decoder, AnyEncoding}; + use ion_rs::{AnyEncoding, Decoder}; let (input, _encoding) = ctx.input(ctx.encoding())?; let mut reader = ion_rs::Reader::new(AnyEncoding.with_catalog(ctx.build_catalog()), input)?; @@ -227,11 +226,11 @@ impl Produces { let mut elem_iter = self.elems.iter(); while is_equal { - let (actual_value, expected_elem) = (reader.next()?, elem_iter.next()); + let (actual_value, expected_elem) = (reader.read_next_element()?, elem_iter.next()); match (actual_value, expected_elem) { (None, None) => break, (Some(actual_value), Some(expected_elem)) => { - is_equal &= super::fragment::ProxyElement(expected_elem, ctx) == actual_value + is_equal &= expected_elem.eq(&actual_value); } _ => is_equal = false, } @@ -253,7 +252,7 @@ pub(crate) struct Denotes { impl Denotes { pub fn evaluate(&self, ctx: &Context) -> InnerResult<()> { - use ion_rs::{Decoder, AnyEncoding}; + use ion_rs::{AnyEncoding, Decoder}; let (input, _encoding) = ctx.input(ctx.encoding())?; let mut reader = ion_rs::Reader::new(AnyEncoding.with_catalog(ctx.build_catalog()), input)?; let mut elem_iter = self.model.iter(); @@ -262,8 +261,9 @@ impl Denotes { while is_equal { let (read_value, expected_element) = (reader.next()?, elem_iter.next()); is_equal = match (read_value, expected_element) { - (Some(actual), Some(expected)) => - is_equal && compare_values(ctx, expected, &actual)?, + (Some(actual), Some(expected)) => { + is_equal && compare_values(ctx, expected, &actual)? + } (None, None) => break, _ => false, } @@ -299,7 +299,10 @@ impl Then { /// Determine the encoding (text/binary) of the fragments contained within this Then clause. fn fragment_encoding(&self) -> IonEncoding { - let enc = self.fragments.iter().find(|f| matches!(f, Fragment::Text(_) | Fragment::Binary(_))); + let enc = self + .fragments + .iter() + .find(|f| matches!(f, Fragment::Text(_) | Fragment::Binary(_))); match enc { Some(Fragment::Text(_)) => IonEncoding::Text, Some(Fragment::Binary(_)) => IonEncoding::Binary, diff --git a/tests/conformance_dsl/document.rs b/tests/conformance_dsl/document.rs index cdeaa794..734c095e 100644 --- a/tests/conformance_dsl/document.rs +++ b/tests/conformance_dsl/document.rs @@ -1,14 +1,17 @@ use std::str::FromStr; -use super::*; use super::context::Context; use super::continuation::*; +use super::*; use ion_rs::{Element, Sequence}; /// Convert a collection of Fragments into a binary encoded ion stream. -pub(crate) fn to_binary<'a, T: IntoIterator>(ctx: &'a Context, fragments: T) -> InnerResult> { - let mut bin_encoded = vec!(); +pub(crate) fn to_binary<'a, T: IntoIterator>( + ctx: &'a Context, + fragments: T, +) -> InnerResult> { + let mut bin_encoded = vec![]; for frag in fragments { let bin = frag.to_binary(ctx)?; bin_encoded.extend(bin); @@ -17,8 +20,11 @@ pub(crate) fn to_binary<'a, T: IntoIterator>(ctx: &'a Context } /// Convert a collection of Fragments into a text encoded ion stream. -pub(crate) fn to_text<'a, T: IntoIterator>(ctx: &'a Context, fragments: T) -> InnerResult> { - let mut txt_encoded = vec!(); +pub(crate) fn to_text<'a, T: IntoIterator>( + ctx: &'a Context, + fragments: T, +) -> InnerResult> { + let mut txt_encoded = vec![]; for frag in fragments { let txt = frag.to_text(ctx)?; txt_encoded.extend(txt); @@ -47,8 +53,11 @@ impl Document { /// Determine the ion encoding (text/binary) of this document based on the fragments defined by /// the document. fn encoding(&self) -> IonEncoding { - match self.fragments.iter().fold((false,false), |acc, f| { - (acc.0 || matches!(f, Fragment::Text(_)), acc.1 || matches!(f, Fragment::Binary(_))) + match self.fragments.iter().fold((false, false), |acc, f| { + ( + acc.0 || matches!(f, Fragment::Text(_)), + acc.1 || matches!(f, Fragment::Binary(_)), + ) }) { (true, false) => IonEncoding::Text, (false, true) => IonEncoding::Binary, @@ -76,8 +85,8 @@ impl FromStr for Document { type Err = ConformanceError; fn from_str(other: &str) -> std::result::Result { - let element = Element::read_first(other)? - .ok_or(ConformanceErrorKind::ExpectedDocumentClause)?; + let element = + Element::read_first(other)?.ok_or(ConformanceErrorKind::ExpectedDocumentClause)?; let Some(seq) = element.as_sequence() else { return Err(ConformanceErrorKind::ExpectedDocumentClause.into()); }; @@ -90,38 +99,36 @@ impl TryFrom for Document { fn try_from(other: Sequence) -> InnerResult { let clause: Clause = Clause::try_from(other)?; - let mut doc: Document = parse_document_like(&clause)?; + let continuation = match clause.tpe { - ClauseType::Ion1_X => { - Continuation::Extensions(vec!( - Continuation::Then(Box::new(Then { - test_name: None, - fragments: [vec!(Fragment::Ivm(1, 0)), doc.fragments.clone()].concat(), - continuation: doc.continuation.clone(), - })), - Continuation::Then(Box::new(Then { - test_name: None, - fragments: [vec!(Fragment::Ivm(1, 1)), doc.fragments].concat(), - continuation: doc.continuation.clone(), - })), - )) - } + ClauseType::Ion1_X => Continuation::Extensions(vec![ + Continuation::Then(Box::new(Then { + test_name: None, + fragments: [vec![Fragment::Ivm(1, 0)], doc.fragments.clone()].concat(), + continuation: doc.continuation.clone(), + })), + Continuation::Then(Box::new(Then { + test_name: None, + fragments: [vec![Fragment::Ivm(1, 1)], doc.fragments].concat(), + continuation: doc.continuation.clone(), + })), + ]), ClauseType::Ion1_0 => Continuation::Then(Box::new(Then { test_name: None, - fragments: [vec!(Fragment::Ivm(1, 0)), doc.fragments].concat(), + fragments: [vec![Fragment::Ivm(1, 0)], doc.fragments].concat(), continuation: doc.continuation.clone(), })), - ClauseType::Ion1_1 => Continuation::Then(Box::new(Then { + ClauseType::Ion1_1 => Continuation::Then(Box::new(Then { test_name: None, - fragments: [vec!(Fragment::Ivm(1, 1)), doc.fragments].concat(), + fragments: [vec![Fragment::Ivm(1, 1)], doc.fragments].concat(), continuation: doc.continuation.clone(), })), ClauseType::Document => return Ok(doc), _ => return Err(ConformanceErrorKind::ExpectedDocumentClause), }; doc.continuation = continuation; - doc.fragments = vec!(); + doc.fragments = vec![]; Ok(doc) } } diff --git a/tests/conformance_dsl/fragment.rs b/tests/conformance_dsl/fragment.rs index f681e492..50248ae6 100644 --- a/tests/conformance_dsl/fragment.rs +++ b/tests/conformance_dsl/fragment.rs @@ -20,13 +20,12 @@ //! so that symbols from the Produces continuation can be evaluated for unknown symbol notation as //! well. +use super::context::Context; +use super::*; use ion_rs::{ion_seq, v1_0, v1_1, Encoding, WriteConfig}; use ion_rs::{Element, SExp, Sequence, Struct, Symbol}; use ion_rs::{RawSymbolRef, SequenceWriter, StructWriter, ValueWriter, WriteAsIon, Writer}; -use super::context::Context; -use super::*; - /// Shared functionality for Fragments. trait FragmentImpl { /// Encode the current fragment into ion given the provided `WriteConfig` @@ -127,7 +126,7 @@ impl TryFrom for Fragment { } Fragment::Text(text) } - ClauseType::Bytes => Fragment::Binary(parse_bytes_exp(other.body.iter())?), + ClauseType::Binary => Fragment::Binary(parse_bytes_exp(other.body.iter())?), ClauseType::Ivm => { // IVM: (ivm ) let maj = other @@ -193,8 +192,7 @@ pub(crate) struct ProxyElement<'a>(pub &'a Element, pub &'a Context<'a>); impl ProxyElement<'_> { fn write_struct(&self, val: &Struct, writer: V) -> ion_rs::IonResult<()> { - let annotations: Vec<&Symbol> = self.0.annotations().iter().collect(); - let annot_writer = writer.with_annotations(annotations)?; + let annot_writer = writer.with_annotations(self.0.annotations())?; let mut strukt = annot_writer.struct_writer()?; for (name, value) in val.fields() { @@ -342,30 +340,33 @@ impl PartialEq> for ProxyElement<'_ impl WriteAsIon for ProxyElement<'_> { fn write_as_ion(&self, writer: V) -> ion_rs::IonResult<()> { - match self.0.ion_type() { - IonType::Symbol => self.write_symbol(writer), - IonType::Struct => { - if !self.0.is_null() { - self.write_struct(self.0.as_struct().unwrap(), writer) - } else { - writer.write(self.0) - } - } - IonType::List => { - let annotations: Vec<&Symbol> = self.0.annotations().iter().collect(); - let annot_writer = writer.with_annotations(annotations)?; + use ion_rs::Value::*; + match self.0.value() { + Symbol(_) => self.write_symbol(writer), + Struct(strukt) => self.write_struct(strukt, writer), + List(list) => { + let annot_writer = writer.with_annotations(self.0.annotations())?; let mut list_writer = annot_writer.list_writer()?; - for elem in self.0.as_sequence().unwrap() { + for elem in list { list_writer.write(ProxyElement(elem, self.1))?; } list_writer.close()?; Ok(()) } - IonType::SExp => { - let annotations: Vec<&Symbol> = self.0.annotations().iter().collect(); - let annot_writer = writer.with_annotations(annotations)?; + SExp(sexp) => { + match sexp.get(0).map(Element::value) { + Some(Symbol(symbol)) if symbol.text().is_some() => { + let text = symbol.text().unwrap(); + if text.starts_with("#$:") { + todo!("e-expression transcription") + } + } + _ => {} + } + + let annot_writer = writer.with_annotations(self.0.annotations())?; let mut sexp_writer = annot_writer.sexp_writer()?; - for elem in self.0.as_sequence().unwrap() { + for elem in sexp { sexp_writer.write(ProxyElement(elem, self.1))?; } sexp_writer.close()?; diff --git a/tests/conformance_dsl/model.rs b/tests/conformance_dsl/model.rs index 5aa31a01..74fd48ae 100644 --- a/tests/conformance_dsl/model.rs +++ b/tests/conformance_dsl/model.rs @@ -1,35 +1,53 @@ -use ion_rs::{Decimal, Element, IonType, Sequence, Timestamp, ValueRef}; -use ion_rs::{LazyRawValue, LazyRawFieldName, v1_0::RawValueRef}; +use super::{ + parse_bytes_exp, parse_text_exp, Clause, ClauseType, ConformanceErrorKind, Context, InnerResult, +}; use ion_rs::decimal::coefficient::Coefficient; -use super::{Clause, ClauseType, Context, ConformanceErrorKind, InnerResult, parse_text_exp, parse_bytes_exp}; - +use ion_rs::{v1_0::RawValueRef, Int, LazyRawValue, List, SExp, SymbolId, SymbolRef, Value}; +use ion_rs::{Decimal, Element, IonType, Sequence, Timestamp, ValueRef}; /// Represents a symbol in the Data Model representation of ion data. #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub(crate) enum SymbolToken { Text(String), - Offset(i64), + Address(SymbolId), Absent(String, i64), } +impl SymbolToken { + fn from_symbol<'a>(symbol: impl Into>) -> Self { + match symbol.into().text() { + Some(text) => SymbolToken::Text(text.to_string()), + None => SymbolToken::Address(0), + } + } + + fn as_symbol_ref(&self) -> SymbolRef<'_> { + use SymbolToken::*; + match self { + Text(text) => SymbolRef::with_text(text.as_str()), + Address(address) if *address == 0 => SymbolRef::with_unknown_text(), + Address(..) | Absent(..) => todo!("deal with SymbolToken with ambiguous meaning"), + } + } +} + impl std::fmt::Display for SymbolToken { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { SymbolToken::Text(txt) => write!(f, "{}", txt), - SymbolToken::Offset(id) => write!(f, "#${}", id), + SymbolToken::Address(id) => write!(f, "#${}", id), SymbolToken::Absent(txt, id) => write!(f, "#${}#{}", txt, id), } } } - impl TryFrom<&Element> for SymbolToken { type Error = ConformanceErrorKind; fn try_from(other: &Element) -> InnerResult { match other.ion_type() { IonType::String => Ok(SymbolToken::Text(other.as_string().unwrap().to_owned())), - IonType::Int => Ok(SymbolToken::Offset(other.as_i64().unwrap())), + IonType::Int => Ok(SymbolToken::Address(other.as_usize().unwrap())), IonType::SExp => { let clause: Clause = other.as_sequence().unwrap().try_into()?; @@ -37,10 +55,18 @@ impl TryFrom<&Element> for SymbolToken { ClauseType::Text => { let text = parse_text_exp(clause.body.iter())?; Ok(SymbolToken::Text(text)) - }, + } ClauseType::Absent => { - let symtab = clause.body.first().and_then(|v| v.as_string()).ok_or(ConformanceErrorKind::ExpectedSymbolType)?; - let offset = clause.body.get(1).and_then(|v| v.as_i64()).ok_or(ConformanceErrorKind::ExpectedSymbolType)?; + let symtab = clause + .body + .first() + .and_then(|v| v.as_string()) + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?; + let offset = clause + .body + .get(1) + .and_then(|v| v.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?; Ok(SymbolToken::Absent(symtab.to_string(), offset)) } _ => unreachable!(), @@ -60,7 +86,7 @@ impl TryFrom<&Element> for SymbolToken { pub(crate) enum ModelValue { Null(IonType), Bool(bool), - Int(i64), + Int(Int), Float(f64), Decimal(Decimal), Timestamp(Timestamp), @@ -73,19 +99,93 @@ pub(crate) enum ModelValue { Clob(Vec), } +impl TryFrom<&Element> for ModelValue { + type Error = ConformanceErrorKind; + + fn try_from(element: &Element) -> Result { + use Value::*; + let model_value = match element.value() { + Null(ion_type) => ModelValue::Null(*ion_type), + Bool(b) => ModelValue::Bool(*b), + Int(i) => ModelValue::Int(*i), + Float(f) => ModelValue::Float(*f), + Decimal(d) => ModelValue::Decimal(*d), + Timestamp(t) => ModelValue::Timestamp(*t), + Symbol(s) => ModelValue::Symbol(SymbolToken::from_symbol(s)), + String(s) => ModelValue::String(s.clone().into()), + Clob(c) => ModelValue::Clob(c.as_ref().to_vec()), + Blob(b) => ModelValue::Blob(b.as_ref().to_vec()), + List(seq) | SExp(seq) => ModelValue::try_from(seq)?, + Struct(s) => ModelValue::Struct( + s.iter() + .map(|(name, value)| Ok((SymbolToken::from_symbol(name), value.try_into()?))) + .collect::, Self::Error>>()?, + ), + }; + Ok(model_value) + } +} + +impl TryFrom<&ModelValue> for Element { + type Error = ConformanceErrorKind; + + fn try_from(model_value: &ModelValue) -> Result { + let element = match model_value { + ModelValue::Null(ion_type) => (*ion_type).into(), + ModelValue::Bool(b) => (*b).into(), + ModelValue::Int(i) => (*i).into(), + ModelValue::Float(f) => (*f).into(), + ModelValue::Decimal(d) => (*d).into(), + ModelValue::Timestamp(t) => (*t).into(), + ModelValue::String(s) => s.to_owned().into(), + // TODO: Logic is needed to identify escaped symbols and resolve them. + ModelValue::Symbol(s) => s.as_symbol_ref().to_owned().into(), + ModelValue::List(values) => { + let elements = values + .iter() + .map(Element::try_from) + .collect::, ConformanceErrorKind>>()?; + List::from(elements).into() + } + ModelValue::Sexp(values) => { + let elements = values + .iter() + .map(Element::try_from) + .collect::, ConformanceErrorKind>>()?; + SExp::from(elements).into() + } + ModelValue::Struct(_) => todo!(), + ModelValue::Blob(_) => todo!(), + ModelValue::Clob(_) => todo!(), + }; + Ok(element) + } +} + impl TryFrom<&Sequence> for ModelValue { type Error = ConformanceErrorKind; fn try_from(other: &Sequence) -> InnerResult { let elems: Vec = other.iter().cloned().collect(); - let tpe_sym = elems.first().ok_or(ConformanceErrorKind::ExpectedModelValue)?.as_symbol().ok_or(ConformanceErrorKind::ExpectedModelValue)?; - let tpe = tpe_sym.text().ok_or(ConformanceErrorKind::ExpectedModelValue)?; + let tpe_sym = elems + .first() + .ok_or(ConformanceErrorKind::ExpectedModelValue)? + .as_symbol() + .ok_or(ConformanceErrorKind::ExpectedModelValue)?; + let tpe = tpe_sym + .text() + .ok_or(ConformanceErrorKind::ExpectedModelValue)?; match tpe { "Null" => { - let type_elem = elems.get(1).ok_or(ConformanceErrorKind::ExpectedModelValue)?; - let type_str = type_elem.as_symbol() - .and_then(|s| s.text()) - .ok_or(ConformanceErrorKind::ExpectedModelValue)?; + let type_str = match elems.get(1) { + Some(type_element) => type_element + .as_symbol() + .ok_or(ConformanceErrorKind::ExpectedSymbolType)? + .text() + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?, + // If no symbol is specified after `Null`, default to `null` to produce `null.null`. + None => "null", + }; match ion_type_from_str(type_str) { Some(tpe) => Ok(ModelValue::Null(tpe)), @@ -93,19 +193,22 @@ impl TryFrom<&Sequence> for ModelValue { } } "Bool" => { - let value = elems.get(1) + let value = elems + .get(1) .and_then(|e| e.as_bool()) .ok_or(ConformanceErrorKind::ExpectedModelValue)?; Ok(ModelValue::Bool(value)) } "Int" => { - let value = elems.get(1) - .and_then(|e| e.as_i64()) + let value = elems + .get(1) + .and_then(|e| e.as_int()) .ok_or(ConformanceErrorKind::ExpectedModelValue)?; - Ok(ModelValue::Int(value)) + Ok(ModelValue::Int(*value)) } "Float" => { - let value_str = elems.get(1) + let value_str = elems + .get(1) .and_then(|e| e.as_string()) .ok_or(ConformanceErrorKind::ExpectedModelValue)?; match value_str.parse::() { @@ -113,16 +216,24 @@ impl TryFrom<&Sequence> for ModelValue { Err(_) => Err(ConformanceErrorKind::ExpectedFloatString), } } - "Decimal" => Ok(ModelValue::Decimal(parse_model_decimal(elems.iter().skip(1))?)), + "Decimal" => Ok(ModelValue::Decimal(parse_model_decimal( + elems.iter().skip(1), + )?)), "String" => { let string = parse_text_exp(elems.iter().skip(1))?; Ok(ModelValue::String(string)) } "Symbol" => { - let value = elems.get(1).ok_or(ConformanceErrorKind::ExpectedSymbolType)?; + let value = elems + .get(1) + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?; match value.ion_type() { - IonType::String => Ok(ModelValue::Symbol(SymbolToken::Text(value.as_string().unwrap().to_owned()))), - IonType::Int => Ok(ModelValue::Symbol(SymbolToken::Offset(value.as_i64().unwrap()))), + IonType::String => Ok(ModelValue::Symbol(SymbolToken::Text( + value.as_string().unwrap().to_owned(), + ))), + IonType::Int => Ok(ModelValue::Symbol(SymbolToken::Address( + value.as_usize().unwrap(), + ))), IonType::SExp => { let clause: Clause = value.as_sequence().unwrap().try_into()?; @@ -130,11 +241,22 @@ impl TryFrom<&Sequence> for ModelValue { ClauseType::Text => { let text = parse_text_exp(clause.body.iter())?; Ok(ModelValue::Symbol(SymbolToken::Text(text))) - }, + } ClauseType::Absent => { - let symtab= clause.body.first().and_then(|v| v.as_string()).ok_or(ConformanceErrorKind::ExpectedSymbolType)?; - let offset = clause.body.get(1).and_then(|v| v.as_i64()).ok_or(ConformanceErrorKind::ExpectedSymbolType)?; - Ok(ModelValue::Symbol(SymbolToken::Absent(symtab.to_string(), offset))) + let symtab = clause + .body + .first() + .and_then(|v| v.as_string()) + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?; + let offset = clause + .body + .get(1) + .and_then(|v| v.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedSymbolType)?; + Ok(ModelValue::Symbol(SymbolToken::Absent( + symtab.to_string(), + offset, + ))) } _ => unreachable!(), } @@ -142,48 +264,35 @@ impl TryFrom<&Sequence> for ModelValue { _ => Err(ConformanceErrorKind::ExpectedSymbolType), } } - "Timestamp" => Ok(ModelValue::Timestamp(parse_timestamp(elems.iter().skip(1))?)), + "Timestamp" => Ok(ModelValue::Timestamp(parse_timestamp( + elems.iter().skip(1), + )?)), "List" => { - let mut list = vec!(); + let mut list = vec![]; for elem in elems.iter().skip(1) { - if let Some(seq) = elem.as_sequence() { - list.push(ModelValue::try_from(seq)?); - } + list.push(ModelValue::try_from(elem)?); } Ok(ModelValue::List(list)) } "Sexp" => { - let mut sexp = vec!(); + let mut sexp = vec![]; for elem in elems.iter().skip(1) { - if let Some(seq) = elem.as_sequence() { - sexp.push(ModelValue::try_from(seq)?); - } + sexp.push(ModelValue::try_from(elem)?); } Ok(ModelValue::Sexp(sexp)) } "Struct" => { - let mut fields = vec!(); + let mut fields = vec![]; for elem in elems.iter().skip(1) { if let Some(seq) = elem.as_sequence() { + if seq.len() != 2 { + // Didn't get a field name/value pair + return Err(ConformanceErrorKind::ExpectedClause); + } // Each elem should be a model symtok followed by a model value. - let (first, second) = (seq.get(0), seq.get(1)); - let field_sym = first.map(SymbolToken::try_from).ok_or(ConformanceErrorKind::ExpectedSymbolType)?.unwrap(); - let value = match second.map(|e| e.ion_type()) { - Some(IonType::String) => { - let string = second.unwrap().as_string().unwrap(); - ModelValue::String(string.to_string()) - } - Some(IonType::Int) => { - let int_val = second.unwrap().as_i64().unwrap(); - ModelValue::Int(int_val) - } - Some(IonType::SExp) => { - let seq = second.unwrap().as_sequence().unwrap(); - ModelValue::try_from(seq)? - } - _ => return Err(ConformanceErrorKind::ExpectedModelValue), - }; - + let (first, second) = (seq.get(0).unwrap(), seq.get(1).unwrap()); + let field_sym = SymbolToken::try_from(first)?; + let value = ModelValue::try_from(second)?; fields.push((field_sym, value)); } } @@ -201,16 +310,17 @@ impl PartialEq for ModelValue { match self { ModelValue::Null(tpe) => other.ion_type() == *tpe && other.is_null(), ModelValue::Bool(val) => other.as_bool() == Some(*val), - ModelValue::Int(val) => other.as_i64() == Some(*val), + ModelValue::Int(val) => other.as_int() == Some(val), ModelValue::Float(val) => other.as_float() == Some(*val), ModelValue::Decimal(dec) => other.as_decimal() == Some(*dec), ModelValue::String(val) => other.as_string() == Some(val), ModelValue::Blob(data) => other.as_blob() == Some(data.as_slice()), ModelValue::Clob(data) => other.as_clob() == Some(data.as_slice()), ModelValue::Timestamp(ts) => other.as_timestamp() == Some(*ts), - _ => unreachable!(), // SAFETY: EQ of Symbols, Lists, Structs, and SExps are handled - // via comparison to LazyValues after moving to using a Reader instead of Element - // API. These should join them but haven't yet. + // SAFETY: EQ of Symbols, Lists, Structs, and SExps are handled + // via comparison to LazyValues after moving to using a Reader instead of Element + // API. These should join them but haven't yet. + unexpected => unreachable!("{unexpected:?}"), } } } @@ -223,83 +333,69 @@ impl PartialEq for &ModelValue { /// Compares a ModelValue to a LazyValue for evaluating Denotes clauses. This is used in place of /// PartialEq in order to communicate errors. -pub(crate) fn compare_values(ctx: &Context, model: &ModelValue, other: &ion_rs::LazyValue<'_, T>) -> InnerResult { +pub(crate) fn compare_values( + ctx: &Context, + model: &ModelValue, + other: &ion_rs::LazyValue<'_, T>, +) -> InnerResult { match model { - ModelValue::Symbol(symbol_token) if other.ion_type() == IonType::Symbol => { - // SAFETY: Tested other in the guard above, should not hit the else clause. + ModelValue::Symbol(symbol_token) => { let Some(raw_symbol) = other.raw().map(|r| r.read()) else { - return Ok(false) + return Ok(false); }; let raw_symbol = raw_symbol?; let RawValueRef::Symbol(raw_symbol) = raw_symbol else { - return Ok(false) + return Ok(false); }; - let ValueRef::Symbol(symbol_text) = other.read().expect("error resolving symbol") else { + let ValueRef::Symbol(symbol_text) = other.read().expect("error resolving symbol") + else { return Ok(false); }; let (expected_txt, expected_id) = match symbol_token { SymbolToken::Text(txt) => return Ok(symbol_text == txt), - SymbolToken::Offset(id) => (String::from(""), *id as usize), - SymbolToken::Absent(symtab, id) => match ctx.get_symbol_from_table(symtab, *id as usize) { - None => (String::from(""), 0_usize), - Some(shared_symbol) => { - let shared_text = shared_symbol.text().unwrap_or(""); - (shared_text.to_string(), other.symbol_table().sid_for(&shared_text).unwrap_or(0)) + SymbolToken::Address(id) => (String::from(""), *id), + SymbolToken::Absent(symtab, id) => { + match ctx.get_symbol_from_table(symtab, *id as usize) { + None => (String::from(""), 0_usize), + Some(shared_symbol) => { + let shared_text = shared_symbol.text().unwrap_or(""); + ( + shared_text.to_string(), + other.symbol_table().sid_for(&shared_text).unwrap_or(0), + ) + } } } }; Ok(raw_symbol.matches_sid_or_text(expected_id, &expected_txt)) } - ModelValue::Struct(expected_fields) if other.ion_type() == IonType::Struct => { - // SAFETY: Tested other in the guard above, should not hit the else clause. - let ValueRef::Struct(strukt) = other.read().expect("error reading struct") else { + ModelValue::Struct(expected_fields) => { + let ValueRef::Struct(actual_struct) = other.read().expect("error reading struct") + else { return Ok(false); }; - let mut is_equal = true; - let mut strukt_iter = strukt.iter(); - let mut expected_iter = expected_fields.iter(); - - while is_equal { - let actual = strukt_iter.next(); - let expected = expected_iter.next(); - - match (actual, expected) { - (Some(actual), Some((expected_field, expected_val))) => { - let actual = actual.expect("unable to read struct field"); - let actual_field = actual.raw_name().map(|n| n.read()).expect("unable to get symbolref for field name"); - let actual_field = actual_field.expect("unable to read symbolref for field name"); - - let (expected_txt, expected_id) = match expected_field { - SymbolToken::Text(txt) => (txt.clone(), usize::MAX), - SymbolToken::Offset(id) => (String::from(""), *id as usize), - SymbolToken::Absent(symtab, id) => match ctx.get_symbol_from_table(symtab, *id as usize) { - None => (String::from(""), 0_usize), - Some(shared_symbol) => { - let shared_text = shared_symbol.text().unwrap_or(""); - (shared_text.to_string(), other.symbol_table().sid_for(&shared_text).unwrap_or(0)) - } - } - }; - is_equal = is_equal && actual_field.matches_sid_or_text(expected_id, &expected_txt); - - let actual_value = actual.value(); - - is_equal = is_equal && compare_values(ctx, expected_val, &actual_value)?; - } - (None, None) => break, - _ => is_equal = false, - } + let actual_elem = Element::try_from(actual_struct)?; + let actual_struct = actual_elem.as_struct().unwrap(); + if actual_struct.len() != expected_fields.len() { + return Ok(false); } - Ok(is_equal) + let expected_struct = Element::struct_builder() + .with_fields(expected_fields.iter().map(|(token, model_value)| { + ( + token.as_symbol_ref().to_owned(), + Element::try_from(model_value).unwrap(), + ) + })) + .build(); + Ok(actual_struct.eq(&expected_struct)) } - ModelValue::List(expected) if other.ion_type() == IonType::List => { - // SAFETY: Tested other in the guard above, should not hit the else clause. + ModelValue::List(expected) => { let ValueRef::List(list) = other.read().expect("error reading list") else { return Ok(false); }; @@ -307,16 +403,14 @@ pub(crate) fn compare_values(ctx: &Context, model: &ModelVal let actual: ion_rs::IonResult>> = list.iter().collect(); let actual = actual.expect("Error parsing list"); - for (actual_val, expected_val) in actual.iter().zip(expected.iter()) { if !compare_values(ctx, expected_val, actual_val)? { - return Ok(false) + return Ok(false); } } Ok(true) } - ModelValue::Sexp(expected) if other.ion_type() == IonType::SExp => { - // SAFETY: Tested other in the guard above, should not hit the else clause. + ModelValue::Sexp(expected) => { let ValueRef::SExp(sexp) = other.read().expect("error reading sexp") else { return Ok(false); }; @@ -325,7 +419,7 @@ pub(crate) fn compare_values(ctx: &Context, model: &ModelVal let actual = actual?; for (actual_val, expected_val) in actual.iter().zip(expected.iter()) { if !compare_values(ctx, expected_val, actual_val)? { - return Ok(false) + return Ok(false); } } Ok(true) @@ -340,26 +434,47 @@ pub(crate) fn compare_values(ctx: &Context, model: &ModelVal } /// Parses a Timestamp clause into an ion-rs Timestamp. -fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResult { +fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResult { let mut iter = elems.into_iter(); - let first = iter.next().and_then(|e| e.as_symbol()).and_then(|s| s.text()); + let first = iter + .next() + .and_then(|e| e.as_symbol()) + .and_then(|s| s.text()); match first { Some("year") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; Ok(Timestamp::with_year(year as u32).build()?) } Some("month") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let month = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let month = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; let ts = Timestamp::with_year(year as u32) .with_month(month as u32) .build()?; Ok(ts) } Some("day") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let month = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let day = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let month = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let day = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; let ts = Timestamp::with_year(year as u32) .with_month(month as u32) .with_day(day as u32) @@ -367,14 +482,33 @@ fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResu Ok(ts) } Some("minute") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let month = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let day = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - - let offset = parse_ts_offset(iter.next().and_then(|e| e.as_sequence()).ok_or(ConformanceErrorKind::ExpectedInteger)?)?; - - let hour = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let minute = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let month = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let day = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + + let offset = parse_ts_offset( + iter.next() + .and_then(|e| e.as_sequence()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?, + )?; + + let hour = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let minute = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; let ts = Timestamp::with_year(year as u32) .with_month(month as u32) .with_day(day as u32) @@ -387,15 +521,37 @@ fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResu } } Some("second") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let month = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let day = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - - let offset = parse_ts_offset(iter.next().and_then(|e| e.as_sequence()).ok_or(ConformanceErrorKind::ExpectedInteger)?)?; - - let hour = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let minute = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let second = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let month = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let day = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + + let offset = parse_ts_offset( + iter.next() + .and_then(|e| e.as_sequence()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?, + )?; + + let hour = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let minute = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let second = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; let ts = Timestamp::with_year(year as u32) .with_month(month as u32) .with_day(day as u32) @@ -409,15 +565,37 @@ fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResu } } Some("fraction") => { - let year = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let month = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let day = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - - let offset = parse_ts_offset(iter.next().and_then(|e| e.as_sequence()).ok_or(ConformanceErrorKind::ExpectedInteger)?)?; - - let hour = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let minute = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; - let second = iter.next().and_then(|e| e.as_i64()).ok_or(ConformanceErrorKind::ExpectedInteger)?; + let year = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let month = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let day = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + + let offset = parse_ts_offset( + iter.next() + .and_then(|e| e.as_sequence()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?, + )?; + + let hour = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let minute = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; + let second = iter + .next() + .and_then(|e| e.as_i64()) + .ok_or(ConformanceErrorKind::ExpectedInteger)?; let fraction = parse_model_decimal(iter)?; let ts = Timestamp::with_year(year as u32) .with_month(month as u32) @@ -437,16 +615,24 @@ fn parse_timestamp<'a, I: IntoIterator>(elems: I) -> InnerResu } /// Parses a data-model value timestamp's 'offset' clause into an i64. -fn parse_ts_offset<'a, I: IntoIterator>(elems: I) -> InnerResult> { +fn parse_ts_offset<'a, I: IntoIterator>(elems: I) -> InnerResult> { let mut iter = elems.into_iter(); - match iter.next().and_then(|e| e.as_symbol()).and_then(|s| s.text()) { + match iter + .next() + .and_then(|e| e.as_symbol()) + .and_then(|s| s.text()) + { Some("offset") => { // Either an int or null.. - let offset = iter.next().ok_or(ConformanceErrorKind::ExpectedTimestampOffset)?; + let offset = iter + .next() + .ok_or(ConformanceErrorKind::ExpectedTimestampOffset)?; if offset.is_null() { Ok(None) } else { - let offset = offset.as_i64().ok_or(ConformanceErrorKind::ExpectedInteger)?; + let offset = offset + .as_i64() + .ok_or(ConformanceErrorKind::ExpectedInteger)?; Ok(Some(offset)) } } @@ -455,14 +641,16 @@ fn parse_ts_offset<'a, I: IntoIterator>(elems: I) -> InnerResu } /// Parses a data-model value's Decimal clause into an ion-rs Decimal. -fn parse_model_decimal<'a, I: IntoIterator>(elems: I) -> InnerResult { +fn parse_model_decimal<'a, I: IntoIterator>(elems: I) -> InnerResult { let mut iter = elems.into_iter(); let (first, second) = (iter.next(), iter.next()); match (first.map(|e| e.ion_type()), second.map(|e| e.ion_type())) { (Some(IonType::String), Some(IonType::Int)) => { let (first, second) = (first.unwrap(), second.unwrap()); // SAFETY: We have non-None types. if let Some("negative_0") = first.as_string() { - let exp = second.as_i64().ok_or(ConformanceErrorKind::ExpectedModelValue)?; + let exp = second + .as_i64() + .ok_or(ConformanceErrorKind::ExpectedModelValue)?; Ok(Decimal::new(Coefficient::NEGATIVE_ZERO, exp)) } else { Err(ConformanceErrorKind::ExpectedModelValue) @@ -471,8 +659,12 @@ fn parse_model_decimal<'a, I: IntoIterator>(elems: I) -> Inner (Some(IonType::Int), Some(IonType::Int)) => { let (first, second) = (first.unwrap(), second.unwrap()); // SAFETY: We have non-None types. Ok(Decimal::new( - first.as_i64().ok_or(ConformanceErrorKind::ExpectedModelValue)?, - second.as_i64().ok_or(ConformanceErrorKind::ExpectedModelValue)?, + first + .as_i64() + .ok_or(ConformanceErrorKind::ExpectedModelValue)?, + second + .as_i64() + .ok_or(ConformanceErrorKind::ExpectedModelValue)?, )) } _ => Err(ConformanceErrorKind::ExpectedModelValue), diff --git a/tests/conformance_tests.rs b/tests/conformance_tests.rs index 2937e32f..66ae7962 100644 --- a/tests/conformance_tests.rs +++ b/tests/conformance_tests.rs @@ -11,34 +11,6 @@ use std::str::FromStr; mod implementation { use super::*; - #[test] - fn test_absent_symbol() { - let tests: &[&str] = &[ - r#"(ion_1_1 - (toplevel '#$2' {'#$9': '#$8'}) - (text "") - (denotes (Symbol 2) (Struct (9 (Symbol 8)))) - )"#, - r#"(ion_1_0 - (text '''$ion_symbol_table::{imports:[{name:"abcs", version: 2}]}''') - (text "$10 $11") - (produces '#$abcs#1' '#$abcs#2') - )"#, - r#"(ion_1_0 - (text '''$ion_symbol_table::{imports:[{name:"abcs", version: 2}]}''') - (text "$10 $11") - (denotes (Symbol (absent "abcs" 1)) (Symbol (absent "abcs" 2))) - )"#, - ]; - - for test in tests { - Document::from_str(test) - .unwrap_or_else(|e| panic!("Failed to load document:\n{:?}", e)) - .run() - .unwrap_or_else(|e| panic!("Test failed: {:?}", e)); - } - } - #[test] fn test_timestamps() { let tests: &[&str] = &[ @@ -82,13 +54,13 @@ mod implementation { "(ion_1_1 (produces ))", "(document (and (produces ) (produces )))", "(document (text \"a\") (not (and (produces b) (produces c))))", - "(ion_1_1 (bytes 0x60 0x61 0x01 0xEB 0x01) (produces 0 1 null.int))", + "(ion_1_1 (binary 0x60 0x61 0x01 0xEB 0x01) (produces 0 1 null.int))", r#"(ion_1_0 (then (text "a") (produces a)))"#, r#"(ion_1_1 (text "a") (text "b") (text "c") (produces a b c))"#, r#"(ion_1_1 (text "\"Hello\" null.int false") (denotes (String "Hello") (Null int) (Bool false)))"#, r#"(ion_1_1 (each (text "0") - (bytes 0x60) + (binary 0x60) (denotes (Int 0))) )"#, r#"(document (ivm 1 2) (signals "Invalid Version"))"#, @@ -107,11 +79,13 @@ mod implementation { mod ion_tests { use super::*; - #[test_resources("ion-tests/conformance/null.ion")] - #[test_resources("ion-tests/conformance/core/typed_null.ion")] - #[test_resources("ion-tests/conformance/core/string_symbol.ion")] - #[test_resources("ion-tests/conformance/core/empty_document.ion")] - #[test_resources("ion-tests/conformance/core/toplevel_produces.ion")] + #[test_resources("ion-tests/conformance/core/*")] + #[test_resources("ion-tests/conformance/data_model/annotations.ion")] + #[test_resources("ion-tests/conformance/data_model/boolean.ion")] + #[test_resources("ion-tests/conformance/data_model/integer.ion")] + #[test_resources("ion-tests/conformance/data_model/null.ion")] + // No support for half-precision floats yet. + // #[test_resources("ion-tests/conformance/data_model/float.ion")] fn conformance(file_name: &str) { println!("Testing: {}", file_name); let collection = TestCollection::load(file_name).expect("unable to load test file");