use super::token::{Token, TokenKind};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Trivia {
pub token_index: usize,
pub kind: TokenKind,
}
#[derive(Debug)]
pub struct PreParsedTokens {
pub token_indices: Vec<usize>,
pub leading_trivia_map: HashMap<usize, Vec<usize>>,
pub trailing_trivia_map: HashMap<usize, Vec<usize>>,
}
impl PreParsedTokens {
fn new() -> Self {
Self {
token_indices: Vec::new(),
leading_trivia_map: HashMap::new(),
trailing_trivia_map: HashMap::new(),
}
}
pub fn get_token<'a>(&self, idx: usize, tokens: &'a [Token]) -> Option<&'a Token> {
self.token_indices.get(idx).and_then(|&i| tokens.get(i))
}
pub fn get_leading_trivia<'a>(&self, idx: usize, tokens: &'a [Token]) -> Vec<&'a Token> {
self.leading_trivia_map
.get(&idx)
.map(|indices| indices.iter().filter_map(|&i| tokens.get(i)).collect())
.unwrap_or_default()
}
pub fn get_trailing_trivia<'a>(&self, idx: usize, tokens: &'a [Token]) -> Vec<&'a Token> {
self.trailing_trivia_map
.get(&idx)
.map(|indices| indices.iter().filter_map(|&i| tokens.get(i)).collect())
.unwrap_or_default()
}
}
impl Default for PreParsedTokens {
fn default() -> Self {
Self::new()
}
}
pub fn preparse(tokens: &[Token]) -> PreParsedTokens {
let mut result = PreParsedTokens::new();
let mut pending_trivia = Vec::new();
let mut last_was_linebreak = false;
let mut last_token_idx: Option<usize> = None;
for (i, token) in tokens.iter().enumerate() {
if token.is_trivia() {
pending_trivia.push(i);
if token.kind == TokenKind::LineBreak {
if let Some(last_idx) = last_token_idx {
result
.trailing_trivia_map
.entry(last_idx)
.or_default()
.append(&mut pending_trivia);
} else {
pending_trivia.clear();
}
last_was_linebreak = true;
} else {
last_was_linebreak = false;
}
} else if token.kind != TokenKind::Eof {
let current_idx = result.token_indices.len();
if !pending_trivia.is_empty() {
if last_was_linebreak || last_token_idx.is_none() {
result
.leading_trivia_map
.entry(current_idx)
.or_default()
.append(&mut pending_trivia);
} else {
if let Some(last_idx) = last_token_idx {
result
.trailing_trivia_map
.entry(last_idx)
.or_default()
.append(&mut pending_trivia);
}
}
}
result.token_indices.push(i);
last_token_idx = Some(current_idx);
last_was_linebreak = false;
}
}
if !pending_trivia.is_empty()
&& let Some(last_idx) = last_token_idx
{
result
.trailing_trivia_map
.entry(last_idx)
.or_default()
.extend(pending_trivia);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::parser::tokenizer::tokenize;
#[test]
fn test_preparse_simple() {
let source = "fn dsp() { 42 }";
let tokens = tokenize(source);
let preparsed = preparse(&tokens);
assert_eq!(preparsed.token_indices.len(), 7);
let first_token = preparsed.get_token(0, &tokens).unwrap();
assert_eq!(first_token.kind, TokenKind::Function);
}
#[test]
fn test_preparse_with_comments() {
let source = "fn // comment\ndsp";
let tokens = tokenize(source);
let preparsed = preparse(&tokens);
assert_eq!(preparsed.token_indices.len(), 2);
let trailing = preparsed.get_trailing_trivia(0, &tokens);
assert!(trailing.len() >= 2);
}
#[test]
fn test_preparse_leading_trivia() {
let source = "// header comment\nfn dsp";
let tokens = tokenize(source);
let preparsed = preparse(&tokens);
let first_token = preparsed.get_token(0, &tokens).unwrap();
assert_eq!(first_token.kind, TokenKind::Function);
let leading = preparsed.get_leading_trivia(0, &tokens);
assert!(leading.is_empty()); }
#[test]
fn test_preparse_trailing_trivia() {
let source = "fn dsp // inline comment\n";
let tokens = tokenize(source);
let preparsed = preparse(&tokens);
let dsp_idx = 1; let trailing = preparsed.get_trailing_trivia(dsp_idx, &tokens);
assert!(!trailing.is_empty());
}
#[test]
fn test_preparse_multiline_comment() {
let source = "fn /* comment\nspanning\nlines */ dsp";
let tokens = tokenize(source);
let preparsed = preparse(&tokens);
assert_eq!(preparsed.token_indices.len(), 2);
let trailing = preparsed.get_trailing_trivia(0, &tokens);
assert!(
trailing
.iter()
.any(|t| t.kind == TokenKind::MultiLineComment)
);
}
}