1use crate::span::Span;
4
5#[derive(Clone, Debug, PartialEq)]
6pub enum Tok {
7 Int(probl_number::Integer),
8 Float(f64),
9 Percent(f64),
11 Dice {
13 count: u32,
14 sides: u32,
15 },
16 Str(Vec<StrPart>),
17 Ident(String),
19
20 And,
22 Break,
23 Catch,
24 Chance,
25 Continue,
26 Div,
27 Else,
28 Enum,
29 False,
30 Fn,
31 For,
32 If,
33 Import,
34 In,
35 Let,
36 Loop,
37 Match,
38 Mod,
39 Not,
40 Observe,
41 Score,
42 Or,
43 Repeat,
44 Report,
45 Return,
46 Simulate,
47 True,
48 Try,
49 Type,
50 Typeof,
51 Var,
52 While,
53 With,
54
55 LParen,
57 RParen,
58 LBracket,
59 RBracket,
60 LBrace,
61 RBrace,
62 Comma,
63 Dot,
64 Colon,
65 Semi,
66 At,
67 Pipe,
68 Underscore,
69 Arrow,
71 FatArrow,
73 Tilde,
74 Assign,
75 PlusAssign,
76 MinusAssign,
77 StarAssign,
78 SlashAssign,
79 EqEq,
80 NotEq,
81 Lt,
82 Le,
83 Gt,
84 Ge,
85 Plus,
86 Minus,
87 Star,
88 Slash,
89 Caret,
90 DotDot,
91 DotDotLt,
93
94 Newline,
95 Eof,
96}
97
98#[derive(Clone, Debug, PartialEq)]
100pub enum StrPart {
101 Lit(String),
102 Expr { src: String, offset: u32 },
103}
104
105#[derive(Clone, Debug, PartialEq)]
106pub struct Token {
107 pub tok: Tok,
108 pub span: Span,
109}
110
111pub fn is_name(text: &str) -> bool {
114 let is_die = text
115 .strip_prefix('d')
116 .is_some_and(|s| !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()));
117 text.bytes()
118 .next()
119 .is_some_and(|b| b.is_ascii_alphabetic() || b == b'_')
120 && text.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
121 && text != "_"
122 && !is_die
123 && keyword(text).is_none()
124}
125
126pub fn keyword(word: &str) -> Option<Tok> {
127 Some(match word {
128 "and" => Tok::And,
129 "break" => Tok::Break,
130 "catch" => Tok::Catch,
131 "chance" => Tok::Chance,
132 "continue" => Tok::Continue,
133 "div" => Tok::Div,
134 "else" => Tok::Else,
135 "enum" => Tok::Enum,
136 "false" => Tok::False,
137 "fn" => Tok::Fn,
138 "for" => Tok::For,
139 "if" => Tok::If,
140 "import" => Tok::Import,
141 "in" => Tok::In,
142 "let" => Tok::Let,
143 "loop" => Tok::Loop,
144 "match" => Tok::Match,
145 "mod" => Tok::Mod,
146 "not" => Tok::Not,
147 "observe" => Tok::Observe,
148 "score" => Tok::Score,
149 "or" => Tok::Or,
150 "repeat" => Tok::Repeat,
151 "report" => Tok::Report,
152 "return" => Tok::Return,
153 "simulate" => Tok::Simulate,
154 "true" => Tok::True,
155 "try" => Tok::Try,
156 "type" => Tok::Type,
157 "typeof" => Tok::Typeof,
158 "var" => Tok::Var,
159 "while" => Tok::While,
160 "with" => Tok::With,
161 _ => return None,
162 })
163}
164
165impl Tok {
166 pub fn describe(&self) -> String {
168 match self {
169 Tok::Int(v) => format!("number `{v}`"),
170 Tok::Float(v) => format!("number `{v}`"),
171 Tok::Percent(v) => format!("percentage `{}%`", v * 100.0),
172 Tok::Dice { count, sides } => format!("dice `{count}d{sides}`"),
173 Tok::Str(_) => "a string".to_string(),
174 Tok::Ident(name) => format!("`{name}`"),
175 Tok::Newline => "the end of the line".to_string(),
176 Tok::Eof => "the end of the file".to_string(),
177 other => format!("`{}`", other.text()),
178 }
179 }
180
181 pub fn text(&self) -> &'static str {
183 match self {
184 Tok::And => "and",
185 Tok::Break => "break",
186 Tok::Catch => "catch",
187 Tok::Chance => "chance",
188 Tok::Continue => "continue",
189 Tok::Div => "div",
190 Tok::Else => "else",
191 Tok::Enum => "enum",
192 Tok::False => "false",
193 Tok::Fn => "fn",
194 Tok::For => "for",
195 Tok::If => "if",
196 Tok::Import => "import",
197 Tok::In => "in",
198 Tok::Let => "let",
199 Tok::Loop => "loop",
200 Tok::Match => "match",
201 Tok::Mod => "mod",
202 Tok::Not => "not",
203 Tok::Observe => "observe",
204 Tok::Score => "score",
205 Tok::Or => "or",
206 Tok::Repeat => "repeat",
207 Tok::Report => "report",
208 Tok::Return => "return",
209 Tok::Simulate => "simulate",
210 Tok::True => "true",
211 Tok::Try => "try",
212 Tok::Type => "type",
213 Tok::Typeof => "typeof",
214 Tok::Var => "var",
215 Tok::While => "while",
216 Tok::With => "with",
217 Tok::LParen => "(",
218 Tok::RParen => ")",
219 Tok::LBracket => "[",
220 Tok::RBracket => "]",
221 Tok::LBrace => "{",
222 Tok::RBrace => "}",
223 Tok::Comma => ",",
224 Tok::Dot => ".",
225 Tok::Colon => ":",
226 Tok::Semi => ";",
227 Tok::At => "@",
228 Tok::Pipe => "|",
229 Tok::Underscore => "_",
230 Tok::Arrow => "->",
231 Tok::FatArrow => "=>",
232 Tok::Tilde => "~",
233 Tok::Assign => "=",
234 Tok::PlusAssign => "+=",
235 Tok::MinusAssign => "-=",
236 Tok::StarAssign => "*=",
237 Tok::SlashAssign => "/=",
238 Tok::EqEq => "==",
239 Tok::NotEq => "!=",
240 Tok::Lt => "<",
241 Tok::Le => "<=",
242 Tok::Gt => ">",
243 Tok::Ge => ">=",
244 Tok::Plus => "+",
245 Tok::Minus => "-",
246 Tok::Star => "*",
247 Tok::Slash => "/",
248 Tok::Caret => "^",
249 Tok::DotDot => "..",
250 Tok::DotDotLt => "..<",
251 Tok::Int(_)
252 | Tok::Float(_)
253 | Tok::Percent(_)
254 | Tok::Dice { .. }
255 | Tok::Str(_)
256 | Tok::Ident(_)
257 | Tok::Newline
258 | Tok::Eof => "",
259 }
260 }
261
262 pub fn is_ident(&self, word: &str) -> bool {
263 matches!(self, Tok::Ident(name) if name == word)
264 }
265}