1use std::sync::{LazyLock, OnceLock};
3
4use crate::rug::{Complete, Float, Integer, integer::IntegerExt64};
5
6use crate::{
7 FLOAT_PRECISION,
8 calculation_results::Number,
9 calculation_tasks::{CalculationBase, CalculationJob},
10};
11
12pub mod recommended {
13 use factorion_math::rug::Integer;
14
15 pub static INTEGER_CONSTRUCTION_LIMIT: fn() -> Integer = || 100_000_000u128.into();
16}
17static INTEGER_CONSTRUCTION_LIMIT: OnceLock<Integer> = OnceLock::new();
18
19use crate::AlreadyInit;
20pub fn init(integer_construction_limit: Integer) -> Result<(), AlreadyInit> {
21 static INITIALIZING: std::sync::Mutex<()> = std::sync::Mutex::new(());
22 let _guard = INITIALIZING.lock();
23 INTEGER_CONSTRUCTION_LIMIT
24 .set(integer_construction_limit)
25 .map_err(|_| AlreadyInit)?;
26 Ok(())
27}
28pub fn init_default() -> Result<(), AlreadyInit> {
29 init(recommended::INTEGER_CONSTRUCTION_LIMIT())
30}
31
32const POI_STARTS: &[char] = &[
33 NEGATION,
34 '!', '.', ESCAPE,
37 '0', '1',
39 '2',
40 '3',
41 '4',
42 '5',
43 '6',
44 '7',
45 '8',
46 '9',
47 'p', 'e',
49 't',
50 'π',
51 'ɸ',
52 'τ',
53 URI_POI,
54 SPOILER_POI,
55 SPOILER_HTML_POI,
56 PAREN_START,
57 PAREN_END,
58];
59
60const NEGATION: char = '-';
61const PAREN_START: char = '(';
62const PAREN_END: char = ')';
63const ESCAPE: char = '\\';
64const URI_START: &str = "://";
65const URI_POI: char = ':';
66const SPOILER_START: &str = ">!";
67const SPOILER_END: &str = "!<";
68const SPOILER_POI: char = '>';
69const SPOILER_HTML_START: &str = ">!";
70const SPOILER_HTML_END: &str = "!<";
71const SPOILER_HTML_POI: char = '&';
72
73const CONSTANT_STARTS: &[char] = &['p', 'e', 't', 'π', 'ɸ', 'τ'];
74static E: LazyLock<Number> = LazyLock::new(|| {
75 Number::Float(
76 Float::with_val(
77 *FLOAT_PRECISION
78 .get()
79 .expect("FLOAT_PRECISION uninitialized"),
80 1,
81 )
82 .exp()
83 .into(),
84 )
85});
86static PHI: LazyLock<Number> = LazyLock::new(|| {
87 Number::Float(Float::into(
88 ((1.0
89 + Float::with_val(
90 *FLOAT_PRECISION
91 .get()
92 .expect("FLOAT_PRECISION uninitialized"),
93 5,
94 )
95 .sqrt()) as Float)
96 / 2.0,
97 ))
98});
99static PI: LazyLock<Number> = LazyLock::new(|| {
100 Number::Float(
101 Float::with_val(
102 *FLOAT_PRECISION
103 .get()
104 .expect("FLOAT_PRECISION uninitialized"),
105 crate::rug::float::Constant::Pi,
106 )
107 .into(),
108 )
109});
110static TAU: LazyLock<Number> = LazyLock::new(|| {
111 Number::Float(Float::into(
112 Float::with_val(
113 *FLOAT_PRECISION
114 .get()
115 .expect("FLOAT_PRECISION uninitialized"),
116 crate::rug::float::Constant::Pi,
117 ) * 2.0,
118 ))
119});
120
121const PREFIX_OPS: [char; 1] = ['!'];
122#[allow(dead_code)]
123const POSTFIX_OPS: [char; 2] = ['!', '?'];
124
125const INTEGER_ONLY_OPS: [i32; 1] = [0];
126
127pub fn parse(mut text: &str, do_termial: bool) -> Vec<CalculationJob> {
128 let mut jobs = Vec::new();
172 let mut base: Option<CalculationBase> = None;
173 let mut paren_steps: Vec<(u32, Option<i32>, bool)> = Vec::new();
174 let mut current_negative: u32 = 0;
175 let mut last_len = usize::MAX;
176 let mut had_text_before = false;
177 while !text.is_empty() {
178 if last_len == text.len() {
179 panic!("Parser caught in a loop! Text: \"{text}\"")
180 }
181 last_len = text.len();
182
183 text = text.trim_start();
184 if text.len() != last_len {
185 current_negative = 0;
186 }
187 let Some(position_of_interest) = text.find(POI_STARTS) else {
189 break;
190 };
191 if position_of_interest != 0 {
192 if let Some(step) = paren_steps.last_mut() {
194 step.2 = true;
195 }
196 current_negative = 0;
197 had_text_before = false;
198 }
199 let had_text =
200 text[..position_of_interest].ends_with(char::is_alphabetic) || had_text_before;
201 had_text_before = false;
202 text = &text[position_of_interest..];
204 if text.starts_with(ESCAPE) {
205 text = &text[1..];
207 let end = if text.starts_with(SPOILER_START) {
208 1
209 } else if text.starts_with(SPOILER_HTML_START) {
210 4
211 } else if text.starts_with(URI_START) {
212 3
213 } else {
214 0
215 };
216 text = &text[end..];
217 continue;
218 } else if text.starts_with(URI_START) {
219 let end = text.find(char::is_whitespace).unwrap_or(text.len());
221 text = &text[end..];
222 continue;
223 } else if text.starts_with(SPOILER_START) {
224 let mut end = 0;
226 loop {
227 if let Some(e) = text[end..].find(SPOILER_END) {
229 if e == 0 {
230 panic!("Parser loop Spoiler! Text \"{text}\"");
231 }
232 end += e;
233 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
235 end += 1;
236 continue;
237 }
238 break;
239 } else {
240 end = 0;
242 break;
243 }
244 }
245 current_negative = 0;
246 text = &text[end + 1..];
247 continue;
248 } else if text.starts_with(SPOILER_HTML_START) {
249 let mut end = 0;
251 loop {
252 if let Some(e) = text[end..].find(SPOILER_HTML_END) {
254 if e == 0 {
255 panic!("Parser loop Spoiler! Text \"{text}\"");
256 }
257 end += e;
258 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
260 end += 1;
261 continue;
262 }
263 break;
264 } else {
265 end = 0;
267 break;
268 }
269 }
270 current_negative = 0;
271 text = &text[end + 4..];
272 continue;
273 } else if text.starts_with(NEGATION) {
274 let end = text.find(|c| c != NEGATION).unwrap_or(text.len());
276 current_negative = end as u32;
277 text = &text[end..];
278 continue;
279 } else if text.starts_with(PAREN_START) {
280 paren_steps.push((current_negative, None, false));
282 if let Some(CalculationBase::Calc(job)) = base.take() {
284 jobs.push(*job);
285 }
286 current_negative = 0;
287 text = &text[1..];
288 continue;
289 } else if text.starts_with(PAREN_END) {
290 text = &text[1..];
292 current_negative = 0;
293 let Some(step) = paren_steps.pop() else {
295 continue;
296 };
297 if step.2 {
299 if let Some(CalculationBase::Calc(job)) = base.take() {
300 jobs.push(*job);
301 }
302 if let Some(step) = paren_steps.last_mut() {
304 step.2 = true;
305 }
306 continue;
307 }
308 let mut had_op = false;
309 if let Some(level) = step.1 {
311 let Some(inner) = base.take() else {
313 if let Some(step) = paren_steps.last_mut() {
315 step.2 = true;
316 }
317 continue;
318 };
319 if let (CalculationBase::Num(Number::Float(_)), true) =
320 (&inner, INTEGER_ONLY_OPS.contains(&level))
321 {
322 continue;
323 }
324 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
325 base: inner,
326 level,
327 negative: 0,
328 })));
329 had_op = true;
330 }
331 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
333 base.take();
334 if let Some(step) = paren_steps.last_mut() {
336 step.2 = true;
337 }
338 continue;
339 };
340 if !levels.is_empty() {
341 for level in levels {
343 let Some(inner) = base.take() else {
345 continue;
346 };
347 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
348 base: inner,
349 level,
350 negative: 0,
351 })));
352 had_op = true;
353 }
354 }
355 if !had_op {
356 match &mut base {
357 Some(CalculationBase::Calc(job)) => job.negative += step.0,
358 Some(CalculationBase::Num(n)) => {
359 if step.0 % 2 != 0 {
360 n.negate();
361 }
362 }
363 None => {}
364 }
365 } else {
366 match &mut base {
367 Some(CalculationBase::Num(n)) => {
368 if step.0 % 2 == 1 {
369 n.negate();
370 }
371 }
372 Some(CalculationBase::Calc(job)) => job.negative += step.0,
373 None => {
374 if let Some(step) = paren_steps.last_mut() {
376 step.2 = true;
377 }
378 }
379 }
380 continue;
381 };
382 } else if text.starts_with(PREFIX_OPS) {
383 let Ok(level) = parse_op(&mut text, true, do_termial) else {
385 parse_num(&mut text, false, true);
387 continue;
388 };
389 if let Some(num) = parse_num(&mut text, false, true) {
391 if let Some(CalculationBase::Calc(job)) = base.take() {
393 if let Some(step) = paren_steps.last_mut() {
395 step.2 = true;
396 }
397 jobs.push(*job);
398 }
399 if let (Number::Float(_), true) = (&num, INTEGER_ONLY_OPS.contains(&level)) {
400 continue;
401 }
402 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
403 base: CalculationBase::Num(num),
404 level,
405 negative: current_negative,
406 })));
407 current_negative = 0;
408 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
409 continue;
410 };
411 for level in levels {
412 let Some(inner) = base.take() else {
414 continue;
415 };
416 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
417 base: inner,
418 level,
419 negative: 0,
420 })));
421 }
422 } else {
423 if text.starts_with(PAREN_START) {
425 paren_steps.push((current_negative, Some(level), false));
426 current_negative = 0;
427 text = &text[1..];
428 }
429 continue;
430 };
431 } else {
432 let Some(num) = parse_num(&mut text, had_text, false) else {
434 had_text_before = true;
435 let mut end = 1;
437 while !text.is_char_boundary(end) && end < text.len() {
438 end += 1;
439 }
440 text = &text[end.min(text.len())..];
441 continue;
442 };
443 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
445 continue;
446 };
447 if !levels.is_empty() {
448 let levels = levels.into_iter();
449 if let Some(CalculationBase::Calc(job)) = base.take() {
450 if let Some(step) = paren_steps.last_mut() {
452 step.2 = true;
453 }
454 jobs.push(*job);
455 }
456 base = Some(CalculationBase::Num(num));
457 for level in levels {
458 let previous = base.take().unwrap();
459 if let (CalculationBase::Num(Number::Float(_)), true) =
460 (&previous, INTEGER_ONLY_OPS.contains(&level))
461 {
462 continue;
463 }
464 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
465 base: previous,
466 level,
467 negative: 0,
468 })))
469 }
470 if let Some(CalculationBase::Calc(job)) = &mut base {
471 job.negative = current_negative;
472 }
473 } else {
474 if !paren_steps.is_empty() {
476 let mut num = num;
477 if current_negative % 2 == 1 {
478 num.negate();
479 }
480
481 if base.is_none() {
482 base = Some(CalculationBase::Num(num))
483 } else {
484 if let Some(step) = paren_steps.last_mut() {
486 step.2 = true;
487 }
488 }
489 }
490 }
491 current_negative = 0;
492 };
493 if paren_steps.is_empty() {
495 if let Some(CalculationBase::Calc(job)) = base.take() {
496 jobs.push(*job);
497 }
498 }
499 }
500 if let Some(CalculationBase::Calc(job)) = base.take() {
501 jobs.push(*job);
502 }
503 jobs.sort();
504 jobs.dedup();
505 jobs
506}
507
508enum ParseOpErr {
509 NonOp,
510 InvalidOp,
511}
512
513fn parse_op(text: &mut &str, prefix: bool, do_termial: bool) -> Result<i32, ParseOpErr> {
514 let op = text.chars().next().ok_or(ParseOpErr::NonOp)?;
515 let end = text.find(|c| c != op).unwrap_or(text.len());
516 let res = match op {
517 '!' => {
518 if prefix {
519 if end != 1 {
520 Err(ParseOpErr::InvalidOp)
521 } else {
522 Ok(0)
523 }
524 } else {
525 Ok(end as i32)
526 }
527 }
528 '?' => {
529 if !do_termial {
530 Err(ParseOpErr::NonOp)
531 } else if prefix {
532 Err(ParseOpErr::InvalidOp)
533 } else {
534 Ok(-(end as i32))
535 }
536 }
537 _ => return Err(ParseOpErr::NonOp),
538 };
539 *text = &text[end..];
540 res
541}
542
543fn parse_ops(text: &mut &str, prefix: bool, do_termial: bool) -> Option<Vec<i32>> {
544 let mut res = Vec::new();
545 loop {
546 match parse_op(text, prefix, do_termial) {
547 Ok(op) => res.push(op),
548 Err(ParseOpErr::NonOp) => break,
549 Err(ParseOpErr::InvalidOp) => return None,
550 }
551 }
552 Some(res)
553}
554
555fn parse_num(text: &mut &str, had_text: bool, had_op: bool) -> Option<Number> {
556 let prec = *FLOAT_PRECISION
557 .get()
558 .expect("FLOAT_PRECISION uninitialized");
559 if text.starts_with(CONSTANT_STARTS) {
560 let (n, x) = if text.starts_with("pi") {
561 ("pi".len(), PI.clone())
562 } else if text.starts_with("π") {
563 ("π".len(), PI.clone())
564 } else if text.starts_with("phi") {
565 ("phi".len(), PHI.clone())
566 } else if text.starts_with("ɸ") {
567 ("ɸ".len(), PHI.clone())
568 } else if text.starts_with("tau") {
569 ("tau".len(), TAU.clone())
570 } else if text.starts_with("τ") {
571 ("τ".len(), TAU.clone())
572 } else if text.starts_with("e") {
573 ("e".len(), E.clone())
574 } else {
575 return None;
576 };
577 if had_text || text[n..].starts_with(char::is_alphabetic) {
578 return None;
579 }
580 *text = &text[n..];
581 return Some(x);
582 }
583
584 let integer_part = {
585 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
586 let part = &text[..end];
587 *text = &text[end..];
588 part
589 };
590 let decimal_part = if text.starts_with(['.', ',']) {
591 *text = &text[1..];
592 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
593 let part = &text[..end];
594 *text = &text[end..];
595 part
596 } else {
597 &text[..0]
598 };
599 let exponent_part = if text.starts_with(['e', 'E']) {
600 *text = &text[1..];
601 let negative = if text.starts_with('+') {
602 *text = &text[1..];
603 false
604 } else if text.starts_with('-') {
605 *text = &text[1..];
606 true
607 } else {
608 false
609 };
610 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
611 let part = &text[..end];
612 *text = &text[end..];
613 (part, negative)
614 } else {
615 (&text[..0], false)
616 };
617 let fraction_part = if !had_op && text.starts_with(['/']) {
618 *text = &text[1..];
619 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
620 let part = &text[..end];
621 *text = &text[end..];
622 part
623 } else {
624 &text[..0]
625 };
626 if text.starts_with(POSTFIX_OPS) && !fraction_part.is_empty() {
627 let n = fraction_part.parse::<Integer>().ok()?;
628 return Some(Number::Exact(n));
629 }
630 if integer_part.is_empty() && decimal_part.is_empty() {
631 return None;
632 }
633 let exponent = if !exponent_part.0.is_empty() {
634 let mut e = exponent_part.0.parse::<Integer>().ok()?;
635 if exponent_part.1 {
636 e *= -1;
637 }
638 e
639 } else {
640 0.into()
641 };
642 let divisor = if !fraction_part.is_empty() {
643 fraction_part.parse::<Integer>().ok()?
644 } else {
645 Integer::ONE.clone()
646 };
647 if exponent >= decimal_part.len() as i64
648 && exponent
649 <= INTEGER_CONSTRUCTION_LIMIT
650 .get()
651 .expect("Limits uninitialized, use init")
652 .clone()
653 - integer_part.len() as i64
654 && (divisor == 1
655 || exponent
656 >= INTEGER_CONSTRUCTION_LIMIT
657 .get()
658 .expect("Limits uninitialized, use init")
659 .clone()
660 / 10)
661 {
662 let exponent = exponent - decimal_part.len();
663 let n = format!("{integer_part}{decimal_part}")
664 .parse::<Integer>()
665 .ok()?;
666 let num = (n * Integer::u64_pow_u64(10, exponent.to_u64().unwrap()).complete()) / divisor;
667 Some(Number::Exact(num))
668 } else if exponent
669 <= INTEGER_CONSTRUCTION_LIMIT
670 .get()
671 .expect("Limits uninitialized, use init")
672 .clone()
673 - integer_part.len() as i64
674 {
675 let x = Float::parse(format!(
676 "{integer_part}.{decimal_part}{}{}{}",
677 if !exponent_part.0.is_empty() { "e" } else { "" },
678 if exponent_part.1 { "-" } else { "" },
679 exponent_part.0
680 ))
681 .ok()?;
682 let x = Float::with_val(prec, x) / divisor;
683 if x.is_integer() {
684 let n = x.to_integer().unwrap();
685 Some(Number::Exact(n))
686 } else if x.is_finite() {
687 Some(Number::Float(x.into()))
688 } else {
689 None
690 }
691 } else {
692 let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?;
693 let x = Float::with_val(prec, x) / divisor;
694 if x.is_finite() {
695 let (b, e) = crate::math::adjust_approximate((x, exponent));
696 Some(Number::Approximate(b.into(), e))
697 } else {
698 None
699 }
700 }
701}
702
703#[cfg(test)]
704mod test {
705 use super::*;
706 use crate::calculation_tasks::CalculationBase::Num;
707 use arbtest::arbtest;
708
709 use crate::recommended::FLOAT_PRECISION;
710
711 #[test]
712 fn test_text_only() {
713 let _ = crate::init_default();
714 let jobs = parse("just some words of encouragement!", true);
715 assert_eq!(jobs, []);
716 }
717 #[test]
718 fn test_factorial() {
719 let _ = crate::init_default();
720 let jobs = parse("a factorial 15!", true);
721 assert_eq!(
722 jobs,
723 [CalculationJob {
724 base: CalculationBase::Num(15.into()),
725 level: 1,
726 negative: 0
727 }]
728 );
729 }
730 #[test]
731 fn test_multifactorial() {
732 let _ = crate::init_default();
733 let jobs = parse("a factorial 15!!! actually a multi", true);
734 assert_eq!(
735 jobs,
736 [CalculationJob {
737 base: CalculationBase::Num(15.into()),
738 level: 3,
739 negative: 0
740 }]
741 );
742 }
743 #[test]
744 fn test_subfactorial() {
745 let _ = crate::init_default();
746 let jobs = parse("a factorial !15 actually a sub", true);
747 assert_eq!(
748 jobs,
749 [CalculationJob {
750 base: CalculationBase::Num(15.into()),
751 level: 0,
752 negative: 0
753 }]
754 );
755 }
756 #[test]
757 fn test_submultifactorial() {
758 let _ = crate::init_default();
759 let jobs = parse("not well defined !!!15", true);
760 assert_eq!(jobs, []);
761 }
762 #[test]
763 fn test_termial() {
764 let _ = crate::init_default();
765 let jobs = parse("a termial 15?", true);
766 assert_eq!(
767 jobs,
768 [CalculationJob {
769 base: CalculationBase::Num(15.into()),
770 level: -1,
771 negative: 0
772 }]
773 );
774 }
775 #[test]
776 fn test_no_termial() {
777 let _ = crate::init_default();
778 let jobs = parse("not enabled 15?", false);
779 assert_eq!(jobs, []);
780 }
781 #[test]
782 fn test_multitermial() {
783 let _ = crate::init_default();
784 let jobs = parse("a termial 15??? actually a multi", true);
785 assert_eq!(
786 jobs,
787 [CalculationJob {
788 base: CalculationBase::Num(15.into()),
789 level: -3,
790 negative: 0
791 }]
792 );
793 }
794 #[test]
795 fn test_subtermial() {
796 let _ = crate::init_default();
797 let jobs = parse("a termial ?15 actually a sub", true);
798 assert_eq!(jobs, []);
799 }
800 #[test]
801 fn test_chain() {
802 let _ = crate::init_default();
803 let jobs = parse("a factorialchain (15!)!", true);
804 assert_eq!(
805 jobs,
806 [CalculationJob {
807 base: CalculationBase::Calc(Box::new(CalculationJob {
808 base: CalculationBase::Num(15.into()),
809 level: 1,
810 negative: 0
811 })),
812 level: 1,
813 negative: 0
814 }]
815 );
816 }
817 #[test]
818 fn test_mixed_chain() {
819 let _ = crate::init_default();
820 let jobs = parse("a factorialchain !(15!)", true);
821 assert_eq!(
822 jobs,
823 [CalculationJob {
824 base: CalculationBase::Calc(Box::new(CalculationJob {
825 base: CalculationBase::Num(15.into()),
826 level: 1,
827 negative: 0
828 })),
829 level: 0,
830 negative: 0
831 }]
832 );
833 }
834 #[test]
835 fn test_postfix_chain() {
836 let _ = crate::init_default();
837 let jobs = parse("a factorialchain -15!?", true);
838 assert_eq!(
839 jobs,
840 [CalculationJob {
841 base: CalculationBase::Calc(Box::new(CalculationJob {
842 base: CalculationBase::Num(15.into()),
843 level: 1,
844 negative: 0
845 })),
846 level: -1,
847 negative: 1
848 }]
849 );
850 }
851 #[test]
852 fn test_negative() {
853 let _ = crate::init_default();
854 let jobs = parse("a factorial ---15!", true);
855 assert_eq!(
856 jobs,
857 [CalculationJob {
858 base: CalculationBase::Num(15.into()),
859 level: 1,
860 negative: 3
861 }]
862 );
863 }
864 #[test]
865 fn test_negative_gap() {
866 let _ = crate::init_default();
867 let jobs = parse("a factorial --- 15!", true);
868 assert_eq!(
869 jobs,
870 [CalculationJob {
871 base: CalculationBase::Num(15.into()),
872 level: 1,
873 negative: 0
874 }]
875 );
876 }
877 #[test]
878 fn test_paren() {
879 let _ = crate::init_default();
880 let jobs = parse("a factorial (15)!", true);
881 assert_eq!(
882 jobs,
883 [CalculationJob {
884 base: CalculationBase::Num(15.into()),
885 level: 1,
886 negative: 0
887 }]
888 );
889 }
890 #[test]
891 fn test_in_paren() {
892 let _ = crate::init_default();
893 let jobs = parse("a factorial (15!)", true);
894 assert_eq!(
895 jobs,
896 [CalculationJob {
897 base: CalculationBase::Num(15.into()),
898 level: 1,
899 negative: 0
900 }]
901 );
902 }
903 #[test]
904 fn test_decimal() {
905 let _ = crate::init_default();
906 let jobs = parse("a factorial 1.5!", true);
907 assert_eq!(
908 jobs,
909 [CalculationJob {
910 base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, 1.5).into()),
911 level: 1,
912 negative: 0
913 }]
914 );
915 }
916 #[test]
917 fn test_paren_negation() {
918 let _ = crate::init_default();
919 let jobs = parse("a factorial -(--(-(-(-3))!))!", true);
920 assert_eq!(
921 jobs,
922 [CalculationJob {
923 base: CalculationBase::Calc(Box::new(CalculationJob {
924 base: CalculationBase::Num(3.into()),
925 level: 1,
926 negative: 3
927 })),
928 level: 1,
929 negative: 1
930 }]
931 );
932 }
933 #[test]
934 fn test_tag() {
935 let _ = crate::init_default();
936 let jobs = parse(">!5 a factorial 15! !<", true);
937 assert_eq!(jobs, []);
938 }
939 #[test]
940 fn test_incomplete_tag() {
941 let _ = crate::init_default();
942 let jobs = parse(">!5 a factorial 15!", true);
943 assert_eq!(
944 jobs,
945 [
946 CalculationJob {
947 base: CalculationBase::Num(5.into()),
948 level: 0,
949 negative: 0
950 },
951 CalculationJob {
952 base: CalculationBase::Num(15.into()),
953 level: 1,
954 negative: 0
955 }
956 ]
957 );
958 }
959 #[test]
960 fn test_escaped_tag() {
961 let _ = crate::init_default();
962 let jobs = parse("\\>!5 a factorial 15! !<", true);
963 assert_eq!(
964 jobs,
965 [
966 CalculationJob {
967 base: CalculationBase::Num(5.into()),
968 level: 0,
969 negative: 0
970 },
971 CalculationJob {
972 base: CalculationBase::Num(15.into()),
973 level: 1,
974 negative: 0
975 }
976 ]
977 );
978 }
979 #[test]
980 fn test_escaped_tag2() {
981 let _ = crate::init_default();
982 let jobs = parse(">!5 a factorial 15! \\!<", true);
983 assert_eq!(
984 jobs,
985 [
986 CalculationJob {
987 base: CalculationBase::Num(5.into()),
988 level: 0,
989 negative: 0
990 },
991 CalculationJob {
992 base: CalculationBase::Num(15.into()),
993 level: 1,
994 negative: 0
995 }
996 ]
997 );
998 }
999
1000 #[test]
1001 fn test_url() {
1002 let _ = crate::init_default();
1003 let jobs = parse(
1004 "https://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1005 true,
1006 );
1007 assert_eq!(jobs, []);
1008 }
1009
1010 #[test]
1011 fn test_uri_poi_doesnt_cause_infinite_loop() {
1012 let _ = crate::init_default();
1013 let jobs = parse("84!:", true);
1014 assert_eq!(
1015 jobs,
1016 [CalculationJob {
1017 base: Num(84.into()),
1018 level: 1,
1019 negative: 0
1020 }]
1021 );
1022 }
1023 #[test]
1024 fn test_escaped_url() {
1025 let _ = crate::init_default();
1026 let jobs = parse(
1027 "\\://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1028 true,
1029 );
1030 assert_eq!(
1031 jobs,
1032 [CalculationJob {
1033 base: CalculationBase::Num(8743.into()),
1034 level: 1,
1035 negative: 0
1036 }]
1037 );
1038 }
1039
1040 #[test]
1041 fn test_word_in_paren() {
1042 let _ = crate::init_default();
1043 let jobs = parse("(x-2)! (2 word)! ((x/k)-3)! (,x-4)!", true);
1044 assert_eq!(jobs, []);
1045 }
1046
1047 #[test]
1048 fn test_multi_number_paren() {
1049 let _ = crate::init_default();
1050 let jobs = parse("(5-2)!", true);
1051 assert_eq!(jobs, []);
1052 }
1053 #[test]
1054 fn test_arbitrary_input() {
1055 let _ = crate::init_default();
1056 arbtest(|u| {
1057 let text: &str = u.arbitrary()?;
1058 let _ = parse(text, u.arbitrary()?);
1059 Ok(())
1060 });
1061 }
1062
1063 #[test]
1064 fn test_constant() {
1065 let _ = crate::init_default();
1066 let jobs = parse("!espi!", true);
1067 assert_eq!(jobs, []);
1068 }
1069
1070 #[test]
1071 fn test_fraction() {
1072 let _ = crate::init_default();
1073 let jobs = parse("!5/6!", true);
1074 assert_eq!(
1075 jobs,
1076 [
1077 CalculationJob {
1078 base: CalculationBase::Num(Number::Exact(5.into())),
1079 level: 0,
1080 negative: 0
1081 },
1082 CalculationJob {
1083 base: CalculationBase::Num(Number::Exact(6.into())),
1084 level: 1,
1085 negative: 0
1086 }
1087 ]
1088 );
1089 let jobs = parse("5/6!", true);
1090 assert_eq!(
1091 jobs,
1092 [CalculationJob {
1093 base: CalculationBase::Num(Number::Exact(6.into())),
1094 level: 1,
1095 negative: 0
1096 }]
1097 );
1098 let jobs = parse("(10/2)!", true);
1099 assert_eq!(
1100 jobs,
1101 [CalculationJob {
1102 base: CalculationBase::Num(Number::Exact(5.into())),
1103 level: 1,
1104 negative: 0
1105 },]
1106 );
1107 }
1108
1109 #[test]
1110 fn test_parse_num() {
1111 let _ = crate::init_default();
1112 let num = parse_num(&mut "1.5more !", false, false);
1113 assert_eq!(
1114 num,
1115 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1116 );
1117 let num = parse_num(&mut "1,5more !", false, false);
1118 assert_eq!(
1119 num,
1120 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1121 );
1122 let num = parse_num(&mut ".5more !", false, false);
1123 assert_eq!(
1124 num,
1125 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1126 );
1127 let num = parse_num(&mut "1more !", false, true);
1128 assert_eq!(num, Some(1.into()));
1129 let num = parse_num(&mut "1.0more !", true, false);
1130 assert_eq!(num, Some(1.into()));
1131 let num = parse_num(&mut "1.5e2more !", false, false);
1132 assert_eq!(num, Some(150.into()));
1133 let num = parse_num(&mut "1e2more !", false, false);
1134 assert_eq!(num, Some(100.into()));
1135 let num = parse_num(&mut "1.531e2more !", false, false);
1136 let Some(Number::Float(f)) = num else {
1137 panic!("Not a float")
1138 };
1139 assert!(Float::abs(f.as_float().clone() - 153.1) < 0.0000001);
1140 let num = parse_num(&mut "5e-1more !", false, false);
1141 assert_eq!(
1142 num,
1143 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1144 );
1145 let num = parse_num(&mut "e2more !", true, false);
1146 assert_eq!(num, None);
1147 let num = parse_num(&mut "es !", false, false);
1148 assert_eq!(num, None);
1149 let num = parse_num(&mut "e !", false, false);
1150 assert_eq!(num, Some(E.clone()));
1151 let num = parse_num(&mut "pi !", false, false);
1152 assert_eq!(num, Some(PI.clone()));
1153 let num = parse_num(&mut "π !", false, false);
1154 assert_eq!(num, Some(PI.clone()));
1155 let num = parse_num(&mut "phi !", false, false);
1156 assert_eq!(num, Some(PHI.clone()));
1157 let num = parse_num(&mut "ɸ !", false, false);
1158 assert_eq!(num, Some(PHI.clone()));
1159 let num = parse_num(&mut "tau !", false, false);
1160 assert_eq!(num, Some(TAU.clone()));
1161 let num = parse_num(&mut "τ !", false, false);
1162 assert_eq!(num, Some(TAU.clone()));
1163 let num = parse_num(&mut "1/2 !", false, false);
1164 assert_eq!(
1165 num,
1166 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1167 );
1168 let num = parse_num(&mut "10/2 !", false, false);
1169 assert_eq!(num, Some(Number::Exact(5.into())));
1170 let num = parse_num(&mut "1.5/2 !", false, false);
1171 assert_eq!(
1172 num,
1173 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.75).into()))
1174 );
1175 let num = parse_num(&mut "10e10000000000/2 !", false, false);
1176 assert_eq!(
1177 num,
1178 Some(Number::Approximate(
1179 Float::with_val(FLOAT_PRECISION, 5).into(),
1180 10000000000u64.into()
1181 ))
1182 );
1183 let num = parse_num(&mut "10/2 !", false, true);
1184 assert_eq!(num, Some(Number::Exact(10.into())));
1185 let num = parse_num(&mut "10/2!", false, false);
1186 assert_eq!(num, Some(Number::Exact(2.into())));
1187 }
1188 #[allow(clippy::uninlined_format_args)]
1189 #[test]
1190 fn test_biggest_num() {
1191 let _ = crate::init_default();
1192 let num = parse_num(
1193 &mut format!("9e{}", INTEGER_CONSTRUCTION_LIMIT.get().unwrap()).as_str(),
1194 true,
1195 false,
1196 );
1197 assert!(matches!(num, Some(Number::Approximate(_, _))));
1198 let num = parse_num(
1199 &mut format!(
1200 "9e{}",
1201 INTEGER_CONSTRUCTION_LIMIT.get().unwrap().clone() - 1
1202 )
1203 .as_str(),
1204 false,
1205 false,
1206 );
1207 assert!(num.is_some());
1208 }
1209}