blob: a12647bc570ed23289e8591cea51f59f5b7759db [file] [edit]
//! Parser that can take Rust source and generates a sequence of tokens.
use super::{Node, ParseError};
use crate::coverage::Coverage;
use crate::parser::{SourceIndex, parse_production};
use grammar::{ExpressionKind, Grammar, Production};
use tracing::debug;
#[derive(Clone)]
pub struct Tokens {
pub tokens: Vec<Node>,
/// Byte range of the shebang.
///
/// The reference lexer is the only tool that sets this.
pub shebang: Option<Node>,
/// Byte range of the frontmatter.
///
/// The reference lexer is the only tool that sets this.
pub frontmatter: Option<Node>,
}
pub fn tokenize(
grammar: &Grammar,
coverage: &mut Coverage,
original_src: &str,
) -> Result<Tokens, ParseError> {
let (normalized_src, removed_indices) = normalize_crlf(original_src);
tokenize_normalized(grammar, coverage, &normalized_src)
.map(|mut tokens| {
for token in &mut tokens.tokens {
adjust_node(&removed_indices, token);
}
if let Some(shebang) = &mut tokens.shebang {
adjust_node(&removed_indices, shebang);
}
if let Some(frontmatter) = &mut tokens.frontmatter {
adjust_node(&removed_indices, frontmatter);
}
tokens
})
.map_err(|mut err| {
err.byte_offset = map_offset(&removed_indices, err.byte_offset);
err
})
}
fn normalize_crlf(src: &str) -> (String, Vec<usize>) {
let mut normalized_src = String::with_capacity(src.len());
let mut removed_indices = Vec::new();
let mut chars = src.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\r'
&& let Some(&'\n') = chars.peek()
{
removed_indices.push(normalized_src.len());
continue;
}
normalized_src.push(ch);
}
(normalized_src, removed_indices)
}
fn map_offset(removed_indices: &[usize], offset: usize) -> usize {
offset + removed_indices.partition_point(|&x| x < offset)
}
/// Adjusts the node range for CRLF normalization so that the range matches
/// the original source with the carriage returns.
fn adjust_node(removed_indices: &[usize], node: &mut Node) {
node.range.start = map_offset(removed_indices, node.range.start);
node.range.end = map_offset(removed_indices, node.range.end);
for child in &mut node.children.0 {
adjust_node(removed_indices, child);
}
}
/// Tokenize source after it has been normalized.
fn tokenize_normalized(
grammar: &Grammar,
coverage: &mut Coverage,
src: &str,
) -> Result<Tokens, ParseError> {
let top_prods = get_top_prods(grammar);
let mut index = SourceIndex(0);
// Remove BOM
if src.starts_with('\u{FEFF}') {
index.0 += 3;
}
let shebang;
(shebang, index) = parse_shebang(grammar, coverage, src, index)?;
let frontmatter;
(frontmatter, index) = parse_frontmatter(grammar, coverage, src, index)?;
let tokens = parse_tokens(grammar, coverage, &top_prods, src, index)?;
validate_delimiters_balanced(&tokens, src)?;
debug!("lexing complete");
let tokens = Tokens {
tokens,
shebang,
frontmatter,
};
Ok(tokens)
}
/// Returns the [`Production`]s that correspond to top-level tokens.
fn get_top_prods(grammar: &Grammar) -> Vec<&Production> {
let mut top_prods = Vec::new();
let mut collect = |name| {
let prod = grammar.productions.get(name).unwrap();
let ExpressionKind::Alt(es) = &prod.expression.kind else {
panic!("expected alts");
};
for e in es {
let nt = match &e.kind {
ExpressionKind::Sequence(es) => {
let seq: Vec<_> = es
.iter()
.filter_map(|e| match &e.kind {
ExpressionKind::Nt(nt) => Some(nt),
ExpressionKind::Break(_) | ExpressionKind::Comment(_) => None,
kind => panic!("unexpected kind {kind:?}"),
})
.collect();
assert_eq!(seq.len(), 1);
seq[0]
}
ExpressionKind::Nt(nt) => nt,
kind => panic!("unexpected kind {kind:?}"),
};
top_prods.push(grammar.productions.get(nt).unwrap());
}
};
collect("COMMENT");
collect("Token");
top_prods
}
fn parse_shebang(
grammar: &Grammar,
coverage: &mut Coverage,
src: &str,
index: SourceIndex,
) -> Result<(Option<Node>, SourceIndex), ParseError> {
let shebang = grammar.productions.get("SHEBANG").unwrap();
if let Some((node, next_index)) = parse_production(grammar, coverage, shebang, &src, index)? {
Ok((Some(node), next_index))
} else {
Ok((None, index))
}
}
fn parse_frontmatter(
grammar: &Grammar,
coverage: &mut Coverage,
src: &str,
index: SourceIndex,
) -> Result<(Option<Node>, SourceIndex), ParseError> {
let frontmatter = grammar.productions.get("FRONTMATTER").unwrap();
if let Some((node, next_index)) = parse_production(grammar, coverage, frontmatter, &src, index)
.map_err(|e| ParseError {
message: format!("invalid frontmatter: {}", e.message),
byte_offset: e.byte_offset,
})?
{
Ok((Some(node), next_index))
} else {
Ok((None, index))
}
}
/// Performs the actual parsing of all the tokens in the source.
fn parse_tokens(
grammar: &Grammar,
coverage: &mut Coverage,
top_prods: &[&Production],
src: &str,
mut index: SourceIndex,
) -> Result<Vec<Node>, ParseError> {
let mut tokens = Vec::new();
let whitespace = grammar.productions.get("WHITESPACE").unwrap();
while index.0 < src.len() {
if let Some((_node, next_index)) =
parse_production(grammar, coverage, whitespace, &src, index)?
{
index = next_index;
continue;
}
let mut matched_token = None;
for token_prod in top_prods {
debug!("try top-level token `{}`", token_prod.name);
if let Some((node, next_index)) =
parse_production(grammar, coverage, token_prod, &src, index)?
&& node.byte_len() > 0
{
index = next_index;
matched_token = Some(node);
break;
}
}
match matched_token {
Some(mut node) => {
normalize_line_doc(&mut node, src);
tokens.push(node);
}
None => {
return Err(ParseError {
byte_offset: index.0,
message: String::from("no tokens matched"),
});
}
}
}
Ok(tokens)
}
fn validate_delimiters_balanced(tokens: &[Node], src: &str) -> Result<(), ParseError> {
let mut stack = Vec::new();
for token in tokens {
let text = &src[token.range.clone()];
match text {
"(" | "[" | "{" => stack.push((text, token.range.start)),
")" => {
if stack.pop().map(|(s, _)| s) != Some("(") {
return Err(ParseError {
byte_offset: token.range.start,
message: "unbalanced `)`".to_string(),
});
}
}
"]" => {
if stack.pop().map(|(s, _)| s) != Some("[") {
return Err(ParseError {
byte_offset: token.range.start,
message: "unbalanced `]`".to_string(),
});
}
}
"}" => {
if stack.pop().map(|(s, _)| s) != Some("{") {
return Err(ParseError {
byte_offset: token.range.start,
message: "unbalanced `}`".to_string(),
});
}
}
_ => {}
}
}
if let Some((_, offset)) = stack.pop() {
return Err(ParseError {
byte_offset: offset,
message: "unclosed delimiter".to_string(),
});
}
Ok(())
}
/// Fix line doc comment range.
///
/// The Reference models line doc comments as *content* followed by a
/// linefeed. However, rustc and proc-macro2 model it as everything excluding
/// the linefeed. For convenience, this normalizes the range so that it
/// matches the other tools.
///
/// A real implementation using the Reference lexer would extract the content
/// from `LINE_DOC_COMMENT_CONTENT`, which does not include the linefeed.
fn normalize_line_doc(node: &mut Node, src: &str) {
if matches!(node.name.as_str(), "INNER_LINE_DOC" | "OUTER_LINE_DOC")
&& src[node.range.clone()].ends_with('\n')
{
node.range.end -= 1;
}
}