1use harper_brill::UPOS;
2use is_macro::Is;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 DictWordMetadata, Number, Punctuation, Quote, TokenKind::Word, dict_word_metadata::Person,
7};
8
9macro_rules! delegate_to_metadata {
12 ($($method:ident),* $(,)?) => {
13 $(
14 #[doc = concat!(
15 "Delegates to [`DictWordMetadata::",
16 stringify!($method),
17 "`] when this token is a word.\n\n",
18 "Returns `false` if the token is not a word."
19 )]
20 pub fn $method(&self) -> bool {
21 let Word(Some(metadata)) = self else {
22 return false;
23 };
24 metadata.$method()
25 }
26 )*
27 };
28}
29
30#[derive(Debug, Is, Clone, Serialize, Deserialize, Default, PartialOrd, Hash, Eq, PartialEq)]
35#[serde(tag = "kind", content = "value")]
36pub enum TokenKind {
37 Word(Option<DictWordMetadata>),
39 Punctuation(Punctuation),
40 Decade,
41 Number(Number),
42 Space(usize),
44 Newline(usize),
46 EmailAddress,
47 Url,
48 Hostname,
49 #[default]
52 Unlintable,
53 ParagraphBreak,
54 Regexish,
55 HeadingStart,
56}
57
58impl TokenKind {
59 delegate_to_metadata! {
61 is_nominal,
63 is_noun,
64 is_pronoun,
65 is_proper_noun,
66 is_singular_nominal,
67 is_plural_nominal,
68 is_possessive_nominal,
69 is_non_plural_nominal,
70 is_singular_noun,
71 is_singular_noun_only,
72 is_plural_noun,
73 is_plural_noun_only,
74 is_non_plural_noun,
75 is_non_possessive_noun,
76 is_countable_noun,
77 is_non_countable_noun,
78 is_mass_noun,
79 is_mass_noun_only,
80 is_non_mass_noun,
81 is_singular_pronoun,
82 is_plural_pronoun,
83 is_non_plural_pronoun,
84 is_reflexive_pronoun,
85 is_personal_pronoun,
86 is_first_person_singular_pronoun,
87 is_first_person_plural_pronoun,
88 is_second_person_pronoun,
89 is_third_person_pronoun,
90 is_third_person_singular_pronoun,
91 is_third_person_plural_pronoun,
92 is_subject_pronoun,
93 is_object_pronoun,
94 is_possessive_noun,
95 is_possessive_pronoun,
97
98 is_verb,
100 is_auxiliary_verb,
101 is_linking_verb,
102 is_verb_lemma,
103 is_verb_past_form,
104 is_verb_regular_past_form,
105 is_verb_simple_past_form,
106 is_verb_past_participle_form,
107 is_verb_simple_past_only,
108 is_verb_past_participle_only,
109 is_verb_progressive_form,
110 is_verb_third_person_singular_present_form,
111
112 is_adjective,
114 is_comparative_adjective,
115 is_superlative_adjective,
116 is_positive_adjective,
117
118 is_adverb,
120 is_manner_adverb,
121 is_frequency_adverb,
122 is_degree_adverb,
123
124 is_determiner,
126 is_demonstrative_determiner,
127 is_possessive_determiner,
128 is_quantifier,
129 is_non_quantifier_determiner,
130 is_non_demonstrative_determiner,
131
132 is_conjunction,
134
135 is_swear,
137 is_abbreviation,
138 is_likely_homograph,
139
140 is_lowercase,
142 is_titlecase,
143 is_allcaps,
144 is_lower_camel,
145 is_upper_camel,
146 is_apostrophized,
147
148 is_roman_numerals
149 }
150
151 pub fn get_pronoun_person(&self) -> Option<Person> {
152 let Word(Some(metadata)) = self else {
153 return None;
154 };
155 metadata.get_person()
156 }
157
158 pub fn is_preposition(&self) -> bool {
160 let Word(Some(metadata)) = self else {
161 return false;
162 };
163 metadata.preposition
164 }
165
166 pub fn is_common_word(&self) -> bool {
169 let Word(Some(metadata)) = self else {
170 return true;
171 };
172 metadata.common
173 }
174
175 pub fn is_np_member(&self) -> bool {
177 let Word(Some(metadata)) = self else {
178 return false;
179 };
180 metadata.np_member.unwrap_or(false)
181 }
182
183 pub fn is_oov(&self) -> bool {
188 matches!(self, TokenKind::Word(None))
189 }
190
191 pub fn is_cardinal_number(&self) -> bool {
194 matches!(self, TokenKind::Number(Number { suffix: None, .. }))
195 }
196
197 pub fn is_ordinal_number(&self) -> bool {
198 matches!(
199 self,
200 TokenKind::Number(Number {
201 suffix: Some(_),
202 ..
203 })
204 )
205 }
206
207 pub fn is_open_square(&self) -> bool {
210 matches!(self, TokenKind::Punctuation(Punctuation::OpenSquare))
211 }
212
213 pub fn is_close_square(&self) -> bool {
214 matches!(self, TokenKind::Punctuation(Punctuation::CloseSquare))
215 }
216
217 pub fn is_less_than(&self) -> bool {
218 matches!(self, TokenKind::Punctuation(Punctuation::LessThan))
219 }
220
221 pub fn is_greater_than(&self) -> bool {
222 matches!(self, TokenKind::Punctuation(Punctuation::GreaterThan))
223 }
224
225 pub fn is_open_round(&self) -> bool {
226 matches!(self, TokenKind::Punctuation(Punctuation::OpenRound))
227 }
228
229 pub fn is_close_round(&self) -> bool {
230 matches!(self, TokenKind::Punctuation(Punctuation::CloseRound))
231 }
232
233 pub fn is_pipe(&self) -> bool {
234 matches!(self, TokenKind::Punctuation(Punctuation::Pipe))
235 }
236
237 pub fn is_currency(&self) -> bool {
238 matches!(self, TokenKind::Punctuation(Punctuation::Currency(..)))
239 }
240
241 pub fn is_ellipsis(&self) -> bool {
242 matches!(self, TokenKind::Punctuation(Punctuation::Ellipsis))
243 }
244
245 pub fn is_hyphen(&self) -> bool {
247 matches!(self, TokenKind::Punctuation(Punctuation::Hyphen))
248 }
249
250 pub fn is_plus(&self) -> bool {
251 matches!(self, TokenKind::Punctuation(Punctuation::Plus))
252 }
253
254 pub fn is_quote(&self) -> bool {
255 matches!(self, TokenKind::Punctuation(Punctuation::Quote(_)))
256 }
257
258 pub fn is_apostrophe(&self) -> bool {
259 matches!(self, TokenKind::Punctuation(Punctuation::Apostrophe))
260 }
261
262 pub fn is_period(&self) -> bool {
263 matches!(self, TokenKind::Punctuation(Punctuation::Period))
264 }
265
266 pub fn is_at(&self) -> bool {
267 matches!(self, TokenKind::Punctuation(Punctuation::At))
268 }
269
270 pub fn is_comma(&self) -> bool {
271 matches!(self, TokenKind::Punctuation(Punctuation::Comma))
272 }
273
274 pub fn is_semicolon(&self) -> bool {
275 matches!(self, TokenKind::Punctuation(Punctuation::Semicolon))
276 }
277
278 pub fn is_acute(&self) -> bool {
279 matches!(self, TokenKind::Punctuation(Punctuation::Acute))
280 }
281
282 pub fn is_ampersand(&self) -> bool {
283 matches!(self, TokenKind::Punctuation(Punctuation::Ampersand))
284 }
285
286 pub fn is_backslash(&self) -> bool {
287 matches!(self, TokenKind::Punctuation(Punctuation::Backslash))
288 }
289
290 pub fn is_slash(&self) -> bool {
291 matches!(self, TokenKind::Punctuation(Punctuation::ForwardSlash))
292 }
293
294 pub fn is_percent(&self) -> bool {
295 matches!(self, TokenKind::Punctuation(Punctuation::Percent))
296 }
297
298 pub fn is_degree(&self) -> bool {
299 matches!(self, TokenKind::Punctuation(Punctuation::Degree))
300 }
301
302 pub fn is_open_single(&self) -> bool {
303 matches!(self, TokenKind::Punctuation(Punctuation::OpenSingle))
304 }
305
306 pub fn is_single_prime(&self) -> bool {
307 matches!(self, TokenKind::Punctuation(Punctuation::SinglePrime))
308 }
309
310 pub fn is_double_prime(&self) -> bool {
311 matches!(self, TokenKind::Punctuation(Punctuation::DoublePrime))
312 }
313
314 pub fn is_backtick(&self) -> bool {
315 matches!(self, TokenKind::Punctuation(Punctuation::Backtick))
316 }
317
318 pub fn is_word_like(&self) -> bool {
323 matches!(
324 self,
325 TokenKind::Word(..)
326 | TokenKind::EmailAddress
327 | TokenKind::Hostname
328 | TokenKind::Decade
329 | TokenKind::Number(..)
330 )
331 }
332
333 pub(crate) fn is_chunk_terminator(&self) -> bool {
334 if self.is_sentence_terminator() {
335 return true;
336 }
337
338 match self {
339 TokenKind::Punctuation(punct) => {
340 matches!(
341 punct,
342 Punctuation::Comma
343 | Punctuation::Semicolon
344 | Punctuation::Quote { .. }
345 | Punctuation::Colon
346 )
347 }
348 _ => false,
349 }
350 }
351
352 pub fn is_sentence_terminator(&self) -> bool {
353 match self {
354 TokenKind::Punctuation(punct) => [
355 Punctuation::Period,
356 Punctuation::Bang,
357 Punctuation::Question,
358 ]
359 .contains(punct),
360 TokenKind::ParagraphBreak => true,
361 _ => false,
362 }
363 }
364
365 pub fn is_case_separator(&self) -> bool {
369 matches!(self, TokenKind::Punctuation(Punctuation::Underscore))
370 || matches!(self, TokenKind::Punctuation(Punctuation::Hyphen))
371 }
372
373 pub fn is_whitespace(&self) -> bool {
375 matches!(self, TokenKind::Space(_) | TokenKind::Newline(_))
376 }
377
378 pub fn is_upos(&self, upos: UPOS) -> bool {
379 let Some(Some(meta)) = self.as_word() else {
380 return false;
381 };
382
383 meta.pos_tag == Some(upos)
384 }
385
386 pub fn matches_variant_of(&self, other: &Self) -> bool {
391 self.with_default_data() == other.with_default_data()
392 }
393
394 pub fn with_default_data(&self) -> Self {
398 match self {
399 TokenKind::Word(_) => TokenKind::Word(Default::default()),
400 TokenKind::Punctuation(_) => TokenKind::Punctuation(Default::default()),
401 TokenKind::Number(..) => TokenKind::Number(Default::default()),
402 TokenKind::Space(_) => TokenKind::Space(Default::default()),
403 TokenKind::Newline(_) => TokenKind::Newline(Default::default()),
404 _ => self.clone(),
405 }
406 }
407
408 pub fn blank_word() -> Self {
410 Self::Word(None)
411 }
412
413 pub fn as_mut_quote(&mut self) -> Option<&mut Quote> {
416 self.as_mut_punctuation()?.as_mut_quote()
417 }
418
419 pub fn as_quote(&self) -> Option<&Quote> {
420 self.as_punctuation()?.as_quote()
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use crate::Document;
427
428 #[test]
429 fn car_is_singular_noun() {
430 let doc = Document::new_plain_english_curated("car");
431 let tk = &doc.tokens().next().unwrap().kind;
432 assert!(tk.is_singular_noun());
433 }
434
435 #[test]
436 fn traffic_is_mass_noun_only() {
437 let doc = Document::new_plain_english_curated("traffic");
438 let tk = &doc.tokens().next().unwrap().kind;
439 assert!(tk.is_mass_noun_only());
440 }
441
442 #[test]
443 fn equipment_is_mass_noun() {
444 let doc = Document::new_plain_english_curated("equipment");
445 let tk = &doc.tokens().next().unwrap().kind;
446 assert!(tk.is_mass_noun());
447 }
448
449 #[test]
450 fn equipment_is_non_countable_noun() {
451 let doc = Document::new_plain_english_curated("equipment");
452 let tk = &doc.tokens().next().unwrap().kind;
453 assert!(tk.is_non_countable_noun());
454 }
455
456 #[test]
457 fn equipment_isnt_countable_noun() {
458 let doc = Document::new_plain_english_curated("equipment");
459 let tk = &doc.tokens().next().unwrap().kind;
460 assert!(!tk.is_countable_noun());
461 }
462
463 #[test]
464 fn ate_is_simple_past_only() {
465 let doc = Document::new_plain_english_curated("ate");
466 let tk = &doc.tokens().next().unwrap().kind;
467 assert!(tk.is_verb_simple_past_only());
468 assert!(!tk.is_verb_past_participle_only());
469 }
470
471 #[test]
472 fn eaten_is_past_participle_only() {
473 let doc = Document::new_plain_english_curated("eaten");
474 let tk = &doc.tokens().next().unwrap().kind;
475 assert!(tk.is_verb_past_participle_only());
476 assert!(!tk.is_verb_simple_past_only());
477 }
478
479 #[test]
480 fn thought_is_regular_past_form() {
481 let doc = Document::new_plain_english_curated("thought");
482 let tk = &doc.tokens().next().unwrap().kind;
483 assert!(tk.is_verb_regular_past_form());
484 }
485
486 #[test]
487 fn oov_word_is_oov() {
488 let doc = Document::new_plain_english_curated("nonexistentword");
489 let tk = &doc.tokens().next().unwrap().kind;
490 assert!(tk.is_oov());
491 }
492
493 #[test]
494 fn known_word_is_not_oov() {
495 let doc = Document::new_plain_english_curated("car");
496 let tk = &doc.tokens().next().unwrap().kind;
497 assert!(!tk.is_oov());
498 }
499
500 #[test]
501 fn non_word_tokens_are_not_oov() {
502 let doc = Document::new_plain_english_curated("Hello, world!");
503 let tokens: Vec<_> = doc.tokens().collect();
504
505 assert!(!tokens[1].kind.is_oov());
507 assert!(!tokens[3].kind.is_oov());
509 }
510}