Skip to main content

nginx_discovery/parser/
token.rs

1//! Token types for NGINX configuration lexer
2
3use crate::ast::Span;
4
5/// A token in the NGINX configuration
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Token {
8    /// The token type and value
9    pub kind: TokenKind,
10    /// Source location
11    pub span: Span,
12}
13
14impl Token {
15    /// Create a new token
16    #[must_use]
17    pub fn new(kind: TokenKind, span: Span) -> Self {
18        Self { kind, span }
19    }
20}
21
22/// Token types in NGINX configuration
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum TokenKind {
25    /// Word/identifier: `server`, `listen`, etc.
26    Word(String),
27
28    /// String literal: `"value"` or `'value'`
29    String(String),
30
31    /// Number: `80`, `443`, etc.
32    Number(String),
33
34    /// Variable: `$host`, `$remote_addr`
35    Variable(String),
36
37    /// Left brace: `{`
38    LeftBrace,
39
40    /// Right brace: `}`
41    RightBrace,
42
43    /// Semicolon: `;`
44    Semicolon,
45
46    /// Comment: `# comment text`
47    Comment(String),
48
49    /// End of file
50    Eof,
51}
52
53impl TokenKind {
54    /// Check if this token is a word
55    #[must_use]
56    pub fn is_word(&self) -> bool {
57        matches!(self, Self::Word(_))
58    }
59
60    /// Check if this token is a string
61    #[must_use]
62    pub fn is_string(&self) -> bool {
63        matches!(self, Self::String(_))
64    }
65
66    /// Check if this token is a number
67    #[must_use]
68    pub fn is_number(&self) -> bool {
69        matches!(self, Self::Number(_))
70    }
71
72    /// Check if this token is a variable
73    #[must_use]
74    pub fn is_variable(&self) -> bool {
75        matches!(self, Self::Variable(_))
76    }
77
78    /// Get the string value if this is a word or string token
79    #[must_use]
80    pub fn as_string(&self) -> Option<&str> {
81        match self {
82            Self::Word(s) | Self::String(s) | Self::Variable(s) | Self::Number(s) => Some(s),
83            _ => None,
84        }
85    }
86}
87
88impl std::fmt::Display for TokenKind {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Self::Word(s) => write!(f, "word '{s}'"),
92            Self::String(s) => write!(f, "string \"{s}\""),
93            Self::Number(s) => write!(f, "number '{s}'"),
94            Self::Variable(s) => write!(f, "variable '${s}'"),
95            Self::LeftBrace => write!(f, "'{{'"), // Changed: double {{ to escape
96            Self::RightBrace => write!(f, "'}}'"), // Changed: double }} to escape
97            Self::Semicolon => write!(f, "';'"),
98            Self::Comment(s) => write!(f, "comment '# {s}'"),
99            Self::Eof => write!(f, "end of file"),
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_token_creation() {
110        let token = Token::new(TokenKind::Word("server".to_string()), Span::new(0, 6, 1, 1));
111        assert_eq!(token.kind, TokenKind::Word("server".to_string()));
112    }
113
114    #[test]
115    fn test_token_kind_checks() {
116        assert!(TokenKind::Word("test".to_string()).is_word());
117        assert!(TokenKind::String("test".to_string()).is_string());
118        assert!(TokenKind::Number("80".to_string()).is_number());
119        assert!(TokenKind::Variable("host".to_string()).is_variable());
120
121        assert!(!TokenKind::LeftBrace.is_word());
122        assert!(!TokenKind::Semicolon.is_string());
123    }
124
125    #[test]
126    fn test_as_string() {
127        assert_eq!(
128            TokenKind::Word("test".to_string()).as_string(),
129            Some("test")
130        );
131        assert_eq!(
132            TokenKind::String("value".to_string()).as_string(),
133            Some("value")
134        );
135        assert_eq!(TokenKind::LeftBrace.as_string(), None);
136    }
137
138    #[test]
139    fn test_token_display() {
140        assert_eq!(
141            TokenKind::Word("server".to_string()).to_string(),
142            "word 'server'"
143        );
144        assert_eq!(TokenKind::LeftBrace.to_string(), "'{'");
145        assert_eq!(TokenKind::Semicolon.to_string(), "';'");
146    }
147}