blob: 75bfc7bea3e224e9f81bbdc326df96ad63f10991 [file] [edit]
//! 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}"),
}
}