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