// Copyright 2017 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! An opinionated parser combinator library with a focus on fully abortable parsing and //! easy error handling. //! //! The approach to macro composition is heavily inspired by nom. It focuses on a simple //! API for combinators, and easy error handling. //! //! We have a number of macros that assist in the gneration or handling of each type //! of error. //! //! # Simple parsing of a url. //! //! ``` //! #[macro_use] //! extern crate abortable_parser; //! use abortable_parser::iter::StrIter; //! use abortable_parser::{Result, eoi, ascii_ws}; //! //! make_fn!(proto, //! do_each!( //! proto => until!(text_token!("://")), //! _ => must!(text_token!("://")), //! (proto) //! ) //! ); //! //! make_fn!(domain, //! do_each!( //! // domains do not start with a slash //! _ => peek!(not!(text_token!("/"))), //! domain => until!(either!( //! discard!(text_token!("/")), //! discard!(ascii_ws), //! eoi)), //! (domain) //! ) //! ); //! //! make_fn!(path, //! until!(either!(discard!(ascii_ws), eoi)) //! ); //! //! make_fn!(full_url, Option<&str>, Option<&str>)>, //! do_each!( //! protocol => proto, //! // If we match the protocol then we must have a domain. //! // This is an example of an unrecoverable parsing error so we //! // abort with the must! macro if it doesn't match. //! domain => must!(domain), //! path => optional!(path), //! (Some(protocol), Some(domain), path) //! ) //! ); //! //! make_fn!(relative_url, Option<&str>, Option<&str>)>, //! do_each!( //! _ => not!(either!(text_token!("//"), proto)), //! // we require a valid path for relative urls. //! path => path, //! (None, None, Some(path)) //! ) //! ); //! //! make_fn!(url, Option<&str>, Option<&str>)>, //! either!( //! full_url, //! relative_url, //! ) //! ); //! //! # fn main() { //! let iter = StrIter::new("http://example.com/some/path "); //! let result = url(iter); //! assert!(result.is_complete()); //! if let Result::Complete(_, (proto, domain, path)) = result { //! assert!(proto.is_some()); //! assert!(domain.is_some()); //! if let Some(domain) = domain { //! assert_eq!(domain, "example.com"); //! } //! assert!(path.is_some()); //! if let Some(path) = path { //! assert_eq!(path, "/some/path"); //! } //! } //! //! let bad_input = StrIter::new("http:///some/path"); //! let bad_result = url(bad_input); //! assert!(bad_result.is_abort()); //! # } //! ``` use std::fmt::Display; use std::iter::Iterator; use std::result; /// A trait for types that can have an offset as a count of processed items. pub trait Offsetable { fn get_offset(&self) -> usize; } impl Offsetable for usize { fn get_offset(&self) -> usize { return *self; } } pub trait Seekable { fn seek(&mut self, usize) -> usize; } /// Trait for Inputs that can track lines and columns in a text input. pub trait TextPositionTracker { fn line(&self) -> usize; fn column(&self) -> usize; } /// SpanRange encompasses the valid Ops::Range types for use with the Span trait. pub enum SpanRange { Range(std::ops::Range), RangeTo(std::ops::RangeTo), RangeFrom(std::ops::RangeFrom), RangeFull(std::ops::RangeFull), } /// An input that can provide a span of a range of the input. pub trait Span { fn span(&self, idx: SpanRange) -> O; } pub trait Peekable { fn peek_next(&self) -> Option; } /// A Cloneable Iterator that can report an offset as a count of processed Items. pub trait InputIter: Iterator + Clone + Offsetable {} /// The custom error type for use in `Result::{Fail, Abort}`. /// Stores a wrapped err that must implement Display as well as an offset and /// an optional cause. #[derive(Debug)] pub struct Error { msg: String, offset: usize, cause: Option>, } impl Error { /// Constructs a new Error with an offset and no cause. pub fn new>(msg: D, offset: &S) -> Self where S: Offsetable, { Error { msg: msg.into(), offset: offset.get_offset(), cause: None, } } /// Constructs a new Error with an offset and a cause. pub fn caused_by<'a, S, D: Into>(msg: D, offset: &'a S, cause: Box) -> Self where S: Offsetable, { Error { msg: msg.into(), offset: offset.get_offset(), cause: Some(cause), } } /// Returns the msg. pub fn get_msg<'a>(&'a self) -> String { format!("{}", &self.msg) } /// Returns `Some(cause)` if there is one, None otherwise. pub fn get_cause<'a>(&'a self) -> Option<&'a Error> { match self.cause { Some(ref e) => Some(e), None => None } } // Returns the offset at which this Error happened. pub fn get_offset(&self) -> usize { self.offset } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> result::Result<(), std::fmt::Error> { try!(write!(f, "{}", self.msg)); match self.cause { Some(ref c) => write!(f, "\n\tCaused By:{}", c), None => Ok(()), } } } impl std::error::Error for Error {} /// The result of a parsing attempt. #[derive(Debug)] pub enum Result { /// Complete represents a successful match. Complete(I, O), /// Incomplete indicates input ended before a match could be completed. /// It contains the offset at which the input ended before a match could be completed. Incomplete(usize), /// Fail represents a failed match. Fail(Error), /// Abort represents a match failure that the parser cannot recover from. Abort(Error), } impl Result { /// Returns true if the Result is Complete. pub fn is_complete(&self) -> bool { if let &Result::Complete(_, _) = self { return true; } return false; } /// Returns true if the Result is Incomoplete. pub fn is_incomplete(&self) -> bool { if let &Result::Incomplete(_) = self { return true; } return false; } /// Returns true if the Result is Fail. pub fn is_fail(&self) -> bool { if let &Result::Fail(_) = self { return true; } return false; } /// Returns true if the Result is Abort. pub fn is_abort(&self) -> bool { if let &Result::Abort(_) = self { return true; } return false; } } pub use combinators::*; pub use iter::SliceIter; pub use iter::StrIter; #[macro_use] pub mod combinators; pub mod iter; #[cfg(test)] mod integration_tests; #[cfg(test)] mod test;