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