abortable_parser/
lib.rs

1// Copyright 2017 Jeremy Wall <jeremy@marzhillstudios.com>
2//
3//  Licensed under the Apache License, Version 2.0 (the "License");
4//  you may not use this file except in compliance with the License.
5//  You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14
15//! An opinionated parser combinator library with a focus on fully abortable parsing and
16//! easy error handling.
17//!
18//! The approach to macro composition is heavily inspired by nom. It focuses on a simple
19//! API for combinators, and easy error handling.
20//!
21//! We have a number of macros that assist in the gneration or handling of each type
22//! of error.
23//!
24//! # Simple parsing of a url.
25//!
26//! ```
27//! #[macro_use]
28//! extern crate abortable_parser;
29//! use abortable_parser::iter::StrIter;
30//! use abortable_parser::{Result, eoi, ascii_ws};
31//!
32//! make_fn!(proto<StrIter, &str>,
33//!     do_each!(
34//!         proto => until!(text_token!("://")),
35//!         _ => must!(text_token!("://")),
36//!         (proto)
37//!     )
38//! );
39//!
40//! make_fn!(domain<StrIter, &str>,
41//!     do_each!(
42//!         // domains do not start with a slash
43//!         _ => peek!(not!(text_token!("/"))),
44//!         domain => until!(either!(
45//!             discard!(text_token!("/")),
46//!             discard!(ascii_ws),
47//!             eoi)),
48//!         (domain)
49//!     )
50//! );
51//!
52//! make_fn!(path<StrIter, &str>,
53//!      until!(either!(discard!(ascii_ws), eoi))
54//! );
55//!
56//! make_fn!(full_url<StrIter, (Option<&str>, Option<&str>, Option<&str>)>,
57//!     do_each!(
58//!         protocol => proto,
59//!         // If we match the protocol then we must have a domain.
60//!         // This is an example of an unrecoverable parsing error so we
61//!         // abort with the must! macro if it doesn't match.
62//!         domain => must!(domain),
63//!         path => optional!(path),
64//!         (Some(protocol), Some(domain), path)
65//!     )
66//! );
67//!
68//! make_fn!(relative_url<StrIter, (Option<&str>, Option<&str>, Option<&str>)>,
69//!     do_each!(
70//!         _ => not!(either!(text_token!("//"), proto)),
71//!         // we require a valid path for relative urls.
72//!         path => path,
73//!         (None, None, Some(path))
74//!     )
75//! );
76//!
77//! make_fn!(url<StrIter, (Option<&str>, Option<&str>, Option<&str>)>,
78//!     either!(
79//!         full_url,
80//!         relative_url,
81//!     )
82//! );
83//!
84//! # fn main() {
85//! let iter = StrIter::new("http://example.com/some/path ");
86//! let result = url(iter);
87//! assert!(result.is_complete());
88//! if let Result::Complete(_, (proto, domain, path)) = result {
89//!     assert!(proto.is_some());
90//!     assert!(domain.is_some());
91//!     if let Some(domain) = domain {
92//!         assert_eq!(domain, "example.com");
93//!     }
94//!     assert!(path.is_some());
95//!     if let Some(path) = path {
96//!         assert_eq!(path, "/some/path");
97//!     }
98//! }
99//!
100//! let bad_input = StrIter::new("http:///some/path");
101//! let bad_result = url(bad_input);
102//! assert!(bad_result.is_abort());
103//! # }
104//! ```
105use std::fmt::{Debug, Display};
106use std::iter::Iterator;
107use std::result;
108
109/// A trait for types that can have an offset as a count of processed items.
110pub trait Offsetable {
111    fn get_offset(&self) -> usize;
112}
113
114impl Offsetable for usize {
115    fn get_offset(&self) -> usize {
116        return *self;
117    }
118}
119
120pub trait Seekable {
121    fn seek(&mut self, u: usize) -> usize;
122}
123
124/// Trait for Inputs that can report current lines and columns in a text input.
125pub trait Positioned {
126    fn line(&self) -> usize;
127    fn column(&self) -> usize;
128}
129
130/// SpanRange encompasses the valid Ops::Range types for use with the Span trait.
131pub enum SpanRange {
132    Range(std::ops::Range<usize>),
133    RangeTo(std::ops::RangeTo<usize>),
134    RangeFrom(std::ops::RangeFrom<usize>),
135    RangeFull(std::ops::RangeFull),
136}
137
138/// An input that can provide a span of a range of the input.
139pub trait Span<O> {
140    fn span(&self, idx: SpanRange) -> O;
141}
142
143pub trait Peekable<O> {
144    fn peek_next(&self) -> Option<O>;
145}
146
147/// A Cloneable Iterator that can report an offset as a count of processed Items.
148pub trait InputIter: Iterator + Clone + Offsetable {
149    fn curr(&self) -> Self::Item;
150}
151
152/// The custom error type for use in `Result::{Fail, Abort}`.
153/// Stores a wrapped err that must implement Display as well as an offset and
154/// an optional cause.
155#[derive(Debug, Clone)]
156pub struct Error<C> {
157    msg: String,
158    cause: Option<Box<Error<C>>>,
159    context: Box<C>,
160}
161
162impl<C> Error<C> {
163    /// Constructs a new Error with an offset and no cause.
164    pub fn new<D: Into<String>>(msg: D, ctx: Box<C>) -> Self {
165        Error {
166            msg: msg.into(),
167            cause: None,
168            context: ctx,
169        }
170    }
171
172    /// Constructs a new Error with an offset and a cause.
173    pub fn caused_by<'a, D: Into<String>>(msg: D, cause: Box<Self>, ctx: Box<C>) -> Self {
174        Error {
175            msg: msg.into(),
176            cause: Some(cause),
177            context: ctx,
178        }
179    }
180
181    /// Returns the msg.
182    pub fn get_msg<'a>(&'a self) -> &str {
183        &self.msg
184    }
185
186    /// Returns `Some(cause)` if there is one, None otherwise.
187    pub fn get_cause<'a>(&'a self) -> Option<&'a Error<C>> {
188        match self.cause {
189            Some(ref e) => Some(e),
190            None => None,
191        }
192    }
193
194    pub fn get_context(&self) -> &C {
195        self.context.as_ref()
196    }
197}
198
199impl<C: Offsetable> Offsetable for Error<C> {
200    // Returns the offset at which this Error happened.
201    fn get_offset(&self) -> usize {
202        self.context.get_offset()
203    }
204}
205
206impl<C: Offsetable> Display for Error<C> {
207    fn fmt(&self, f: &mut std::fmt::Formatter) -> result::Result<(), std::fmt::Error> {
208        write!(f, "{}", self.msg)?;
209        match self.cause {
210            Some(ref c) => write!(f, "\n\tCaused By:{}", c),
211            None => Ok(()),
212        }
213    }
214}
215
216impl<C: Offsetable + Debug> std::error::Error for Error<C> {}
217
218/// The result of a parsing attempt.
219#[derive(Debug)]
220pub enum Result<I: InputIter, O> {
221    /// Complete represents a successful match.
222    Complete(I, O),
223    /// Incomplete indicates input ended before a match could be completed.
224    /// It contains the offset at which the input ended before a match could be completed.
225    Incomplete(I),
226    /// Fail represents a failed match.
227    Fail(Error<I>),
228    /// Abort represents a match failure that the parser cannot recover from.
229    Abort(Error<I>),
230}
231
232impl<I: InputIter, O> Result<I, O> {
233    /// Returns true if the Result is Complete.
234    pub fn is_complete(&self) -> bool {
235        if let &Result::Complete(_, _) = self {
236            return true;
237        }
238        return false;
239    }
240
241    /// Returns true if the Result is Incomoplete.
242    pub fn is_incomplete(&self) -> bool {
243        if let &Result::Incomplete(_) = self {
244            return true;
245        }
246        return false;
247    }
248
249    /// Returns true if the Result is Fail.
250    pub fn is_fail(&self) -> bool {
251        if let &Result::Fail(_) = self {
252            return true;
253        }
254        return false;
255    }
256
257    /// Returns true if the Result is Abort.
258    pub fn is_abort(&self) -> bool {
259        if let &Result::Abort(_) = self {
260            return true;
261        }
262        return false;
263    }
264}
265
266pub use combinators::*;
267pub use iter::SliceIter;
268pub use iter::StrIter;
269
270#[macro_use]
271pub mod combinators;
272pub mod iter;
273
274#[cfg(test)]
275mod integration_tests;
276#[cfg(test)]
277mod test;