Skip to main content

nautilus_schema/
token.rs

1//! Token types for schema lexing.
2
3use crate::span::Span;
4use std::fmt;
5
6/// A token in the schema language.
7#[derive(Debug, Clone, PartialEq)]
8pub struct Token {
9    /// The kind of token.
10    pub kind: TokenKind,
11    /// The span of the token in source.
12    pub span: Span,
13}
14
15impl Token {
16    /// Create a new token.
17    pub const fn new(kind: TokenKind, span: Span) -> Self {
18        Self { kind, span }
19    }
20}
21
22impl fmt::Display for Token {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{:?} @ {}", self.kind, self.span)
25    }
26}
27
28/// Token kinds for the schema language.
29#[derive(Debug, Clone, PartialEq)]
30pub enum TokenKind {
31    /// `datasource` keyword.
32    Datasource,
33    /// `generator` keyword.
34    Generator,
35    /// `model` keyword.
36    Model,
37    /// `enum` keyword.
38    Enum,
39    /// `type` keyword.
40    Type,
41    /// `true` keyword.
42    True,
43    /// `false` keyword.
44    False,
45
46    /// Identifier (e.g., `User`, `email`, `autoincrement`).
47    Ident(String),
48    /// String literal (e.g., `"postgresql"`).
49    String(String),
50    /// Number literal (e.g., `42`, `3.14`).
51    Number(String),
52
53    /// `@` symbol (field attribute).
54    At,
55    /// `@@` symbol (model attribute).
56    AtAt,
57
58    /// `{` symbol.
59    LBrace,
60    /// `}` symbol.
61    RBrace,
62    /// `[` symbol.
63    LBracket,
64    /// `]` symbol.
65    RBracket,
66    /// `(` symbol.
67    LParen,
68    /// `)` symbol.
69    RParen,
70    /// `,` symbol.
71    Comma,
72    /// `:` symbol.
73    Colon,
74    /// `=` symbol.
75    Equal,
76    /// `?` symbol (optional field).
77    Question,
78    /// `!` symbol (not-null field).
79    Bang,
80    /// `.` symbol (for method calls).
81    Dot,
82
83    /// `*` operator.
84    Star,
85    /// `+` operator.
86    Plus,
87    /// `-` operator.
88    Minus,
89    /// `/` operator.
90    Slash,
91    /// `|` operator.
92    Pipe,
93    /// `||` operator (SQL string concatenation).
94    DoublePipe,
95    /// `<` operator.
96    LAngle,
97    /// `>` operator.
98    RAngle,
99    /// `%` operator.
100    Percent,
101    /// `<=` operator.
102    LessEqual,
103    /// `>=` operator.
104    GreaterEqual,
105    /// `!=` operator.
106    BangEqual,
107
108    /// Newline (significant for statement termination).
109    Newline,
110    /// End of file.
111    Eof,
112}
113
114impl TokenKind {
115    /// Check if this token is a keyword.
116    pub fn is_keyword(&self) -> bool {
117        matches!(
118            self,
119            TokenKind::Datasource
120                | TokenKind::Generator
121                | TokenKind::Model
122                | TokenKind::Enum
123                | TokenKind::Type
124                | TokenKind::True
125                | TokenKind::False
126        )
127    }
128
129    /// Try to convert an identifier string to a keyword.
130    pub fn from_ident(ident: &str) -> Self {
131        match ident {
132            "datasource" => TokenKind::Datasource,
133            "generator" => TokenKind::Generator,
134            "model" => TokenKind::Model,
135            "enum" => TokenKind::Enum,
136            "type" => TokenKind::Type,
137            "true" => TokenKind::True,
138            "false" => TokenKind::False,
139            _ => TokenKind::Ident(ident.to_string()),
140        }
141    }
142}
143
144impl fmt::Display for TokenKind {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match self {
147            TokenKind::Datasource => write!(f, "datasource"),
148            TokenKind::Generator => write!(f, "generator"),
149            TokenKind::Model => write!(f, "model"),
150            TokenKind::Enum => write!(f, "enum"),
151            TokenKind::Type => write!(f, "type"),
152            TokenKind::True => write!(f, "true"),
153            TokenKind::False => write!(f, "false"),
154            TokenKind::Ident(s) => write!(f, "identifier '{}'", s),
155            TokenKind::String(s) => write!(f, "string \"{}\"", s),
156            TokenKind::Number(s) => write!(f, "number {}", s),
157            TokenKind::At => write!(f, "@"),
158            TokenKind::AtAt => write!(f, "@@"),
159            TokenKind::LBrace => write!(f, "{{"),
160            TokenKind::RBrace => write!(f, "}}"),
161            TokenKind::LBracket => write!(f, "["),
162            TokenKind::RBracket => write!(f, "]"),
163            TokenKind::LParen => write!(f, "("),
164            TokenKind::RParen => write!(f, ")"),
165            TokenKind::Comma => write!(f, ","),
166            TokenKind::Colon => write!(f, ":"),
167            TokenKind::Equal => write!(f, "="),
168            TokenKind::Question => write!(f, "?"),
169            TokenKind::Bang => write!(f, "!"),
170            TokenKind::Dot => write!(f, "."),
171            TokenKind::Star => write!(f, "*"),
172            TokenKind::Plus => write!(f, "+"),
173            TokenKind::Minus => write!(f, "-"),
174            TokenKind::Slash => write!(f, "/"),
175            TokenKind::Pipe => write!(f, "|"),
176            TokenKind::DoublePipe => write!(f, "||"),
177            TokenKind::LAngle => write!(f, "<"),
178            TokenKind::RAngle => write!(f, ">"),
179            TokenKind::Percent => write!(f, "%"),
180            TokenKind::LessEqual => write!(f, "<="),
181            TokenKind::GreaterEqual => write!(f, ">="),
182            TokenKind::BangEqual => write!(f, "!="),
183            TokenKind::Newline => write!(f, "newline"),
184            TokenKind::Eof => write!(f, "end of file"),
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_keyword_detection() {
195        assert!(TokenKind::Datasource.is_keyword());
196        assert!(TokenKind::Model.is_keyword());
197        assert!(!TokenKind::Ident("foo".to_string()).is_keyword());
198    }
199
200    #[test]
201    fn test_from_ident() {
202        assert_eq!(TokenKind::from_ident("model"), TokenKind::Model);
203        assert_eq!(TokenKind::from_ident("datasource"), TokenKind::Datasource);
204        assert_eq!(
205            TokenKind::from_ident("User"),
206            TokenKind::Ident("User".to_string())
207        );
208    }
209
210    #[test]
211    fn test_token_display() {
212        let token = Token::new(TokenKind::Model, Span::new(0, 5));
213        let display = token.to_string();
214        assert!(display.contains("Model"));
215    }
216}