1#[cfg(any(feature = "serde", test))]
4use serde::{Deserialize, Serialize};
5
6use crate::rug::integer::IntegerExt64;
7use crate::rug::{Complete, Integer};
8
9use crate::Consts;
10use crate::calculation_results::{Calculation, FormatOptions};
11use crate::calculation_tasks::{CalculationBase, CalculationJob};
12use crate::parse::parse;
13
14use std::fmt::Write;
15use std::ops::*;
16#[macro_export]
17macro_rules! impl_bitwise {
18 ($s_name:ident {$($s_fields:ident),*}, $t_name:ident, $fn_name:ident) => {
19 impl $t_name for $s_name {
20 type Output = Self;
21 fn $fn_name(self, rhs: Self) -> Self {
22 Self {
23 $($s_fields: self.$s_fields.$fn_name(rhs.$s_fields),)*
24 }
25 }
26 }
27 };
28}
29#[macro_export]
30macro_rules! impl_all_bitwise {
31 ($s_name:ident {$($s_fields:ident,)*}) => {impl_all_bitwise!($s_name {$($s_fields),*});};
32 ($s_name:ident {$($s_fields:ident),*}) => {
33 impl_bitwise!($s_name {$($s_fields),*}, BitOr, bitor);
34 impl_bitwise!($s_name {$($s_fields),*}, BitXor, bitxor);
35 impl_bitwise!($s_name {$($s_fields),*}, BitAnd, bitand);
36 impl Not for $s_name {
37 type Output = Self;
38 fn not(self) -> Self {
39 Self {
40 $($s_fields: self.$s_fields.not(),)*
41 }
42 }
43 }
44 };
45}
46
47#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
54#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
55pub struct Comment<Meta, S> {
56 pub meta: Meta,
58 pub calculation_list: S,
60 pub notify: Option<String>,
62 pub status: Status,
63 pub commands: Commands,
64 pub max_length: usize,
66 pub locale: String,
67}
68pub type CommentConstructed<Meta> = Comment<Meta, String>;
70pub type CommentExtracted<Meta> = Comment<Meta, Vec<CalculationJob>>;
72pub type CommentCalculated<Meta> = Comment<Meta, Vec<Calculation>>;
74
75#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
76#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
77#[non_exhaustive]
78pub struct Status {
79 pub already_replied_or_rejected: bool,
80 pub not_replied: bool,
81 pub number_too_big_to_calculate: bool,
82 pub no_factorial: bool,
83 pub reply_would_be_too_long: bool,
84 pub factorials_found: bool,
85 pub limit_hit: bool,
86 pub nested_used: bool,
87}
88
89impl_all_bitwise!(Status {
90 already_replied_or_rejected,
91 not_replied,
92 number_too_big_to_calculate,
93 no_factorial,
94 reply_would_be_too_long,
95 factorials_found,
96 limit_hit,
97 nested_used,
98});
99#[allow(dead_code)]
100impl Status {
101 pub const NONE: Self = Self {
102 already_replied_or_rejected: false,
103 not_replied: false,
104 number_too_big_to_calculate: false,
105 no_factorial: false,
106 reply_would_be_too_long: false,
107 factorials_found: false,
108 limit_hit: false,
109 nested_used: false,
110 };
111 pub const ALREADY_REPLIED_OR_REJECTED: Self = Self {
112 already_replied_or_rejected: true,
113 ..Self::NONE
114 };
115 pub const NOT_REPLIED: Self = Self {
116 not_replied: true,
117 ..Self::NONE
118 };
119 pub const NUMBER_TOO_BIG_TO_CALCULATE: Self = Self {
120 number_too_big_to_calculate: true,
121 ..Self::NONE
122 };
123 pub const NO_FACTORIAL: Self = Self {
124 no_factorial: true,
125 ..Self::NONE
126 };
127 pub const REPLY_WOULD_BE_TOO_LONG: Self = Self {
128 reply_would_be_too_long: true,
129 ..Self::NONE
130 };
131 pub const FACTORIALS_FOUND: Self = Self {
132 factorials_found: true,
133 ..Self::NONE
134 };
135 pub const LIMIT_HIT: Self = Self {
136 limit_hit: true,
137 ..Self::NONE
138 };
139 pub const NESTED_USED: Self = Self {
140 nested_used: true,
141 ..Self::NONE
142 };
143}
144
145#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
146#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
147#[non_exhaustive]
148pub struct Commands {
149 #[cfg_attr(any(feature = "serde", test), serde(default))]
151 pub shorten: bool,
152 #[cfg_attr(any(feature = "serde", test), serde(default))]
154 pub steps: bool,
155 #[cfg_attr(any(feature = "serde", test), serde(default))]
157 pub nested: bool,
158 #[cfg_attr(any(feature = "serde", test), serde(default))]
160 pub termial: bool,
161 #[cfg_attr(any(feature = "serde", test), serde(default))]
163 pub no_note: bool,
164 #[cfg_attr(any(feature = "serde", test), serde(default))]
166 pub write_out: bool,
167}
168impl_all_bitwise!(Commands {
169 shorten,
170 steps,
171 nested,
172 termial,
173 no_note,
174 write_out,
175});
176#[allow(dead_code)]
177impl Commands {
178 pub const NONE: Self = Self {
179 shorten: false,
180 steps: false,
181 nested: false,
182 termial: false,
183 no_note: false,
184 write_out: false,
185 };
186 pub const SHORTEN: Self = Self {
187 shorten: true,
188 ..Self::NONE
189 };
190 pub const STEPS: Self = Self {
191 steps: true,
192 ..Self::NONE
193 };
194 pub const NESTED: Self = Self {
195 nested: true,
196 ..Self::NONE
197 };
198 pub const TERMIAL: Self = Self {
199 termial: true,
200 ..Self::NONE
201 };
202 pub const NO_NOTE: Self = Self {
203 no_note: true,
204 ..Self::NONE
205 };
206 pub const WRITE_OUT: Self = Self {
207 write_out: true,
208 ..Self::NONE
209 };
210}
211
212impl Commands {
213 fn contains_command_format(text: &str, command: &str) -> bool {
214 let pattern1 = format!("\\[{command}\\]");
215 let pattern2 = format!("[{command}]");
216 let pattern3 = format!("!{command}");
217 text.contains(&pattern1) || text.contains(&pattern2) || text.contains(&pattern3)
218 }
219
220 pub fn from_comment_text(text: &str) -> Self {
221 Self {
222 shorten: Self::contains_command_format(text, "short")
223 || Self::contains_command_format(text, "shorten"),
224 steps: Self::contains_command_format(text, "steps")
225 || Self::contains_command_format(text, "all"),
226 nested: Self::contains_command_format(text, "nest")
227 || Self::contains_command_format(text, "nested")
228 || Self::contains_command_format(text, "no multi")
229 || Self::contains_command_format(text, "no\\_multi")
230 || Self::contains_command_format(text, "no_multi"),
231 termial: Self::contains_command_format(text, "termial")
232 || Self::contains_command_format(text, "triangle"),
233 no_note: Self::contains_command_format(text, "no note")
234 || Self::contains_command_format(text, "no\\_note")
235 || Self::contains_command_format(text, "no_note"),
236 write_out: Self::contains_command_format(text, "write_out")
237 || Self::contains_command_format(text, "write\\_out")
238 || Self::contains_command_format(text, "write_num")
239 || Self::contains_command_format(text, "write\\_num"),
240 }
241 }
242 pub fn overrides_from_comment_text(text: &str) -> Self {
243 Self {
244 shorten: !Self::contains_command_format(text, "long"),
245 steps: !(Self::contains_command_format(text, "no steps")
246 || Self::contains_command_format(text, "no_steps")
247 || Self::contains_command_format(text, "no\\_steps")),
248 nested: !(Self::contains_command_format(text, "no_nest")
249 || Self::contains_command_format(text, "no\\_nest")
250 || Self::contains_command_format(text, "multi")),
251 termial: !(Self::contains_command_format(text, "no termial")
252 || Self::contains_command_format(text, "no_termial")
253 || Self::contains_command_format(text, "no\\_termial")),
254 no_note: !Self::contains_command_format(text, "note"),
255 write_out: !(Self::contains_command_format(text, "dont_write_out")
256 || Self::contains_command_format(text, "dont\\_write\\_out")
257 || Self::contains_command_format(text, "normal num")
258 || Self::contains_command_format(text, "normal\\_num")),
259 }
260 }
261}
262
263macro_rules! contains_comb {
264 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
266 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end,$($end_rest),*]) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
267 };
268 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
270 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
271 };
272 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
274 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end])
275 };
276 ($var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
278 $var.contains(concat!($start, $end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
279 };
280 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
282 $var.contains(concat!($start,$end))
283 };
284 (@inner $var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
286 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
287 };
288 ($var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
290 $var.contains(concat!($start, $end))
291 };
292 (@inner $var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
294 $var.contains(concat!($start,$end))
295 };
296}
297
298impl<Meta> CommentConstructed<Meta> {
299 pub fn new(
301 comment_text: &str,
302 meta: Meta,
303 pre_commands: Commands,
304 max_length: usize,
305 locale: &str,
306 ) -> Self {
307 let command_overrides = Commands::overrides_from_comment_text(comment_text);
308 let commands: Commands =
309 (Commands::from_comment_text(comment_text) | pre_commands) & command_overrides;
310
311 let mut status: Status = Default::default();
312 if Commands::contains_command_format(comment_text, "nest")
313 || Commands::contains_command_format(comment_text, "nested")
314 || Commands::contains_command_format(comment_text, "no_nest")
315 || Commands::contains_command_format(comment_text, "no\\_nest")
316 {
317 status.nested_used = true;
318 }
319
320 let text = if Self::might_have_factorial(comment_text) {
321 comment_text.to_owned()
322 } else {
323 status.no_factorial = true;
324 String::new()
325 };
326
327 Comment {
328 meta,
329 notify: None,
330 calculation_list: text,
331 status,
332 commands,
333 max_length,
334 locale: locale.to_owned(),
335 }
336 }
337
338 fn might_have_factorial(text: &str) -> bool {
339 contains_comb!(
340 text,
341 [
342 "0",
343 "1",
344 "2",
345 "3",
346 "4",
347 "5",
348 "6",
349 "7",
350 "8",
351 "9",
352 ")",
353 "e",
354 "pi",
355 "phi",
356 "tau",
357 "π",
358 "ɸ",
359 "τ",
360 "infinity",
361 "inf",
362 "∞\u{303}",
363 "∞"
364 ],
365 ["!", "?"]
366 ) || contains_comb!(
367 text,
368 ["!"],
369 [
370 "0",
371 "1",
372 "2",
373 "3",
374 "4",
375 "5",
376 "6",
377 "7",
378 "8",
379 "9",
380 "(",
381 "e",
382 "pi",
383 "phi",
384 "tau",
385 "π",
386 "ɸ",
387 "τ",
388 "infinity",
389 "inf",
390 "∞\u{303}",
391 "∞"
392 ]
393 )
394 }
395
396 pub fn extract(self, consts: &Consts) -> CommentExtracted<Meta> {
398 let Comment {
399 meta,
400 calculation_list: comment_text,
401 notify,
402 mut status,
403 commands,
404 max_length,
405 locale,
406 } = self;
407 let mut pending_list: Vec<CalculationJob> = parse(
408 &comment_text,
409 commands.termial,
410 consts,
411 &consts
412 .locales
413 .get(&locale)
414 .unwrap_or(consts.locales.get(&consts.default_locale).unwrap())
415 .format
416 .number_format,
417 );
418
419 if commands.nested {
420 for calc in &mut pending_list {
421 Self::multi_to_nested(calc);
422 }
423 }
424
425 if pending_list.is_empty() {
426 status.no_factorial = true;
427 }
428
429 Comment {
430 meta,
431 calculation_list: pending_list,
432 notify,
433 status,
434 commands,
435 max_length,
436 locale,
437 }
438 }
439
440 fn multi_to_nested(mut calc: &mut CalculationJob) {
441 loop {
442 let level = calc.level.clamp(-1, 1);
443 let depth = calc.level.abs();
444 calc.level = level;
445 for _ in 1..depth {
446 let base = std::mem::replace(
447 &mut calc.base,
448 CalculationBase::Num(
449 crate::calculation_results::CalculationResult::ComplexInfinity,
450 ),
451 );
452 let new_base = CalculationBase::Calc(Box::new(CalculationJob {
453 base,
454 level,
455 negative: 0,
456 }));
457 let _ = std::mem::replace(&mut calc.base, new_base);
458 }
459 let CalculationBase::Calc(next) = &mut calc.base else {
460 return;
461 };
462 calc = next;
463 }
464 }
465
466 pub fn new_already_replied(meta: Meta, max_length: usize, locale: &str) -> Self {
468 let text = String::new();
469 let status: Status = Status {
470 already_replied_or_rejected: true,
471 ..Default::default()
472 };
473 let commands: Commands = Default::default();
474
475 Comment {
476 meta,
477 notify: None,
478 calculation_list: text,
479 status,
480 commands,
481 max_length,
482 locale: locale.to_owned(),
483 }
484 }
485}
486impl<Meta, S> Comment<Meta, S> {
487 pub fn add_status(&mut self, status: Status) {
488 self.status = self.status | status;
489 }
490}
491impl<Meta> CommentExtracted<Meta> {
492 pub fn calc(self, consts: &Consts) -> CommentCalculated<Meta> {
494 let Comment {
495 meta,
496 calculation_list: pending_list,
497 notify,
498 mut status,
499 commands,
500 max_length,
501 locale,
502 } = self;
503 let mut calculation_list: Vec<Calculation> = pending_list
504 .into_iter()
505 .flat_map(|calc| calc.execute(commands.steps, consts))
506 .filter_map(|x| {
507 if x.is_none() {
508 status.number_too_big_to_calculate = true;
509 };
510 x
511 })
512 .collect();
513
514 calculation_list.sort();
515 calculation_list.dedup();
516 calculation_list.sort_by_key(|x| x.steps.len());
517
518 if calculation_list.is_empty() {
519 status.no_factorial = true;
520 } else {
521 status.factorials_found = true;
522 }
523 Comment {
524 meta,
525 calculation_list,
526 notify,
527 status,
528 commands,
529 max_length,
530 locale,
531 }
532 }
533}
534impl<Meta> CommentCalculated<Meta> {
535 pub fn get_reply(&self, consts: &Consts) -> String {
537 let mut fell_back = false;
538 let locale = consts.locales.get(&self.locale).unwrap_or_else(|| {
539 fell_back = true;
540 consts.locales.get(&consts.default_locale).unwrap()
541 });
542 let mut note = self
543 .notify
544 .as_ref()
545 .map(|user| locale.notes.mention.replace("{mention}", user) + "\n\n")
546 .unwrap_or_default();
547
548 if fell_back {
549 let _ = note.write_str("Sorry, I currently don't speak ");
550 let _ = note.write_str(&self.locale);
551 let _ = note.write_str(". Maybe you could [teach me](https://github.com/tolik518/factorion-bot/blob/master/CONTRIBUTING.md#translation)? \n\n");
552 }
553
554 let too_big_number = Integer::u64_pow_u64(10, self.max_length as u64).complete();
555 let too_big_number = &too_big_number;
556
557 let multiple = self.add_note(consts, locale, &mut note, too_big_number);
558
559 let mut reply = self.add_factorials(
561 consts,
562 locale,
563 ¬e,
564 too_big_number,
565 FormatOptions {
566 force_shorten: self.commands.shorten,
567 write_out: self.commands.write_out,
568 ..FormatOptions::NONE
569 },
570 );
571
572 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length
574 && !self.commands.shorten
575 && !self
576 .calculation_list
577 .iter()
578 .all(|fact| fact.is_too_long(too_big_number))
579 {
580 if note.is_empty() && !self.commands.no_note {
581 if multiple {
582 let _ = note.write_str(&locale.notes.too_big_mult);
583 } else {
584 let _ = note.write_str(&locale.notes.too_big);
585 }
586 let _ = note.write_str("\n\n");
587 };
588 reply = self.add_factorials(
589 consts,
590 locale,
591 ¬e,
592 too_big_number,
593 FormatOptions {
594 write_out: self.commands.write_out,
595 ..FormatOptions::FORCE_SHORTEN
596 },
597 );
598 }
599
600 let note = if !self.commands.no_note {
601 locale.notes.tetration.clone().into_owned() + "\n\n"
602 } else {
603 String::new()
604 };
605 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length && !self.commands.steps
607 {
608 reply = self.add_factorials(
609 consts,
610 locale,
611 ¬e,
612 too_big_number,
613 FormatOptions {
614 write_out: self.commands.write_out,
615 ..{ FormatOptions::FORCE_SHORTEN | FormatOptions::AGRESSIVE_SHORTEN }
616 },
617 );
618 }
619
620 let note = if !self.commands.no_note {
621 locale.notes.remove.clone().into_owned() + "\n\n"
622 } else {
623 String::new()
624 };
625 self.add_factorials_to_fit(consts, locale, too_big_number, &mut reply, note);
627 if !locale.bot_disclaimer.is_empty() {
628 reply.push_str("\n*^(");
629 reply.push_str(&locale.bot_disclaimer);
630 reply.push_str(")*");
631 }
632 reply
633 }
634
635 fn add_factorials_to_fit(
636 &self,
637 consts: &Consts<'_>,
638 locale: &crate::locale::Locale<'_>,
639 too_big_number: &Integer,
640 reply: &mut String,
641 note: String,
642 ) {
643 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length {
644 let mut factorial_list: Vec<String> = self
645 .calculation_list
646 .iter()
647 .map(|fact| {
648 let mut res = String::new();
649 let _ = fact.format(
650 &mut res,
651 FormatOptions {
652 agressive_shorten: !self.commands.steps,
653 write_out: self.commands.write_out,
654 ..FormatOptions::FORCE_SHORTEN
655 },
656 too_big_number,
657 consts,
658 &locale.format,
659 );
660 res
661 })
662 .collect();
663 'drop_last: {
664 while note.len()
665 + factorial_list.iter().map(|s| s.len()).sum::<usize>()
666 + locale.bot_disclaimer.len()
667 + 16
668 > self.max_length
669 {
670 factorial_list.pop();
672 if factorial_list.is_empty() {
673 *reply = locale.notes.no_post.to_string();
674 break 'drop_last;
675 }
676 }
677 *reply = factorial_list.iter().fold(note, |mut acc, factorial| {
678 let _ = acc.write_str(factorial);
679 acc
680 });
681 }
682 }
683 }
684
685 fn add_factorials(
686 &self,
687 consts: &Consts<'_>,
688 locale: &crate::locale::Locale<'_>,
689 note: &str,
690 too_big_number: &Integer,
691 format_options: FormatOptions,
692 ) -> String {
693 self.calculation_list
694 .iter()
695 .fold(note.to_owned(), |mut acc, factorial| {
696 let _ = factorial.format(
697 &mut acc,
698 format_options.clone(),
699 too_big_number,
700 consts,
701 &locale.format,
702 );
703 acc
704 })
705 }
706
707 fn add_note(
708 &self,
709 consts: &Consts<'_>,
710 locale: &crate::locale::Locale<'_>,
711 note: &mut String,
712 too_big_number: &Integer,
713 ) -> bool {
714 let multiple = self.calculation_list.len() > 1;
715 if self.status.nested_used {
716 let _ = note.write_str(locale.notes.nested_used
717 .as_ref()
718 .map(AsRef::as_ref)
719 .unwrap_or("The command \"nested\" is now \"no_multi\", which is way more clear in what it means. I'll ignore the former at some point in the future.")
720 );
721 let _ = note.write_str("\n\n");
722 }
723 if !self.commands.no_note {
724 if self.status.limit_hit {
725 let _ = note.write_str(
726 locale
727 .notes
728 .limit_hit
729 .as_ref()
730 .map(AsRef::as_ref)
731 .unwrap_or(
732 "I have repeated myself enough, I won't do that calculation again.",
733 ),
734 );
735 let _ = note.write_str("\n\n");
736 } else if self
737 .calculation_list
738 .iter()
739 .any(Calculation::is_digit_tower)
740 {
741 if multiple {
742 let _ = note.write_str(&locale.notes.tower_mult);
743 let _ = note.write_str("\n\n");
744 } else {
745 let _ = note.write_str(&locale.notes.tower);
746 let _ = note.write_str("\n\n");
747 }
748 } else if self
749 .calculation_list
750 .iter()
751 .any(Calculation::is_aproximate_digits)
752 {
753 if multiple {
754 let _ = note.write_str(&locale.notes.digits_mult);
755 let _ = note.write_str("\n\n");
756 } else {
757 let _ = note.write_str(&locale.notes.digits);
758 let _ = note.write_str("\n\n");
759 }
760 } else if self
761 .calculation_list
762 .iter()
763 .any(Calculation::is_approximate)
764 {
765 if multiple {
766 let _ = note.write_str(&locale.notes.approx_mult);
767 let _ = note.write_str("\n\n");
768 } else {
769 let _ = note.write_str(&locale.notes.approx);
770 let _ = note.write_str("\n\n");
771 }
772 } else if self.calculation_list.iter().any(Calculation::is_rounded) {
773 if multiple {
774 let _ = note.write_str(&locale.notes.round_mult);
775 let _ = note.write_str("\n\n");
776 } else {
777 let _ = note.write_str(&locale.notes.round);
778 let _ = note.write_str("\n\n");
779 }
780 } else if self
781 .calculation_list
782 .iter()
783 .any(|c| c.is_too_long(too_big_number))
784 && !(self.commands.write_out
785 && self
786 .calculation_list
787 .iter()
788 .all(|c| c.can_write_out(consts.float_precision)))
789 {
790 if multiple {
791 let _ = note.write_str(&locale.notes.too_big_mult);
792 let _ = note.write_str("\n\n");
793 } else {
794 let _ = note.write_str(&locale.notes.too_big);
795 let _ = note.write_str("\n\n");
796 }
797 } else if self.commands.write_out && self.locale != "en" {
798 let _ =
799 note.write_str("I can only write out numbers in english, so I will do that.");
800 let _ = note.write_str("\n\n");
801 }
802 }
803 multiple
804 }
805}
806
807#[cfg(test)]
808mod tests {
809 use crate::{
810 calculation_results::Number,
811 calculation_tasks::{CalculationBase, CalculationJob},
812 locale::NumFormat,
813 };
814
815 const MAX_LENGTH: usize = 10_000;
816
817 use super::*;
818
819 type Comment<S> = super::Comment<(), S>;
820
821 #[test]
822 fn test_extraction_dedup() {
823 let consts = Consts::default();
824 let jobs = parse(
825 "24! -24! 2!? (2!?)!",
826 true,
827 &consts,
828 &NumFormat { decimal: '.' },
829 );
830 assert_eq!(
831 jobs,
832 [
833 CalculationJob {
834 base: CalculationBase::Num(Number::Exact(24.into())),
835 level: 1,
836 negative: 0
837 },
838 CalculationJob {
839 base: CalculationBase::Num(Number::Exact(24.into())),
840 level: 1,
841 negative: 1
842 },
843 CalculationJob {
844 base: CalculationBase::Calc(Box::new(CalculationJob {
845 base: CalculationBase::Num(Number::Exact(2.into())),
846 level: 1,
847 negative: 0
848 })),
849 level: -1,
850 negative: 0
851 },
852 CalculationJob {
853 base: CalculationBase::Calc(Box::new(CalculationJob {
854 base: CalculationBase::Calc(Box::new(CalculationJob {
855 base: CalculationBase::Num(Number::Exact(2.into())),
856 level: 1,
857 negative: 0
858 })),
859 level: -1,
860 negative: 0
861 })),
862 level: 1,
863 negative: 0
864 }
865 ]
866 );
867 }
868
869 #[test]
870 fn test_commands_from_comment_text() {
871 let cmd1 = Commands::from_comment_text("!shorten!all !triangle !no_note !nested");
872 assert!(cmd1.shorten);
873 assert!(cmd1.steps);
874 assert!(cmd1.termial);
875 assert!(cmd1.no_note);
876 assert!(cmd1.nested);
877 let cmd2 = Commands::from_comment_text("[shorten][all] [triangle] [no_note] [nest]");
878 assert!(cmd2.shorten);
879 assert!(cmd2.steps);
880 assert!(cmd2.termial);
881 assert!(cmd2.no_note);
882 assert!(cmd2.nested);
883 let comment = r"\[shorten\]\[all\] \[triangle\] \[no_note\] \[nest\]";
884 let cmd3 = Commands::from_comment_text(comment);
885 assert!(cmd3.shorten);
886 assert!(cmd3.steps);
887 assert!(cmd3.termial);
888 assert!(cmd3.no_note);
889 assert!(cmd3.nested);
890 let cmd4 = Commands::from_comment_text("shorten all triangle no_note nest");
891 assert!(!cmd4.shorten);
892 assert!(!cmd4.steps);
893 assert!(!cmd4.termial);
894 assert!(!cmd4.no_note);
895 assert!(!cmd4.nested);
896 }
897
898 #[test]
899 fn test_commands_overrides_from_comment_text() {
900 let cmd1 = Commands::overrides_from_comment_text("long no_steps no_termial note multi");
901 assert!(cmd1.shorten);
902 assert!(cmd1.steps);
903 assert!(cmd1.termial);
904 assert!(cmd1.no_note);
905 assert!(cmd1.nested);
906 }
907
908 #[test]
909 fn test_might_have_factorial() {
910 assert!(Comment::might_have_factorial("5!"));
911 assert!(Comment::might_have_factorial("3?"));
912 assert!(!Comment::might_have_factorial("!?"));
913 }
914
915 #[test]
916 fn test_new_already_replied() {
917 let comment = Comment::new_already_replied((), MAX_LENGTH, "en");
918 assert_eq!(comment.calculation_list, "");
919 assert!(comment.status.already_replied_or_rejected);
920 }
921
922 #[test]
923 fn test_locale_fallback_note() {
924 let consts = Consts::default();
925 let comment = Comment::new_already_replied((), MAX_LENGTH, "n/a")
926 .extract(&consts)
927 .calc(&consts);
928 let reply = comment.get_reply(&consts);
929 assert_eq!(
930 reply,
931 "Sorry, I currently don't speak n/a. Maybe you could [teach me](https://github.com/tolik518/factorion-bot/blob/master/CONTRIBUTING.md#translation)? \n\n\n*^(This action was performed by a bot | [Source code](http://f.r0.fyi))*"
932 );
933 }
934
935 #[test]
936 fn test_limit_hit_note() {
937 let consts = Consts::default();
938 let mut comment = Comment::new_already_replied((), MAX_LENGTH, "en")
939 .extract(&consts)
940 .calc(&consts);
941 comment.add_status(Status::LIMIT_HIT);
942 let reply = comment.get_reply(&consts);
943 assert_eq!(
944 reply,
945 "I have repeated myself enough, I won't do that calculation again.\n\n\n*^(This action was performed by a bot | [Source code](http://f.r0.fyi))*"
946 );
947 }
948
949 #[test]
950 fn test_write_out_unsupported_note() {
951 let consts = Consts::default();
952 let comment = Comment::new("1!", (), Commands::WRITE_OUT, MAX_LENGTH, "de")
953 .extract(&consts)
954 .calc(&consts);
955 let reply = comment.get_reply(&consts);
956 assert_eq!(
957 reply,
958 "I can only write out numbers in english, so I will do that.\n\nFakultät von one ist one \n\n\n*^(Dieser Kommentar wurde automatisch geschrieben | [Quelltext](http://f.r0.fyi))*"
959 );
960 }
961
962 #[test]
963 fn test_nested_used_note() {
964 let consts = Consts::default();
965 let comment = Comment::new("n!!!!! !nested", (), Commands::NONE, MAX_LENGTH, "en")
966 .extract(&consts)
967 .calc(&consts);
968 let reply = comment.get_reply(&consts);
969 assert_eq!(
970 reply,
971 "The command \"nested\" is now \"no_multi\", which is way more clear in what it means. I'll ignore the former at some point in the future.\n\n\n*^(This action was performed by a bot | [Source code](http://f.r0.fyi))*"
972 );
973 }
974}