use rowan::TextRange;
use smol_str::SmolStr;
use crate::syntax::{SyntaxKind, SyntaxToken};
pub trait AstToken {
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized;
fn cast(syntax: SyntaxToken) -> Option<Self>
where
Self: Sized;
fn syntax(&self) -> &SyntaxToken;
fn text(&self) -> &str {
self.syntax().text()
}
fn text_range(&self) -> TextRange {
self.syntax().text_range()
}
}
macro_rules! ast_token {
($name:ident, $kind:expr) => {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxToken);
impl AstToken for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == $kind
}
fn cast(syntax: SyntaxToken) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| Self(syntax))
}
fn syntax(&self) -> &SyntaxToken {
&self.0
}
}
};
}
ast_token!(Ident, SyntaxKind::IDENT);
ast_token!(IntLit, SyntaxKind::INT);
ast_token!(FloatLit, SyntaxKind::FLOAT);
ast_token!(ComplexLit, SyntaxKind::COMPLEX);
ast_token!(StringLit, SyntaxKind::STRING);
ast_token!(Comment, SyntaxKind::COMMENT);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RConstant {
True,
False,
Na,
NaInteger,
NaReal,
NaComplex,
NaCharacter,
Null,
NaN,
Inf,
TSymbol,
FSymbol,
}
impl Ident {
pub fn name(&self) -> &str {
self.text()
}
pub fn constant(&self) -> Option<RConstant> {
Some(match self.text() {
"TRUE" => RConstant::True,
"FALSE" => RConstant::False,
"NA" => RConstant::Na,
"NA_integer_" => RConstant::NaInteger,
"NA_real_" => RConstant::NaReal,
"NA_complex_" => RConstant::NaComplex,
"NA_character_" => RConstant::NaCharacter,
"NULL" => RConstant::Null,
"NaN" => RConstant::NaN,
"Inf" => RConstant::Inf,
"T" => RConstant::TSymbol,
"F" => RConstant::FSymbol,
_ => return None,
})
}
pub fn is_true(&self) -> bool {
self.text() == "TRUE"
}
pub fn is_false(&self) -> bool {
self.text() == "FALSE"
}
pub fn is_na(&self) -> bool {
matches!(
self.text(),
"NA" | "NA_integer_" | "NA_real_" | "NA_complex_" | "NA_character_"
)
}
pub fn is_null(&self) -> bool {
self.text() == "NULL"
}
pub fn is_nan(&self) -> bool {
self.text() == "NaN"
}
pub fn is_bool_symbol(&self) -> bool {
matches!(self.text(), "T" | "F")
}
pub fn is_dots(&self) -> bool {
let name = self.text();
name.starts_with('.') && name.chars().all(|c| c == '.' || c.is_ascii_digit())
}
pub fn is_reserved_constant(&self) -> bool {
matches!(
self.constant(),
Some(
RConstant::Na
| RConstant::NaInteger
| RConstant::NaReal
| RConstant::NaComplex
| RConstant::NaCharacter
| RConstant::Null
| RConstant::True
| RConstant::False
| RConstant::Inf
| RConstant::NaN
)
)
}
}
impl StringLit {
pub fn quote(&self) -> Option<char> {
let bytes = self.text().as_bytes();
if bytes.len() < 2 {
return None;
}
let q = bytes[0];
(matches!(q, b'"' | b'\'') && bytes[bytes.len() - 1] == q).then_some(q as char)
}
pub fn inner(&self) -> Option<&str> {
self.quote()?;
let text = self.text();
Some(&text[1..text.len() - 1])
}
pub fn is_backtick(&self) -> bool {
let bytes = self.text().as_bytes();
bytes.len() >= 2 && bytes[0] == b'`' && bytes[bytes.len() - 1] == b'`'
}
pub fn unquote(&self) -> Option<&str> {
let text = self.text();
let bytes = text.as_bytes();
if bytes.len() >= 2 {
let (first, last) = (bytes[0], bytes[bytes.len() - 1]);
if matches!(first, b'"' | b'\'' | b'`') && first == last {
return Some(&text[1..text.len() - 1]);
}
}
None
}
}
pub fn token_name(token: &SyntaxToken) -> SmolStr {
if token.kind() == SyntaxKind::STRING {
let text = token.text();
let bytes = text.as_bytes();
if bytes.len() >= 2 {
let (first, last) = (bytes[0], bytes[bytes.len() - 1]);
if matches!(first, b'"' | b'\'' | b'`') && first == last {
return SmolStr::new(&text[1..text.len() - 1]);
}
}
}
SmolStr::new(token.text())
}