use crate::lib::fmt; #[cfg(feature = "std")] use std::error::Error as StdError; use crate::{stream::StreamOnce, ErrorOffset}; use self::ParseResult::*; pub(crate) trait ResultExt { fn committed(self) -> ParseResult; } impl ResultExt for Result { fn committed(self) -> ParseResult { match self { Ok(x) => CommitOk(x), Err(x) => CommitErr(x), } } } #[macro_export] #[doc(hidden)] macro_rules! ctry { ($result:expr) => { match $result { $crate::error::ParseResult::CommitOk(x) => (x, $crate::error::Commit::Commit(())), $crate::error::ParseResult::PeekOk(x) => (x, $crate::error::Commit::Peek(())), $crate::error::ParseResult::CommitErr(err) => { return $crate::error::ParseResult::CommitErr(err.into()) } $crate::error::ParseResult::PeekErr(err) => { return $crate::error::ParseResult::PeekErr(err.into()) } } }; } /// Trait for types which can be used to construct error information. /// /// To call functions expecting this trait, use the wrapper types defined in this module /// `Token`, `Range`, `Format` or `Static`/`&'static str` pub trait ErrorInfo<'s, T, R> { type Format: fmt::Display; #[allow(clippy::wrong_self_convention)] fn into_info(&'s self) -> Info; } impl<'s, 'a, T, R, F> ErrorInfo<'s, T, R> for &'a F where F: ErrorInfo<'s, T, R>, { type Format = F::Format; fn into_info(&'s self) -> Info { (**self).into_info() } } #[derive(Clone, Debug)] pub enum Info { Token(T), Range(R), Static(&'static str), Format(F), } impl<'s, T, R, F> ErrorInfo<'s, T, R> for Info where T: Clone, R: Clone, F: fmt::Display + 's, { type Format = &'s F; fn into_info(&'s self) -> Info>::Format> { match self { Info::Token(b) => Info::Token(b.clone()), Info::Range(b) => Info::Range(b.clone()), Info::Static(b) => Info::Static(*b), Info::Format(b) => Info::Format(b), } } } impl From for Info { fn from(s: char) -> Self { Info::Token(s) } } impl<'s, R> ErrorInfo<'s, char, R> for char { type Format = &'static str; fn into_info(&self) -> Info { Info::Token(*self) } } impl From<&'static str> for Info { fn from(s: &'static str) -> Self { Info::Static(s) } } impl<'s, T, R> ErrorInfo<'s, T, R> for &'static str { type Format = &'static str; fn into_info(&self) -> Info { Info::Static(*self) } } impl From for Info { fn from(s: u8) -> Self { Info::Token(s) } } impl ErrorInfo<'_, Self, R> for u8 { type Format = &'static str; fn into_info(&self) -> Info { Info::Token(*self) } } /// Newtype which constructs an `Info::Token` through `ErrorInfo` pub struct Token(pub T); impl From> for Info { fn from(s: Token) -> Self { Info::Token(s.0) } } impl<'s, T, R> ErrorInfo<'s, T, R> for Token where T: Clone, { type Format = &'static str; fn into_info(&'s self) -> Info { Info::Token(self.0.clone()) } } /// Newtype which constructs an `Info::Range` through `ErrorInfo` pub struct Range(pub R); impl From> for Info { fn from(s: Range) -> Self { Info::Range(s.0) } } impl<'s, T, R> ErrorInfo<'s, T, R> for Range where R: Clone, { type Format = &'static str; fn into_info(&'s self) -> Info { Info::Range(self.0.clone()) } } /// Newtype which constructs an `Info::Static` through `ErrorInfo` /// A plain `&'static str` can also be used, this exists for consistency. pub struct Static(&'static str); impl From for Info where F: fmt::Display, { fn from(s: Static) -> Self { Info::Static(s.0) } } impl<'s, T, R> ErrorInfo<'s, T, R> for Static { type Format = &'static str; fn into_info(&'s self) -> Info { Info::Static(self.0) } } /// Newtype which constructs an `Info::Format` through `ErrorInfo` pub struct Format(pub F) where F: fmt::Display; impl From> for Info where F: fmt::Display, { fn from(s: Format) -> Self { Info::Format(s.0) } } impl<'s, T, R, F> ErrorInfo<'s, T, R> for Format where F: fmt::Display + 's, { type Format = &'s F; fn into_info(&'s self) -> Info { Info::Format(&self.0) } } /// Enum used to indicate if a parser committed any items of the stream it was given as an input. /// /// This is used by parsers such as `or` and `choice` to determine if they should try to parse /// with another parser as they will only be able to provide good error reporting if the preceding /// parser did not commit to the parse. #[derive(Clone, PartialEq, Debug, Copy)] pub enum Commit { /// Constructor indicating that the parser has committed to this parse. If a parser after this fails, /// other parser alternatives will not be attempted (`CommitErr` will be returned) Commit(T), /// Constructor indicating that the parser has not committed to this parse. If a parser after this fails, /// other parser alternatives will be attempted (`EmptyErr` will be returned) Peek(T), } impl AsMut for Commit { fn as_mut(&mut self) -> &mut T { match *self { Commit::Peek(ref mut t) | Commit::Commit(ref mut t) => t, } } } impl AsRef for Commit { fn as_ref(&self) -> &T { match *self { Commit::Peek(ref t) | Commit::Commit(ref t) => t, } } } impl Commit { /// Returns true if `self` is peek. pub fn is_peek(&self) -> bool { match *self { Commit::Peek(_) => true, Commit::Commit(_) => false, } } /// Extracts the contained value. pub fn into_inner(self) -> T { match self { Commit::Peek(x) | Commit::Commit(x) => x, } } /// Converts `self` into the `Commit` state. pub fn into_commit(self) -> Commit { Commit::Commit(self.into_inner()) } /// Converts `self` into the `Peek` state. pub fn into_peek(self) -> Commit { Commit::Peek(self.into_inner()) } /// Maps over the contained value without changing the committed state. pub fn map(self, f: F) -> Commit where F: FnOnce(T) -> U, { match self { Commit::Peek(x) => Commit::Peek(f(x)), Commit::Commit(x) => Commit::Commit(f(x)), } } pub fn merge(&self, current: Commit) -> Commit { match *self { Commit::Peek(_) => current, Commit::Commit(_) => current.into_commit(), } } /// Combines the `Commit` flags from `self` and the result of `f`. /// /// ```text /// Peek <> Peek -> Peek /// Commit <> Peek -> Commit /// Peek <> Commit -> Commit /// Commit <> Commit -> Commit /// ``` /// /// ``` /// # extern crate combine as pc; /// # use pc::*; /// # fn main() { /// //Parses a character of string literal and handles the escaped characters \\ and \" as \ /// //and " respectively /// fn char(input: &mut Input) -> StdParseResult /// where Input: Stream, /// Input::Error: ParseError, /// { /// let (c, committed) = satisfy(|c| c != '"').parse_stream(input).into_result()?; /// match c { /// //Since the `char` parser has already committed some of the input `combine` is used /// //propagate the committed state to the next part of the parser /// '\\' => committed.combine(|_| { /// satisfy(|c| c == '"' || c == '\\') /// .map(|c| { /// match c { /// '"' => '"', /// '\\' => '\\', /// c => c /// } /// }) /// .parse_stream(input) /// .into_result() /// }), /// _ => Ok((c, committed)) /// } /// } /// let result = many(parser(char)) /// .easy_parse(r#"abc\"\\"#); /// assert_eq!(result, Ok((r#"abc"\"#.to_string(), ""))); /// } /// ``` pub fn combine(self, f: F) -> StdParseResult2 where F: FnOnce(T) -> StdParseResult2, { match self { Commit::Commit(x) => match f(x) { Ok((v, Commit::Peek(()))) => Ok((v, Commit::Commit(()))), Err(Commit::Peek(err)) => Err(Commit::Commit(err)), y => y, }, Commit::Peek(x) => f(x), } } pub fn combine_commit(self, f: F) -> ParseResult where F: FnOnce(T) -> ParseResult, { use self::ParseResult::*; match self { Commit::Commit(x) => match f(x) { PeekOk(v) => CommitOk(v), PeekErr(err) => CommitErr(err.error), y => y, }, Commit::Peek(x) => f(x), } } } /// A type alias over the specific `Result` type used by parsers to indicate whether they were /// successful or not. /// `O` is the type that is output on success. /// `Input` is the specific stream type used in the parser. pub type StdParseResult = Result<(O, Commit<()>), Commit::Error>>>; pub type StdParseResult2 = Result<(O, Commit<()>), Commit>>; /// `StreamError` represents a single error returned from a `Stream` or a `Parser`. /// /// Usually multiple instances of `StreamError` is composed into a `ParseError` to build the final /// error value. pub trait StreamError: Sized { fn unexpected_token(token: Item) -> Self; fn unexpected_range(token: Range) -> Self; fn unexpected_format(msg: T) -> Self where T: fmt::Display; fn unexpected(info: E) -> Self where E: for<'s> ErrorInfo<'s, Item, Range>, { match info.into_info() { Info::Token(b) => Self::unexpected_token(b), Info::Range(b) => Self::unexpected_range(b), Info::Static(b) => Self::unexpected_static_message(b), Info::Format(b) => Self::unexpected_format(b), } } fn unexpected_static_message(msg: &'static str) -> Self { Self::unexpected_format(msg) } fn expected_token(token: Item) -> Self; fn expected_range(token: Range) -> Self; fn expected_format(msg: T) -> Self where T: fmt::Display; fn expected(info: E) -> Self where E: for<'s> ErrorInfo<'s, Item, Range>, { match info.into_info() { Info::Token(b) => Self::expected_token(b), Info::Range(b) => Self::expected_range(b), Info::Static(b) => Self::expected_static_message(b), Info::Format(b) => Self::expected_format(b), } } fn expected_static_message(msg: &'static str) -> Self { Self::expected_format(msg) } fn message_token(token: Item) -> Self; fn message_range(token: Range) -> Self; fn message_format(msg: T) -> Self where T: fmt::Display; fn message_static_message(msg: &'static str) -> Self { Self::message_format(msg) } fn message(info: E) -> Self where E: for<'s> ErrorInfo<'s, Item, Range>, { match info.into_info() { Info::Token(b) => Self::message_token(b), Info::Range(b) => Self::message_range(b), Info::Static(b) => Self::message_static_message(b), Info::Format(b) => Self::message_format(b), } } #[cfg(feature = "std")] fn other(err: E) -> Self where E: StdError + Send + Sync + 'static, { Self::message_format(err) } fn end_of_input() -> Self { Self::unexpected_static_message("end of input") } fn is_unexpected_end_of_input(&self) -> bool; /// Converts `self` into a different `StreamError` type. /// /// This should aim to preserve as much information as possible into the returned `T` value but /// if `Self` ignores some information passed to it using one of the constructors that /// information is naturally lost. fn into_other(self) -> T where T: StreamError; } /// Trait which defines a combine parse error. /// /// A parse error is composed of zero or more `StreamError` instances which gets added to it as /// errors are encountered during parsing. pub trait ParseError: Sized + PartialEq { type StreamError: StreamError; /// Constructs an empty error. /// /// An empty error is expected to be cheap to create as it is frequently created and discarded. fn empty(position: Position) -> Self; /// Creates a `ParseError` from a single `Self::StreamError` fn from_error(position: Position, err: Self::StreamError) -> Self { let mut errors = Self::empty(position); errors.add(err); errors } fn position(&self) -> Position { // TODO Remove the default implementation in a breaking release unimplemented!() } /// Sets the position of this `ParseError` fn set_position(&mut self, position: Position); /// Merges two errors. If they exist at the same position the errors of `other` are /// added to `self` (using the semantics of `add`). If they are not at the same /// position the error furthest ahead are returned, ignoring the other `ParseError`. fn merge(self, other: Self) -> Self { other } /// Adds a `StreamError` to `self`. /// /// It is up to each individual error type to define what adding an error does, some may push /// it to a vector while others may only keep `self` or `err` to avoid allocation fn add(&mut self, err: Self::StreamError); fn add_expected(&mut self, info: E) where E: for<'s> ErrorInfo<'s, Item, Range>, { self.add(Self::StreamError::expected(info)) } fn add_unexpected(&mut self, info: E) where E: for<'s> ErrorInfo<'s, Item, Range>, { self.add(Self::StreamError::unexpected(info)) } fn add_message(&mut self, info: E) where E: for<'s> ErrorInfo<'s, Item, Range>, { self.add(Self::StreamError::message(info)) } /// Sets `info` as the *only* `Expected` error of `self` fn set_expected(self_: &mut Tracked, info: Self::StreamError, f: F) where F: FnOnce(&mut Tracked); /// Removes any expected errors currently in `self` fn clear_expected(&mut self) {} fn is_unexpected_end_of_input(&self) -> bool; /// Does a best-effort conversion of `self` into another `ParseError` fn into_other(self) -> T where T: ParseError; } /// Defines a conversion between two parse error types. /// /// Like `ParseError::into_other` but with a more general signature /// (This will take the place of `into_other` on breaking release of combine) pub trait ParseErrorInto: Sized { fn into_other_error(self) -> T where T: ParseError, Item2: From, Range2: From, Position2: From; } /// Defines a conversion between two stream error types. /// /// Like `StreamError::into_other` but with a more general signature /// (This will take the place of `into_other` on breaking release of combine) pub trait StreamErrorInto: Sized { fn into_other_error(self) -> T where T: StreamError, Item2: From, Range2: From; } #[derive(Clone, Copy, Debug, PartialEq)] pub enum UnexpectedParse { Eoi, Unexpected, } impl fmt::Display for UnexpectedParse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } #[cfg(feature = "std")] impl StdError for UnexpectedParse { fn description(&self) -> &str { self.as_str() } } impl UnexpectedParse { fn as_str(&self) -> &str { use self::UnexpectedParse::*; match *self { Unexpected => "unexpected parse", Eoi => "unexpected end of input", } } } impl StreamError for UnexpectedParse { #[inline] fn unexpected_token(_: Item) -> Self { UnexpectedParse::Unexpected } #[inline] fn unexpected_range(_: Range) -> Self { UnexpectedParse::Unexpected } #[inline] fn unexpected_format(_: T) -> Self where T: fmt::Display, { UnexpectedParse::Unexpected } #[inline] fn expected_token(_: Item) -> Self { UnexpectedParse::Unexpected } #[inline] fn expected_range(_: Range) -> Self { UnexpectedParse::Unexpected } #[inline] fn expected_format(_: T) -> Self where T: fmt::Display, { UnexpectedParse::Unexpected } #[inline] fn message_format(_: T) -> Self where T: fmt::Display, { UnexpectedParse::Unexpected } #[inline] fn message_token(_: Item) -> Self { UnexpectedParse::Unexpected } #[inline] fn message_range(_: Range) -> Self { UnexpectedParse::Unexpected } #[inline] fn end_of_input() -> Self { UnexpectedParse::Eoi } #[inline] fn is_unexpected_end_of_input(&self) -> bool { *self == UnexpectedParse::Eoi } #[inline] fn into_other(self) -> T where T: StreamError, { match self { UnexpectedParse::Unexpected => T::unexpected_static_message("parse"), UnexpectedParse::Eoi => T::end_of_input(), } } } impl ParseError for UnexpectedParse where Position: Default, { type StreamError = Self; #[inline] fn empty(_position: Position) -> Self { UnexpectedParse::Unexpected } #[inline] fn from_error(_: Position, err: Self::StreamError) -> Self { err } fn position(&self) -> Position { Position::default() } #[inline] fn set_position(&mut self, _position: Position) {} #[inline] fn add(&mut self, err: Self::StreamError) { *self = match (*self, err) { (UnexpectedParse::Eoi, _) => UnexpectedParse::Eoi, (_, err) => err, }; } #[inline] fn set_expected(self_: &mut Tracked, info: Self::StreamError, f: F) where F: FnOnce(&mut Tracked), { f(self_); self_.error = info; } fn is_unexpected_end_of_input(&self) -> bool { *self == UnexpectedParse::Eoi } #[inline] fn into_other(self) -> T where T: ParseError, { T::from_error(Position::default(), StreamError::into_other(self)) } } impl ParseErrorInto for UnexpectedParse where Position: Default, { fn into_other_error(self) -> T where T: ParseError, Item2: From, Range2: From, Position2: From, { T::from_error( Position::default().into(), StreamErrorInto::::into_other_error(self), ) } } impl StreamErrorInto for UnexpectedParse { fn into_other_error(self) -> T where T: StreamError, Item2: From, Range2: From, { StreamError::into_other(self) } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum StringStreamError { UnexpectedParse, Eoi, CharacterBoundary, } pub(crate) const CHAR_BOUNDARY_ERROR_MESSAGE: &str = "unexpected slice on character boundary"; impl fmt::Display for StringStreamError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } #[cfg(feature = "std")] impl StdError for StringStreamError { fn description(&self) -> &str { self.as_str() } } impl StringStreamError { fn as_str(&self) -> &str { use self::StringStreamError::*; match *self { UnexpectedParse => "unexpected parse", Eoi => "unexpected end of input", CharacterBoundary => CHAR_BOUNDARY_ERROR_MESSAGE, } } } impl StreamError for StringStreamError { #[inline] fn unexpected_token(_: Item) -> Self { StringStreamError::UnexpectedParse } #[inline] fn unexpected_range(_: Range) -> Self { StringStreamError::UnexpectedParse } #[inline] fn unexpected_format(_msg: T) -> Self where T: fmt::Display, { StringStreamError::UnexpectedParse } #[inline] fn expected_token(_: Item) -> Self { StringStreamError::UnexpectedParse } #[inline] fn expected_range(_: Range) -> Self { StringStreamError::UnexpectedParse } #[inline] fn expected_format(_: T) -> Self where T: fmt::Display, { StringStreamError::UnexpectedParse } #[inline] fn message_format(_: T) -> Self where T: fmt::Display, { StringStreamError::UnexpectedParse } #[inline] fn message_token(_: Item) -> Self { StringStreamError::UnexpectedParse } #[inline] fn message_range(_: Range) -> Self { StringStreamError::UnexpectedParse } fn message_static_message(msg: &'static str) -> Self { if msg == CHAR_BOUNDARY_ERROR_MESSAGE { StringStreamError::CharacterBoundary } else { StringStreamError::UnexpectedParse } } #[inline] fn end_of_input() -> Self { StringStreamError::Eoi } #[inline] fn is_unexpected_end_of_input(&self) -> bool { *self == StringStreamError::Eoi } #[inline] fn into_other(self) -> T where T: StreamError, { let msg = match self { StringStreamError::CharacterBoundary => CHAR_BOUNDARY_ERROR_MESSAGE, StringStreamError::UnexpectedParse => "parse", StringStreamError::Eoi => return T::end_of_input(), }; T::unexpected_static_message(msg) } } impl ParseError for StringStreamError where Position: Default, { type StreamError = Self; #[inline] fn empty(_position: Position) -> Self { StringStreamError::UnexpectedParse } #[inline] fn from_error(_: Position, err: Self::StreamError) -> Self { err } fn position(&self) -> Position { Position::default() } #[inline] fn set_position(&mut self, _position: Position) {} #[inline] fn add(&mut self, err: Self::StreamError) { *self = match (*self, err) { (StringStreamError::Eoi, _) => StringStreamError::Eoi, (_, err) => err, }; } #[inline] fn set_expected(self_: &mut Tracked, info: Self::StreamError, f: F) where F: FnOnce(&mut Tracked), { f(self_); self_.error = info; } fn is_unexpected_end_of_input(&self) -> bool { *self == StringStreamError::Eoi } #[inline] fn into_other(self) -> T where T: ParseError, { T::from_error(Position::default(), StreamError::into_other(self)) } } impl ParseErrorInto for StringStreamError where Position: Default, { fn into_other_error(self) -> T where T: ParseError, Item2: From, Range2: From, Position2: From, { T::from_error( Position::default().into(), StreamErrorInto::::into_other_error(self), ) } } impl StreamErrorInto for StringStreamError { fn into_other_error(self) -> T where T: StreamError, Item2: From, Range2: From, { StreamError::into_other(self) } } /// Error wrapper which lets parsers track which parser in a sequence of sub-parsers has emitted /// the error. `Tracked::from` can be used to construct this and it should otherwise be /// ignored outside of combine. #[derive(Clone, PartialEq, Debug, Copy)] pub struct Tracked { /// The error returned pub error: E, #[doc(hidden)] pub offset: ErrorOffset, } impl From for Tracked { fn from(error: E) -> Self { Tracked { error, offset: ErrorOffset(1), } } } /// A `Result` type which has the committed status flattened into the result. /// Conversions to and from `std::result::Result` can be done using `result.into()` or /// `From::from(result)` #[derive(Clone, PartialEq, Debug, Copy)] pub enum ParseResult { /// The parser has succeeded and has committed to this parse. If a parser after this fails, /// other parser alternatives will not be attempted (`CommitErr` will be returned) CommitOk(T), /// The parser has succeeded and has not committed to this parse. If a parser after this fails, /// other parser alternatives will be attempted (`PeekErr` will be returned) PeekOk(T), /// The parser failed other parse alternatives will not be attempted. CommitErr(E), /// The parser failed but other parse alternatives may be attempted. PeekErr(Tracked), } impl ParseResult { #[inline] pub fn is_ok(&self) -> bool { match *self { CommitOk(_) | PeekOk(_) => true, CommitErr(_) | PeekErr(_) => false, } } #[inline] pub fn is_err(&self) -> bool { !self.is_ok() } pub fn as_ref(&self) -> ParseResult<&T, &E> { match *self { CommitOk(ref t) => CommitOk(t), PeekOk(ref t) => PeekOk(t), CommitErr(ref e) => CommitErr(e), PeekErr(ref e) => PeekErr(Tracked { error: &e.error, offset: e.offset, }), } } pub fn and_then(self, f: F) -> F::Output where F: FnOnce(T) -> ParseResult, { match self { CommitOk(t) => match f(t) { CommitOk(t2) | PeekOk(t2) => CommitOk(t2), PeekErr(e) => CommitErr(e.error), CommitErr(e) => CommitErr(e), }, PeekOk(t) => f(t), CommitErr(e) => CommitErr(e), PeekErr(e) => PeekErr(e), } } pub fn map_err(self, f: F) -> ParseResult where F: FnOnce(E) -> E2, { match self { CommitOk(t) => CommitOk(t), PeekOk(t) => PeekOk(t), CommitErr(e) => CommitErr(f(e)), PeekErr(e) => PeekErr(Tracked { error: f(e.error), offset: e.offset, }), } } pub fn map(self, f: F) -> ParseResult where F: FnOnce(T) -> T2, { match self { CommitOk(t) => CommitOk(f(t)), PeekOk(t) => PeekOk(f(t)), CommitErr(e) => CommitErr(e), PeekErr(e) => PeekErr(e), } } } impl ParseResult { pub fn into_result(self) -> StdParseResult2 { self.into() } } impl Into, Commit>>> for ParseResult { #[inline] fn into(self) -> Result, Commit>> { match self { CommitOk(t) => Ok(Commit::Commit(t)), PeekOk(t) => Ok(Commit::Peek(t)), CommitErr(e) => Err(Commit::Commit(e.into())), PeekErr(e) => Err(Commit::Peek(e)), } } } impl Into> for ParseResult { #[inline] fn into(self) -> StdParseResult2 { use self::ParseResult::*; match self { CommitOk(t) => Ok((t, Commit::Commit(()))), PeekOk(t) => Ok((t, Commit::Peek(()))), CommitErr(e) => Err(Commit::Commit(e.into())), PeekErr(e) => Err(Commit::Peek(e)), } } } impl From> for ParseResult { #[inline] fn from(result: StdParseResult2) -> ParseResult { use self::ParseResult::*; match result { Ok((t, Commit::Commit(()))) => CommitOk(t), Ok((t, Commit::Peek(()))) => PeekOk(t), Err(Commit::Commit(e)) => CommitErr(e.error), Err(Commit::Peek(e)) => PeekErr(e), } } } #[cfg(all(feature = "std", test))] mod tests_std { use crate::Parser; #[derive(Clone, PartialEq, Debug)] struct CloneOnly { s: String, } #[test] fn parse_clone_but_not_copy() { // This verifies we can parse slice references with an token type that is Clone but not Copy. let input = &[ CloneOnly { s: "x".to_string() }, CloneOnly { s: "y".to_string() }, ][..]; let result = crate::parser::range::take_while(|c: CloneOnly| c.s == "x").parse(input); assert_eq!( result, Ok(( &[CloneOnly { s: "x".to_string() }][..], &[CloneOnly { s: "y".to_string() }][..] )) ); } }