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;