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}