| //! Parser that can take Rust source and generate a parse tree. | |
| use super::{Node, ParseError}; | |
| use crate::coverage::Coverage; | |
| use crate::lexer::tokenize; | |
| use crate::parser::parse_production; | |
| use crate::parser::{Source, SourceIndex}; | |
| use grammar::Grammar; | |
| use std::ops::Range; | |
| struct TokenSource<'src> { | |
| src: &'src str, | |
| tokens: Vec<Node>, | |
| } | |
| impl Source for TokenSource<'_> { | |
| fn get_substring(&self, offset: SourceIndex, bytes: usize) -> Option<(&str, Range<usize>)> { | |
| self.tokens.get(offset.0).and_then(|t| { | |
| let s = &self.src[t.range.clone()]; | |
| if !s.len() == bytes { | |
| None | |
| } else { | |
| Some((s, t.range.clone())) | |
| } | |
| }) | |
| } | |
| fn get_element(&self, offset: SourceIndex) -> Option<(&str, Range<usize>)> { | |
| self.tokens.get(offset.0).map(|t| { | |
| let s = &self.src[t.range.clone()]; | |
| (s, t.range.clone()) | |
| }) | |
| } | |
| fn len(&self) -> SourceIndex { | |
| SourceIndex(self.tokens.len()) | |
| } | |
| fn advance(&self, index: SourceIndex, bytes: usize) -> SourceIndex { | |
| let token = &self.tokens[index.0]; | |
| if token.byte_len() != bytes { | |
| panic!("advancing {bytes} at {index:?} is not equal to {token:?}"); | |
| } | |
| SourceIndex(index.0 + 1) | |
| } | |
| fn get_node(&self, index: SourceIndex) -> Option<&Node> { | |
| self.tokens.get(index.0) | |
| } | |
| fn index_to_bytes(&self, index: SourceIndex) -> usize { | |
| if index.0 == self.tokens.len() { | |
| self.tokens[index.0 - 1].range.end | |
| } else { | |
| self.tokens[index.0].range.start | |
| } | |
| } | |
| } | |
| /// Parse Rust source for the given named production, and return a [`Node`] tree. | |
| pub fn parse(grammar: &Grammar, src: &str, production: &str) -> Result<Node, ParseError> { | |
| let mut coverage = Coverage::default(); | |
| let krate = grammar.productions.get(production).unwrap(); | |
| let tokens = tokenize(grammar, &mut coverage, src)?; | |
| // Strip comments. | |
| let tokens = tokens | |
| .tokens | |
| .into_iter() | |
| .filter(|token| !matches!(token.name.as_str(), "LINE_COMMENT" | "BLOCK_COMMENT")) | |
| .collect(); | |
| let token_source = TokenSource { src, tokens }; | |
| match parse_production(grammar, &mut coverage, krate, &token_source, SourceIndex(0))? { | |
| Some((node, next_index)) => { | |
| if next_index < token_source.len() { | |
| return Err(ParseError { | |
| message: format!("{production} production failed to parse all tokens"), | |
| byte_offset: token_source.index_to_bytes(next_index), | |
| }); | |
| } | |
| Ok(node) | |
| } | |
| None => panic!("input did not match {production}"), | |
| } | |
| } |