// using String.push_str(token.to_string()) is simply way too slow pubfn push_to_string(&self, target: &mut String) { matchself.as_static_str() {
Some(s) => { target.push_str(s); }
None => { match *self {
Token::Character(c) | Token::Whitespace(c) => target.push(c),
_ => unreachable!()
}
}
}
}
/// Returns `true` if this token contains data that can be interpreted /// as a part of the text. Surprisingly, this also means '>' and '=' and '"' and "'" and '-->'. #[inline] pubfn contains_char_data(&self) -> bool { match *self {
Token::Whitespace(_) | Token::Chunk(_) | Token::Character(_) | Token::CommentEnd |
Token::TagEnd | Token::EqualsSign | Token::DoubleQuote | Token::SingleQuote | Token::CDataEnd |
Token::ProcessingInstructionEnd | Token::EmptyTagEnd => true,
_ => false
}
}
/// Returns `true` if this token corresponds to a white space character. #[inline] pubfn is_whitespace(&self) -> bool { match *self {
Token::Whitespace(_) => true,
_ => false
}
}
}
enum State { /// Triggered on '<'
TagStarted, /// Triggered on '<!'
CommentOrCDataOrDoctypeStarted, /// Triggered on '<!-'
CommentStarted, /// Triggered on '<!D' up to '<!DOCTYPE'
DoctypeStarted(DoctypeStartedSubstate), /// Triggered after DoctypeStarted to handle sub elements
DoctypeFinishing(u8), /// Triggered on '<![' up to '<![CDATA'
CDataStarted(CDataStartedSubstate), /// Triggered on '?'
ProcessingInstructionClosing, /// Triggered on '/'
EmptyTagClosing, /// Triggered on '-' up to '--'
CommentClosing(ClosingSubstate), /// Triggered on ']' up to ']]'
CDataClosing(ClosingSubstate), /// Default state
Normal
}
#[derive(Copy, Clone)] enum ClosingSubstate {
First, Second
}
/// `Result` represents lexing result. It is either a token or an error message. pubtype Result = result::Result<Option<Token>, Error>;
/// Helps to set up a dispatch table for lexing large unambigous tokens like /// `<![CDATA[` or `<!DOCTYPE `.
macro_rules! dispatch_on_enum_state(
($_self:ident, $s:expr, $c:expr, $is:expr,
$($st:ident; $stc:expr ; $next_st:ident ; $chunk:expr),+;
$end_st:ident ; $end_c:expr ; $end_chunk:expr ; $e:expr) => ( match $s {
$(
$st => match $c {
$stc => $_self.move_to($is($next_st)),
_ => $_self.handle_error($chunk, $c)
},
)+
$end_st => match $c {
$end_c => $e,
_ => $_self.handle_error($end_chunk, $c)
}
}
)
);
/// `Lexer` is a lexer for XML documents, which implements pull API. /// /// Main method is `next_token` which accepts an `std::io::Read` instance and /// tries to read the next lexeme from it. /// /// When `skip_errors` flag is set, invalid lexemes will be returned as `Chunk`s. /// When it is not set, errors will be reported as `Err` objects with a string message. /// By default this flag is not set. Use `enable_errors` and `disable_errors` methods /// to toggle the behavior. pubstruct Lexer {
pos: TextPosition,
head_pos: TextPosition,
char_queue: VecDeque<char>,
st: State,
skip_errors: bool,
inside_comment: bool,
inside_token: bool,
eof_handled: bool
}
impl Position for Lexer { #[inline] /// Returns the position of the last token produced by the lexer fn position(&self) -> TextPosition { self.pos }
}
/// Enables error handling so `next_token` will return `Some(Err(..))` /// upon invalid lexeme. #[inline] pubfn enable_errors(&mutself) { self.skip_errors = false; }
/// Disables error handling so `next_token` will return `Some(Chunk(..))` /// upon invalid lexeme with this lexeme content. #[inline] pubfn disable_errors(&mutself) { self.skip_errors = true; }
/// Enables special handling of some lexemes which should be done when we're parsing comment /// internals. #[inline] pubfn inside_comment(&mutself) { self.inside_comment = true; }
/// Disables the effect of `inside_comment()` method. #[inline] pubfn outside_comment(&mutself) { self.inside_comment = false; }
/// Reset the eof handled flag of the lexer. #[inline] pubfn reset_eof_handled(&mutself) { self.eof_handled = false; }
/// Tries to read the next token from the buffer. /// /// It is possible to pass different instaces of `BufReader` each time /// this method is called, but the resulting behavior is undefined in this case. /// /// Return value: /// * `Err(reason) where reason: reader::Error` - when an error occurs; /// * `Ok(None)` - upon end of stream is reached; /// * `Ok(Some(token)) where token: Token` - in case a complete-token has been read from the stream. pubfn next_token<B: Read>(&mutself, b: &mut B) -> Result { // Already reached end of buffer ifself.eof_handled { return Ok(None);
}
if !self.inside_token { self.pos = self.head_pos; self.inside_token = true;
}
// Check if we have saved a char or two for ourselves whilelet Some(c) = self.char_queue.pop_front() { matchtry!(self.read_next_token(c)) {
Some(t) => { self.inside_token = false; return Ok(Some(t));
}
None => {} // continue
}
}
loop { // TODO: this should handle multiple encodings let c = matchtry!(util::next_char_from(b)) {
Some(c) => c, // got next char
None => break, // nothing to read left
};
#[inline] fn read_next_token(&mutself, c: char) -> Result { let res = self.dispatch_char(c); ifself.char_queue.is_empty() { if c == '\n' { self.head_pos.new_line();
} else { self.head_pos.advance(1);
}
}
res
}
/// State used while awaiting the closing bracket for the <!DOCTYPE tag fn doctype_finishing(&mutself, c: char, d: u8) -> Result { match c { '<' => self.move_to(State::DoctypeFinishing(d + 1)), '>'if d == 1 => self.move_to_with(State::Normal, Token::TagEnd), '>' => self.move_to(State::DoctypeFinishing(d - 1)),
_ => Ok(None),
}
}
/// Encountered '?' fn processing_instruction_closing(&mutself, c: char) -> Result { match c { '>' => self.move_to_with(State::Normal, Token::ProcessingInstructionEnd),
_ => self.move_to_with_unread(State::Normal, &[c], Token::Character('?')),
}
}
/// Encountered '/' fn empty_element_closing(&mutself, c: char) -> Result { match c { '>' => self.move_to_with(State::Normal, Token::EmptyTagEnd),
_ => self.move_to_with_unread(State::Normal, &[c], Token::Character('/')),
}
}
/// Encountered '-' fn comment_closing(&mutself, c: char, s: ClosingSubstate) -> Result { match s {
ClosingSubstate::First => match c { '-' => self.move_to(State::CommentClosing(ClosingSubstate::Second)),
_ => self.move_to_with_unread(State::Normal, &[c], Token::Character('-'))
},
ClosingSubstate::Second => match c { '>' => self.move_to_with(State::Normal, Token::CommentEnd), // double dash not followed by a greater-than is a hard error inside comment
_ ifself.inside_comment => self.handle_error("--", c), // nothing else except comment closing starts with a double dash, and comment // closing can never be after another dash, and also we're outside of a comment, // therefore it is safe to push only the last read character to the list of unread // characters and pass the double dash directly to the output
_ => self.move_to_with_unread(State::Normal, &[c], Token::Chunk("--"))
}
}
}
/// Encountered ']' fn cdata_closing(&mutself, c: char, s: ClosingSubstate) -> Result { match s {
ClosingSubstate::First => match c { ']' => self.move_to(State::CDataClosing(ClosingSubstate::Second)),
_ => self.move_to_with_unread(State::Normal, &[c], Token::Character(']'))
},
ClosingSubstate::Second => match c { '>' => self.move_to_with(State::Normal, Token::CDataEnd),
_ => self.move_to_with_unread(State::Normal, &[']', c], Token::Character(']'))
}
}
}
}
#[cfg(test)] mod tests { use common::{Position}; use std::io::{BufReader, Cursor};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.