horkos 0.2.0

Cloud infrastructure language where insecure code won't compile
Documentation
//! Token definitions for Horkos.

use crate::ast::Span;
use serde::{Deserialize, Serialize};

/// A token with its kind, span, and original lexeme.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
    pub kind: TokenKind,
    pub span: Span,
    pub lexeme: String,
}

impl Token {
    /// Check if this token is of a specific kind.
    pub fn is(&self, kind: &TokenKind) -> bool {
        std::mem::discriminant(&self.kind) == std::mem::discriminant(kind)
    }

    /// Check if this is an identifier.
    pub fn is_ident(&self) -> bool {
        matches!(self.kind, TokenKind::Ident(_))
    }
}

/// Token kinds.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TokenKind {
    // Literals
    Ident(String),
    String(String),
    Number(f64),

    // Keywords
    Val,
    Import,
    As,
    Unsafe,
    Module,
    True,
    False,
    If,
    Then,
    Else,
    Assert,
    Hcl,

    // Punctuation
    LParen,   // (
    RParen,   // )
    LBrace,   // {
    RBrace,   // }
    LBracket, // [
    RBracket, // ]
    Comma,    // ,
    Colon,    // :
    Dot,      // .

    // Operators
    Eq,     // =
    Arrow,  // =>
    EqEq,   // ==
    BangEq, // !=
    Lt,     // <
    LtEq,   // <=
    Gt,     // >
    GtEq,   // >=
    Bang,   // !
    And,    // &&
    Or,     // ||
    Plus,   // +
    Minus,  // -
    Star,   // *
    Slash,  // /

    // Special
    Eof,
}

impl TokenKind {
    /// Get a human-readable name for this token kind.
    pub fn name(&self) -> &'static str {
        match self {
            TokenKind::Ident(_) => "identifier",
            TokenKind::String(_) => "string",
            TokenKind::Number(_) => "number",
            TokenKind::Val => "'val'",
            TokenKind::Import => "'import'",
            TokenKind::As => "'as'",
            TokenKind::Unsafe => "'unsafe'",
            TokenKind::Module => "'module'",
            TokenKind::True => "'true'",
            TokenKind::False => "'false'",
            TokenKind::If => "'if'",
            TokenKind::Then => "'then'",
            TokenKind::Else => "'else'",
            TokenKind::Assert => "'assert'",
            TokenKind::Hcl => "'hcl'",
            TokenKind::LParen => "'('",
            TokenKind::RParen => "')'",
            TokenKind::LBrace => "'{'",
            TokenKind::RBrace => "'}'",
            TokenKind::LBracket => "'['",
            TokenKind::RBracket => "']'",
            TokenKind::Comma => "','",
            TokenKind::Colon => "':'",
            TokenKind::Dot => "'.'",
            TokenKind::Eq => "'='",
            TokenKind::Arrow => "'=>'",
            TokenKind::EqEq => "'=='",
            TokenKind::BangEq => "'!='",
            TokenKind::Lt => "'<'",
            TokenKind::LtEq => "'<='",
            TokenKind::Gt => "'>'",
            TokenKind::GtEq => "'>='",
            TokenKind::Bang => "'!'",
            TokenKind::And => "'&&'",
            TokenKind::Or => "'||'",
            TokenKind::Plus => "'+'",
            TokenKind::Minus => "'-'",
            TokenKind::Star => "'*'",
            TokenKind::Slash => "'/'",
            TokenKind::Eof => "end of file",
        }
    }
}

impl std::fmt::Display for TokenKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TokenKind::Ident(s) => write!(f, "{}", s),
            TokenKind::String(s) => write!(f, "\"{}\"", s),
            TokenKind::Number(n) => write!(f, "{}", n),
            _ => write!(f, "{}", self.name()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_token_kind_name() {
        assert_eq!(TokenKind::Val.name(), "'val'");
        assert_eq!(TokenKind::Ident("foo".to_string()).name(), "identifier");
    }

    #[test]
    fn test_token_is() {
        let token = Token {
            kind: TokenKind::Val,
            span: Span::dummy(),
            lexeme: "val".to_string(),
        };
        assert!(token.is(&TokenKind::Val));
        assert!(!token.is(&TokenKind::Import));
    }
}