use super::span::Span;
use super::trivia::{Trivia, TriviaKind};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenKind {
Variable,
Number,
StringSq,
StringDq,
HereStringSq,
HereStringDq,
Generic,
Keyword,
Parameter,
Operator,
Pipe,
Amp,
Semicolon,
Comma,
Dot,
DoubleColon,
LParen,
RParen,
LBrace,
RBrace,
LBracket,
RBracket,
DollarParen,
AtParen,
AtBrace,
Redirect,
VerbatimArgs,
Eof,
Unknown,
}
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub kind: TokenKind,
pub value: String,
pub span: Span,
pub leading: Vec<Trivia>,
pub trailing: Vec<Trivia>,
}
impl Token {
pub fn full_span(&self) -> Span {
let start = self
.leading
.first()
.map_or(self.span.start, |t| t.span.start);
let end = self.trailing.last().map_or(self.span.end, |t| t.span.end);
Span::new(start, end)
}
pub fn write_full(&self, out: &mut String) {
for t in &self.leading {
out.push_str(&t.text);
}
out.push_str(&self.value);
for t in &self.trailing {
out.push_str(&t.text);
}
}
pub fn leading_comments(&self) -> impl Iterator<Item = &Trivia> {
self.leading.iter().filter(|t| t.is_comment())
}
pub fn trailing_comment(&self) -> Option<&Trivia> {
self.trailing.iter().find(|t| t.is_comment())
}
pub fn starts_line(&self) -> bool {
self.leading.iter().any(|t| t.kind == TriviaKind::Newline)
}
pub fn ends_line(&self) -> bool {
self.trailing.iter().any(|t| t.kind == TriviaKind::Newline)
}
pub fn value_eq_ci(&self, other: &str) -> bool {
self.value.eq_ignore_ascii_case(other)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LexError {
pub span: Span,
pub message: String,
}
impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at {}", self.message, self.span)
}
}
impl std::error::Error for LexError {}
pub const NAMED_OPERATORS: &[&str] = &[
"eq",
"ne",
"gt",
"ge",
"lt",
"le",
"like",
"notlike",
"match",
"notmatch",
"replace",
"contains",
"notcontains",
"in",
"notin",
"is",
"isnot",
"as",
"and",
"or",
"xor",
"not",
"band",
"bor",
"bxor",
"bnot",
"shl",
"shr",
"join",
"split",
"f",
];
pub const KEYWORDS: &[&str] = &[
"if",
"elseif",
"else",
"switch",
"while",
"for",
"foreach",
"do",
"until",
"break",
"continue",
"function",
"filter",
"workflow",
"return",
"throw",
"trap",
"try",
"catch",
"finally",
"param",
"begin",
"process",
"end",
"dynamicparam",
"data",
"class",
"enum",
"using",
"in",
"exit",
];
#[cfg(test)]
mod tests {
use super::*;
use crate::v2::trivia::TriviaKind;
fn trivia(kind: TriviaKind, text: &str, start: usize) -> Trivia {
Trivia {
kind,
text: text.to_string(),
span: Span::new(start, start + text.len()),
}
}
#[test]
fn full_span_and_write_full() {
let tok = Token {
kind: TokenKind::Generic,
value: "ls".to_string(),
span: Span::new(2, 4),
leading: vec![trivia(TriviaKind::Whitespace, " ", 0)],
trailing: vec![
trivia(TriviaKind::Whitespace, " ", 4),
trivia(TriviaKind::LineComment, "# x", 5),
trivia(TriviaKind::Newline, "\n", 8),
],
};
assert_eq!(tok.full_span(), Span::new(0, 9));
let mut s = String::new();
tok.write_full(&mut s);
assert_eq!(s, " ls # x\n");
assert_eq!(tok.trailing_comment().unwrap().text, "# x");
assert!(!tok.starts_line());
assert!(tok.value_eq_ci("LS"));
}
#[test]
fn starts_line_via_leading_newline() {
let tok = Token {
kind: TokenKind::Generic,
value: "x".to_string(),
span: Span::new(1, 2),
leading: vec![trivia(TriviaKind::Newline, "\n", 0)],
trailing: vec![],
};
assert!(tok.starts_line());
assert_eq!(tok.leading_comments().count(), 0);
}
}