Skip to main content

elm_ast/
token.rs

1use crate::literal::Literal;
2
3/// A token produced by the Elm lexer.
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(Clone, Debug, PartialEq)]
6pub enum Token {
7    // ── Keywords ─────────────────────────────────────────────────────
8    /// `module`
9    Module,
10    /// `where`
11    Where,
12    /// `import`
13    Import,
14    /// `as`
15    As,
16    /// `exposing`
17    Exposing,
18    /// `type`
19    Type,
20    /// `alias`
21    Alias,
22    /// `port`
23    Port,
24    /// `if`
25    If,
26    /// `then`
27    Then,
28    /// `else`
29    Else,
30    /// `case`
31    Case,
32    /// `of`
33    Of,
34    /// `let`
35    Let,
36    /// `in`
37    In,
38    /// `infix`
39    Infix,
40
41    // ── Delimiters ───────────────────────────────────────────────────
42    /// `(`
43    LeftParen,
44    /// `)`
45    RightParen,
46    /// `[`
47    LeftBracket,
48    /// `]`
49    RightBracket,
50    /// `{`
51    LeftBrace,
52    /// `}`
53    RightBrace,
54    /// `,`
55    Comma,
56    /// `|`
57    Pipe,
58    /// `=`
59    Equals,
60    /// `:`
61    Colon,
62    /// `.`
63    Dot,
64    /// `..`
65    DotDot,
66    /// `\`
67    Backslash,
68    /// `_`
69    Underscore,
70    /// `->`
71    Arrow,
72
73    // ── Operators ────────────────────────────────────────────────────
74    /// Any operator: `+`, `-`, `*`, `/`, `//`, `^`, `++`, `::`, `<|`, `|>`,
75    /// `>>`, `<<`, `==`, `/=`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `</>`, etc.
76    ///
77    /// We store operators as strings rather than individual variants because
78    /// Elm's operator set is extensible (via `infix` declarations in core
79    /// packages), and the parser handles precedence/associativity.
80    Operator(String),
81
82    /// `-` when used as prefix negation (contextually disambiguated from
83    /// the `-` operator).
84    Minus,
85
86    // ── Identifiers ──────────────────────────────────────────────────
87    /// A lowercase identifier: `foo`, `myFunction`, `x1`
88    LowerName(String),
89
90    /// An uppercase identifier: `Maybe`, `Cmd`, `Html`
91    UpperName(String),
92
93    // ── Literals ─────────────────────────────────────────────────────
94    /// A literal value (char, string, int, hex, float).
95    Literal(Literal),
96
97    // ── Comments ─────────────────────────────────────────────────────
98    /// A single-line comment: `-- ...`
99    LineComment(String),
100
101    /// A block comment: `{- ... -}` (may be nested)
102    BlockComment(String),
103
104    /// A documentation comment: `{-| ... -}`
105    DocComment(String),
106
107    // ── Special ──────────────────────────────────────────────────────
108    /// A GLSL shader block: `[glsl| ... |]`
109    Glsl(String),
110
111    /// A newline. The lexer emits these so the parser can track
112    /// indentation-sensitive layout.
113    Newline,
114
115    /// End of file.
116    Eof,
117}
118
119// Manual Eq because Token contains Literal which contains f64.
120impl Eq for Token {}
121
122impl Token {
123    /// Look up a keyword from a lowercase identifier string.
124    /// Returns `None` if the string is not a keyword.
125    pub fn keyword(s: &str) -> Option<Token> {
126        match s {
127            "module" => Some(Token::Module),
128            "where" => Some(Token::Where),
129            "import" => Some(Token::Import),
130            "as" => Some(Token::As),
131            "exposing" => Some(Token::Exposing),
132            "type" => Some(Token::Type),
133            "alias" => Some(Token::Alias),
134            "port" => Some(Token::Port),
135            // Note: `effect` is NOT a keyword — it is only contextual in
136            // `effect module` declarations and is a valid identifier
137            // everywhere else, just like `left`, `right`, `non`.
138            "if" => Some(Token::If),
139            "then" => Some(Token::Then),
140            "else" => Some(Token::Else),
141            "case" => Some(Token::Case),
142            "of" => Some(Token::Of),
143            "let" => Some(Token::Let),
144            "in" => Some(Token::In),
145            "infix" => Some(Token::Infix),
146            // Note: `left`, `right`, `non` are NOT keywords — they are only
147            // contextual in `infix` declarations and are valid identifiers
148            // everywhere else (e.g., `String.left`, `Dict` node fields).
149            _ => None,
150        }
151    }
152
153    /// Returns `true` if this token is a comment.
154    pub fn is_comment(&self) -> bool {
155        matches!(
156            self,
157            Token::LineComment(_) | Token::BlockComment(_) | Token::DocComment(_)
158        )
159    }
160
161    /// Returns `true` if this token is whitespace or a newline.
162    pub fn is_whitespace(&self) -> bool {
163        matches!(self, Token::Newline)
164    }
165}