use crate::TokenID;
use parlex::{Span, Token};
use smartstring::alias::String;
#[derive(Debug, Clone)]
pub enum TokenValue {
None,
Ident(usize),
Number(i64),
Comment(String),
Stat {
comments: Vec<String>,
value: Option<i64>,
},
}
#[derive(Debug, Clone)]
pub struct CalcToken {
pub token_id: TokenID,
pub value: TokenValue,
pub span: Option<Span>,
}
impl CalcToken {
pub fn merge_span(&mut self, other_span: &Option<Span>) {
match other_span {
Some(other_span) => match &mut self.span {
Some(my_span) => {
*my_span = my_span.merge(other_span);
}
None => {
self.span = Some(*other_span);
}
},
None => (),
}
}
pub fn to_statement(&mut self, comment: Option<String>) {
self.token_id = TokenID::Stat;
match &mut self.value {
TokenValue::None => {
self.value = TokenValue::Stat {
comments: if let Some(comment) = comment {
vec![comment]
} else {
vec![]
},
value: None,
};
}
TokenValue::Comment(comment2) => {
self.value = TokenValue::Stat {
comments: if let Some(comment) = comment {
vec![comment, std::mem::take(comment2)]
} else {
vec![std::mem::take(comment2)]
},
value: None,
};
}
TokenValue::Ident(_) => panic!("unexpected token value in `to_statement`"),
TokenValue::Number(value) => {
self.value = TokenValue::Stat {
comments: if let Some(comment) = comment {
vec![comment]
} else {
vec![]
},
value: Some(*value),
};
}
TokenValue::Stat { comments, value } => {
let mut cs = std::mem::take(comments);
if let Some(comment) = comment {
cs.insert(0, comment);
}
self.value = TokenValue::Stat {
comments: cs,
value: std::mem::take(value),
};
}
}
}
}
impl Token for CalcToken {
type TokenID = TokenID;
fn token_id(&self) -> Self::TokenID {
self.token_id
}
fn span(&self) -> Option<Span> {
self.span
}
}
#[cfg(test)]
mod tests {
use super::*;
use parlex::{Position, span};
#[test]
fn token_value_number_extraction_with_let_else() {
let tok = TokenValue::Number(42);
let TokenValue::Number(n) = tok else {
panic!("Expected a numeric token");
};
assert_eq!(n, 42);
}
#[test]
#[should_panic(expected = "Expected a numeric token")]
fn token_value_number_extraction_should_panic_if_not_number() {
let tok = TokenValue::Ident(5);
let TokenValue::Number(_n) = tok else {
panic!("Expected a numeric token");
};
}
#[test]
fn token_value_ident_stores_symbol_index() {
let idx = 7usize;
let tok = TokenValue::Ident(idx);
if let TokenValue::Ident(i) = tok {
assert_eq!(i, idx);
} else {
panic!("Expected Ident token");
}
}
#[test]
fn token_value_none_matches() {
let tok = TokenValue::None;
assert!(matches!(tok, TokenValue::None));
}
#[test]
fn calc_token_trait_accessors_return_values() {
let t = CalcToken {
token_id: TokenID::Number,
value: TokenValue::Number(99),
span: span!(1, 2, 1, 10),
};
assert_eq!(t.token_id(), TokenID::Number);
assert_eq!(t.span().unwrap().start.column, 2);
}
#[test]
fn calc_token_with_identifier_round_trip() {
let t = CalcToken {
token_id: TokenID::Ident,
value: TokenValue::Ident(5),
span: span!(1, 2, 1, 10),
};
assert_eq!(t.token_id(), TokenID::Ident);
if let TokenValue::Ident(i) = t.value {
assert_eq!(i, 5);
} else {
panic!("Expected TokenValue::Ident");
}
assert_eq!(t.span().unwrap().display(), "span 1:2 to 1:10");
}
#[test]
fn calc_token_is_cloneable_and_debuggable() {
let t1 = CalcToken {
token_id: TokenID::Number,
value: TokenValue::Number(-1),
span: span!(10, 20, 12, 20),
};
let t2 = t1.clone();
assert_eq!(t2.token_id(), t1.token_id());
assert_eq!(t2.span().unwrap().display(), "span 10:20 to 12:20");
let dbg_out = format!("{t1:?}");
assert!(dbg_out.contains("CalcToken"));
}
#[test]
#[should_panic(expected = "Expected TokenValue::Ident")]
fn calc_token_with_identifier_should_panic_if_wrong_kind() {
let t = CalcToken {
token_id: TokenID::Number,
value: TokenValue::Number(0),
span: span!(10, 20, 12, 20),
};
if let TokenValue::Ident(_) = t.value {
} else {
panic!("Expected TokenValue::Ident");
}
}
fn sp(sl: usize, sc: usize, el: usize, ec: usize) -> Span {
Span::new(Position::new(sl, sc), Position::new(el, ec))
}
fn tok(token_id: TokenID, value: TokenValue, span: Option<Span>) -> CalcToken {
CalcToken {
token_id,
value,
span,
}
}
#[test]
fn merge_span_expands_existing_span_to_cover_both() {
let mut t = tok(
TokenID::Number,
TokenValue::Number(1),
Some(sp(0, 5, 0, 10)),
);
let other = Some(sp(0, 2, 0, 12));
t.merge_span(&other);
let m = t.span.unwrap();
assert_eq!(m.start, Position::new(0, 2));
assert_eq!(m.end, Position::new(0, 12));
}
#[test]
fn merge_span_sets_when_self_is_none() {
let mut t = tok(TokenID::Ident, TokenValue::Ident(0), None);
let new_span = Some(sp(1, 0, 1, 3));
t.merge_span(&new_span);
assert_eq!(t.span, new_span);
}
#[test]
fn merge_span_is_noop_when_other_is_none() {
let before = Some(sp(2, 4, 2, 9));
let mut t = tok(TokenID::Number, TokenValue::Number(0), before);
t.merge_span(&None);
assert_eq!(t.span, before);
}
#[test]
fn merge_span_both_none_remains_none() {
let mut t = tok(TokenID::Number, TokenValue::Number(0), None);
t.merge_span(&None);
assert!(t.span.is_none());
}
#[test]
fn merge_span_other_within_self_no_change() {
let mut t = tok(
TokenID::Number,
TokenValue::Number(0),
Some(sp(5, 2, 5, 10)),
);
let inner = Some(sp(5, 4, 5, 7));
t.merge_span(&inner);
assert_eq!(t.span, Some(sp(5, 2, 5, 10)));
}
#[test]
fn merge_span_cross_line_expands() {
let mut t = tok(TokenID::Ident, TokenValue::Ident(1), Some(sp(1, 5, 2, 3)));
let other = Some(sp(0, 9, 3, 1));
t.merge_span(&other);
let m = t.span.unwrap();
assert_eq!(m.start, Position::new(0, 9));
assert_eq!(m.end, Position::new(3, 1));
}
}