conftaal/parse/
end.rs

1use std::fmt::Write;
2
3use super::consume::Consume;
4use super::error::{Error, Message, error};
5use super::whitespace::skip_whitespace;
6
7/// Determines until what point should be parsed.
8/// An value of this type is given to the `parse_*` functions.
9#[derive(Clone, Copy)]
10pub enum End<'a> {
11
12	/// Don't stop till the end of the file is earched.
13	EndOfFile,
14
15	/// Only stop when this specific string is found.
16	Specific(&'static str),
17
18	/// Only stop when these brackets are matched.
19	///
20	/// `MatchingBracket("(", ")")` looks for a `")"` to match the `"("`.
21	MatchingBracket(&'a str, &'static str),
22
23	/// Stop at either a comma, semicolon, or newline.
24	///
25	/// Values in conftaal objects use this mode.
26	ElementEnd, // , or ; or \n
27}
28
29impl<'a> End<'a> {
30
31	fn consume(&self, source: &mut &[u8]) -> bool {
32		match self {
33			End::EndOfFile => source.is_empty(),
34			End::Specific(s) | End::MatchingBracket(_, s) => source.consume(s).is_some(),
35			End::ElementEnd => source.consume_one_of(",;\n").is_some(),
36		}
37	}
38
39	fn matches(&self, mut source: &[u8]) -> bool {
40		self.consume(&mut source)
41	}
42
43	/// An human-readable description of what this `End` matches.
44	/// Useful for in error messages.
45	pub fn description(&self) -> String {
46		match self {
47			End::EndOfFile => "end of file".to_string(),
48			End::Specific(s) | End::MatchingBracket(_, s) => format!("`{}'", s),
49			End::ElementEnd => "newline or `,` or `;'".to_string(),
50		}
51	}
52
53	fn error(&self, source: &'a [u8]) -> Error<'a> {
54		let mut e = error(&source[..0], format!("expected {}", self.description()));
55		if let &End::MatchingBracket(b, _) = self {
56			e.notes = vec![Message{
57				message: format!("... to match this `{}'", b),
58				location: Some(b.as_bytes())
59			}];
60		}
61		e
62	}
63
64	/// Check if we reached this `End` yet.
65	///
66	/// First eats whitespace, and then tries to match.
67	///
68	/// If the end of the file is reached, and this is not a `End::EndOfFile`,
69	/// an error is given.
70	pub fn parse(&self, source: &mut &'a [u8]) -> Result<bool, Error<'a>> {
71		skip_whitespace(source, match self { &End::ElementEnd => false, _ => true });
72		let did_match = self.consume(source);
73		if !did_match && source.is_empty() {
74			Err(self.error(*source))
75		} else {
76			Ok(did_match)
77		}
78	}
79
80	pub fn or_before(self, or_before: Self) -> OptionalEnd<'a> {
81		OptionalEnd{ end: self, or_before: Some(or_before) }
82	}
83
84	pub fn as_optional(self) -> OptionalEnd<'a> {
85		OptionalEnd{ end: self, or_before: None }
86	}
87}
88
89pub struct OptionalEnd<'a> {
90	pub end: End<'a>,
91	pub or_before: Option<End<'a>>,
92}
93
94impl<'a> OptionalEnd<'a> {
95
96	pub fn parse(&self, source: &mut &'a [u8]) -> Result<bool, Error<'a>> {
97		skip_whitespace(source, match self.end { End::ElementEnd => false, _ => true });
98		let did_match = self.end.consume(source) || self.or_before.map(|e| e.matches(*source)).unwrap_or(false);
99		if !did_match && source.is_empty() {
100			Err(self.error(*source))
101		} else {
102			Ok(did_match)
103		}
104	}
105
106	pub fn description(&self) -> String {
107		let mut desc = self.end.description();
108		if let Some(e) = self.or_before {
109			write!(&mut desc, " or {}", e.description()).unwrap();
110		}
111		desc
112	}
113
114	fn error(&self, source: &'a [u8]) -> Error<'a> {
115		match self.or_before {
116			None => self.end.error(source),
117			Some(_) => error(&source[..0], format!("expected {}", self.description())),
118		}
119	}
120}
121
122//TODO: Add tests.