use num_bigint::BigInt;
use crate::ast::BinOp;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub line: u32,
pub col: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
pub kind: TokenKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum StrSegment {
Lit(String),
Hole(Vec<Token>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
Pls,
Bork,
Bonk,
Bark,
Wow,
Such,
Much,
Many,
So,
Very,
Super,
OhNo,
If,
Elif,
Else,
For,
While,
In,
Return,
Continue,
And,
Or,
Not,
True,
False,
None,
Def,
Class,
Amaze,
Ident(String),
Int(BigInt),
Float(f64),
Str(String),
StrInterp(Vec<StrSegment>),
Plus,
Minus,
Star,
StarStar,
Slash,
SlashSlash,
Percent,
Amp,
Pipe,
Caret,
Tilde,
Shl,
Shr,
EqEq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
Eq,
AugAssign(BinOp),
Colon,
Bang,
Comma,
Dot,
LParen,
RParen,
LBracket,
RBracket,
LBrace,
RBrace,
Newline,
Indent,
Dedent,
Eof,
}
impl TokenKind {
pub fn describe(&self) -> String {
match self {
TokenKind::Pls
| TokenKind::Bork
| TokenKind::Bonk
| TokenKind::Bark
| TokenKind::Wow
| TokenKind::Such
| TokenKind::Much
| TokenKind::Many
| TokenKind::So
| TokenKind::Very
| TokenKind::Super
| TokenKind::If
| TokenKind::Elif
| TokenKind::Else
| TokenKind::For
| TokenKind::While
| TokenKind::In
| TokenKind::Return
| TokenKind::Continue
| TokenKind::And
| TokenKind::Or
| TokenKind::Not
| TokenKind::True
| TokenKind::False
| TokenKind::None
| TokenKind::Def
| TokenKind::Class
| TokenKind::Amaze => crate::keywords::keyword_spelling(self)
.expect("compiler bug: keyword token missing from KEYWORDS table")
.into(),
TokenKind::OhNo => "oh no".into(),
TokenKind::Ident(name) => format!("name '{name}'"),
TokenKind::Int(n) => format!("the number {n}"),
TokenKind::Float(f) => format!("the number {f}"),
TokenKind::Str(_) | TokenKind::StrInterp(_) => "a string".into(),
TokenKind::Plus => "+".into(),
TokenKind::Minus => "-".into(),
TokenKind::Star => "*".into(),
TokenKind::StarStar => "**".into(),
TokenKind::Slash => "/".into(),
TokenKind::SlashSlash => "//".into(),
TokenKind::Percent => "%".into(),
TokenKind::Amp => "&".into(),
TokenKind::Pipe => "|".into(),
TokenKind::Caret => "^".into(),
TokenKind::Tilde => "~".into(),
TokenKind::Shl => "<<".into(),
TokenKind::Shr => ">>".into(),
TokenKind::EqEq => "==".into(),
TokenKind::NotEq => "!=".into(),
TokenKind::Lt => "<".into(),
TokenKind::LtEq => "<=".into(),
TokenKind::Gt => ">".into(),
TokenKind::GtEq => ">=".into(),
TokenKind::Eq => "=".into(),
TokenKind::AugAssign(op) => format!("{}=", op.symbol()),
TokenKind::Colon => ":".into(),
TokenKind::Bang => "!".into(),
TokenKind::Comma => ",".into(),
TokenKind::Dot => ".".into(),
TokenKind::LParen => "(".into(),
TokenKind::RParen => ")".into(),
TokenKind::LBracket => "[".into(),
TokenKind::RBracket => "]".into(),
TokenKind::LBrace => "{".into(),
TokenKind::RBrace => "}".into(),
TokenKind::Newline => "the end of the line".into(),
TokenKind::Indent => "more indentation".into(),
TokenKind::Dedent => "less indentation".into(),
TokenKind::Eof => "the end of the script".into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keywords::{keyword_spelling, lookup, KEYWORDS};
#[test]
fn every_keyword_round_trips_through_lookup_and_describe() {
for (spelling, kind) in KEYWORDS {
assert_eq!(lookup(spelling).as_ref(), Some(kind));
assert_eq!(keyword_spelling(kind), Some(*spelling));
assert_eq!(kind.describe(), *spelling);
}
}
#[test]
fn non_keyword_tokens_have_no_keyword_spelling() {
assert_eq!(keyword_spelling(&TokenKind::OhNo), None);
assert_eq!(keyword_spelling(&TokenKind::Plus), None);
assert_eq!(keyword_spelling(&TokenKind::Eof), None);
}
#[test]
fn ohno_describes_as_two_words() {
assert_eq!(TokenKind::OhNo.describe(), "oh no");
}
}