use std::borrow::Cow;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Position {
LineNumberReference {
start: usize,
end: usize,
line_number: usize,
},
LineNumber {
content: Cow<'static, str>,
line_number: usize,
},
Any {
content: Cow<'static, str>,
},
}
impl Position {
#[inline]
pub fn line_number(content: impl Into<Cow<'static, str>>, line_number: usize) -> Position {
Self::LineNumber {
content: content.into(),
line_number,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TriviaKind {
Comment,
Whitespace,
}
impl TriviaKind {
pub fn at(self, start: usize, end: usize, line_number: usize) -> Trivia {
Trivia {
position: Position::LineNumberReference {
start,
end,
line_number,
},
kind: self,
}
}
pub fn with_content<IntoCowStr: Into<Cow<'static, str>>>(self, content: IntoCowStr) -> Trivia {
Trivia {
position: Position::Any {
content: content.into(),
},
kind: self,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Trivia {
position: Position,
kind: TriviaKind,
}
impl Trivia {
pub fn read<'a: 'b, 'b>(&'a self, code: &'b str) -> &'b str {
match &self.position {
Position::LineNumberReference { start, end, .. } => {
code.get(*start..*end).unwrap_or_else(|| {
panic!("unable to extract code from position: {} - {}", start, end);
})
}
Position::LineNumber { content, .. } | Position::Any { content } => content,
}
}
pub fn try_read(&self) -> Option<&str> {
match &self.position {
Position::LineNumberReference { .. } => None,
Position::LineNumber { content, .. } | Position::Any { content } => Some(content),
}
}
pub fn kind(&self) -> TriviaKind {
self.kind.clone()
}
pub fn get_line_number(&self) -> Option<usize> {
match &self.position {
Position::LineNumber { line_number, .. }
| Position::LineNumberReference { line_number, .. } => Some(*line_number),
Position::Any { .. } => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Token {
position: Position,
leading_trivia: Vec<Trivia>,
trailing_trivia: Vec<Trivia>,
}
impl Token {
pub fn new_with_line(start: usize, end: usize, line_number: usize) -> Self {
Self {
position: Position::LineNumberReference {
start,
end,
line_number,
},
leading_trivia: Vec::new(),
trailing_trivia: Vec::new(),
}
}
pub fn from_content<IntoCowStr: Into<Cow<'static, str>>>(content: IntoCowStr) -> Self {
Self {
position: Position::Any {
content: content.into(),
},
leading_trivia: Vec::new(),
trailing_trivia: Vec::new(),
}
}
pub fn from_position(position: Position) -> Self {
Self {
position,
leading_trivia: Vec::new(),
trailing_trivia: Vec::new(),
}
}
pub fn with_leading_trivia(mut self, trivia: Trivia) -> Self {
self.leading_trivia.push(trivia);
self
}
pub fn with_trailing_trivia(mut self, trivia: Trivia) -> Self {
self.trailing_trivia.push(trivia);
self
}
#[inline]
pub fn has_trivia(&self) -> bool {
!self.leading_trivia.is_empty() || !self.trailing_trivia.is_empty()
}
#[inline]
pub fn push_leading_trivia(&mut self, trivia: Trivia) {
self.leading_trivia.push(trivia);
}
#[inline]
pub fn push_trailing_trivia(&mut self, trivia: Trivia) {
self.trailing_trivia.push(trivia);
}
#[inline]
pub fn iter_leading_trivia(&self) -> impl Iterator<Item = &Trivia> {
self.leading_trivia.iter()
}
#[inline]
pub fn iter_trailing_trivia(&self) -> impl Iterator<Item = &Trivia> {
self.trailing_trivia.iter()
}
pub fn read<'a: 'b, 'b>(&'a self, code: &'b str) -> &'b str {
match &self.position {
Position::LineNumberReference { start, end, .. } => code
.get(*start..*end)
.expect("unable to extract code from position"),
Position::LineNumber { content, .. } | Position::Any { content } => content,
}
}
pub fn get_line_number(&self) -> Option<usize> {
match &self.position {
Position::LineNumber { line_number, .. }
| Position::LineNumberReference { line_number, .. } => Some(*line_number),
Position::Any { .. } => None,
}
}
pub fn replace_with_content<IntoCowStr: Into<Cow<'static, str>>>(
&mut self,
content: IntoCowStr,
) {
self.position = match &self.position {
Position::LineNumber { line_number, .. }
| Position::LineNumberReference { line_number, .. } => Position::LineNumber {
line_number: *line_number,
content: content.into(),
},
Position::Any { .. } => Position::Any {
content: content.into(),
},
};
}
pub fn clear_comments(&mut self) {
self.leading_trivia
.retain(|trivia| trivia.kind() != TriviaKind::Comment);
self.trailing_trivia
.retain(|trivia| trivia.kind() != TriviaKind::Comment);
}
pub fn clear_whitespaces(&mut self) {
self.leading_trivia
.retain(|trivia| trivia.kind() != TriviaKind::Whitespace);
self.trailing_trivia
.retain(|trivia| trivia.kind() != TriviaKind::Whitespace);
}
pub(crate) fn replace_referenced_tokens(&mut self, code: &str) {
if let Position::LineNumberReference {
start,
end,
line_number,
} = self.position
{
self.position = Position::LineNumber {
line_number,
content: code
.get(start..end)
.expect("unable to extract code from position")
.to_owned()
.into(),
}
};
for trivia in self
.leading_trivia
.iter_mut()
.chain(self.trailing_trivia.iter_mut())
{
if let Position::LineNumberReference {
start,
end,
line_number,
} = trivia.position
{
trivia.position = Position::LineNumber {
line_number,
content: code
.get(start..end)
.expect("unable to extract code from position")
.to_owned()
.into(),
}
};
}
}
pub(crate) fn shift_token_line(&mut self, amount: usize) {
match &mut self.position {
Position::LineNumberReference { line_number, .. }
| Position::LineNumber { line_number, .. } => *line_number += amount,
Position::Any { .. } => {}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn read_line_number_reference_token() {
let code = "return true";
let token = Token::new_with_line(7, 11, 1);
assert_eq!("true", token.read(code));
}
#[test]
fn read_any_position_token() {
let token = Token::from_content("true");
assert_eq!("true", token.read(""));
}
}