1use crate::ast::BinOp;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub struct Span {
6 pub line: u32,
7 pub col: u32,
8}
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct Token {
13 pub kind: TokenKind,
14 pub span: Span,
15}
16
17#[derive(Debug, Clone, PartialEq)]
21pub enum StrSegment {
22 Lit(String),
23 Hole(Vec<Token>),
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum TokenKind {
29 Pls,
30 Bork,
31 Bonk,
32 Bark,
33 Wow,
34 Such,
35 Much,
36 Many,
37 So,
38 Very,
39 Super,
41 OhNo,
43
44 If,
46 Elif,
47 Else,
48 For,
49 While,
50 In,
51 Return,
52 Continue,
53 And,
54 Or,
55 Not,
56 True,
57 False,
58 None,
59
60 Def,
62 Class,
63 Amaze,
64
65 Ident(String),
67 Int(i64),
68 Float(f64),
69 Str(String),
70 StrInterp(Vec<StrSegment>),
72
73 Plus,
75 Minus,
76 Star,
77 StarStar,
78 Slash,
79 SlashSlash,
80 Percent,
81 Amp,
82 Pipe,
83 Caret,
84 Tilde,
85 Shl,
86 Shr,
87 EqEq,
88 NotEq,
89 Lt,
90 LtEq,
91 Gt,
92 GtEq,
93 Eq,
94 AugAssign(BinOp),
97 Colon,
98 Bang,
99 Comma,
100 Dot,
101 LParen,
102 RParen,
103 LBracket,
104 RBracket,
105 LBrace,
106 RBrace,
107
108 Newline,
110 Indent,
111 Dedent,
112 Eof,
113}
114
115impl TokenKind {
116 pub fn describe(&self) -> String {
119 match self {
120 TokenKind::Pls
124 | TokenKind::Bork
125 | TokenKind::Bonk
126 | TokenKind::Bark
127 | TokenKind::Wow
128 | TokenKind::Such
129 | TokenKind::Much
130 | TokenKind::Many
131 | TokenKind::So
132 | TokenKind::Very
133 | TokenKind::Super
134 | TokenKind::If
135 | TokenKind::Elif
136 | TokenKind::Else
137 | TokenKind::For
138 | TokenKind::While
139 | TokenKind::In
140 | TokenKind::Return
141 | TokenKind::Continue
142 | TokenKind::And
143 | TokenKind::Or
144 | TokenKind::Not
145 | TokenKind::True
146 | TokenKind::False
147 | TokenKind::None
148 | TokenKind::Def
149 | TokenKind::Class
150 | TokenKind::Amaze => crate::keywords::keyword_spelling(self)
151 .expect("compiler bug: keyword token missing from KEYWORDS table")
152 .into(),
153 TokenKind::OhNo => "oh no".into(),
154 TokenKind::Ident(name) => format!("name '{name}'"),
155 TokenKind::Int(n) => format!("the number {n}"),
156 TokenKind::Float(f) => format!("the number {f}"),
157 TokenKind::Str(_) | TokenKind::StrInterp(_) => "a string".into(),
158 TokenKind::Plus => "+".into(),
159 TokenKind::Minus => "-".into(),
160 TokenKind::Star => "*".into(),
161 TokenKind::StarStar => "**".into(),
162 TokenKind::Slash => "/".into(),
163 TokenKind::SlashSlash => "//".into(),
164 TokenKind::Percent => "%".into(),
165 TokenKind::Amp => "&".into(),
166 TokenKind::Pipe => "|".into(),
167 TokenKind::Caret => "^".into(),
168 TokenKind::Tilde => "~".into(),
169 TokenKind::Shl => "<<".into(),
170 TokenKind::Shr => ">>".into(),
171 TokenKind::EqEq => "==".into(),
172 TokenKind::NotEq => "!=".into(),
173 TokenKind::Lt => "<".into(),
174 TokenKind::LtEq => "<=".into(),
175 TokenKind::Gt => ">".into(),
176 TokenKind::GtEq => ">=".into(),
177 TokenKind::Eq => "=".into(),
178 TokenKind::AugAssign(op) => format!("{}=", op.symbol()),
179 TokenKind::Colon => ":".into(),
180 TokenKind::Bang => "!".into(),
181 TokenKind::Comma => ",".into(),
182 TokenKind::Dot => ".".into(),
183 TokenKind::LParen => "(".into(),
184 TokenKind::RParen => ")".into(),
185 TokenKind::LBracket => "[".into(),
186 TokenKind::RBracket => "]".into(),
187 TokenKind::LBrace => "{".into(),
188 TokenKind::RBrace => "}".into(),
189 TokenKind::Newline => "the end of the line".into(),
190 TokenKind::Indent => "more indentation".into(),
191 TokenKind::Dedent => "less indentation".into(),
192 TokenKind::Eof => "the end of the script".into(),
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::keywords::{keyword_spelling, lookup, KEYWORDS};
201
202 #[test]
203 fn every_keyword_round_trips_through_lookup_and_describe() {
204 for (spelling, kind) in KEYWORDS {
205 assert_eq!(lookup(spelling).as_ref(), Some(kind));
206 assert_eq!(keyword_spelling(kind), Some(*spelling));
207 assert_eq!(kind.describe(), *spelling);
208 }
209 }
210
211 #[test]
212 fn non_keyword_tokens_have_no_keyword_spelling() {
213 assert_eq!(keyword_spelling(&TokenKind::OhNo), None);
214 assert_eq!(keyword_spelling(&TokenKind::Plus), None);
215 assert_eq!(keyword_spelling(&TokenKind::Eof), None);
216 }
217
218 #[test]
219 fn ohno_describes_as_two_words() {
220 assert_eq!(TokenKind::OhNo.describe(), "oh no");
221 }
222}