1use crate::locale::{self, NumFormat};
4use crate::rug::{Complete, Float, Integer, integer::IntegerExt64};
5
6use crate::Consts;
7use crate::{
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}
17
18const POI_STARTS: &[char] = &[
19 NEGATION,
20 '!', '.', ESCAPE,
23 '0', '1',
25 '2',
26 '3',
27 '4',
28 '5',
29 '6',
30 '7',
31 '8',
32 '9',
33 'p', 'e',
35 't',
36 'π',
37 'ɸ',
38 'τ',
39 URI_POI,
40 SPOILER_POI,
41 SPOILER_HTML_POI,
42 PAREN_START,
43 PAREN_END,
44];
45
46const NEGATION: char = '-';
47const PAREN_START: char = '(';
48const PAREN_END: char = ')';
49const ESCAPE: char = '\\';
50const URI_START: &str = "://";
51const URI_POI: char = ':';
52const SPOILER_START: &str = ">!";
53const SPOILER_END: &str = "!<";
54const SPOILER_POI: char = '>';
55const SPOILER_HTML_START: &str = ">!";
56const SPOILER_HTML_END: &str = "!<";
57const SPOILER_HTML_POI: char = '&';
58
59const CONSTANT_STARTS: &[char] = &['p', 'e', 't', 'π', 'ɸ', 'τ'];
60static E: fn(u32) -> Number = |prec| Number::Float(Float::with_val(prec, 1).exp().into());
61static PHI: fn(u32) -> Number = |prec| {
62 Number::Float(Float::into(
63 ((1.0 + Float::with_val(prec, 5).sqrt()) as Float) / 2.0,
64 ))
65};
66static PI: fn(u32) -> Number =
67 |prec| Number::Float(Float::with_val(prec, crate::rug::float::Constant::Pi).into());
68static TAU: fn(u32) -> Number = |prec| {
69 Number::Float(Float::into(
70 Float::with_val(prec, crate::rug::float::Constant::Pi) * 2.0,
71 ))
72};
73
74const PREFIX_OPS: [char; 1] = ['!'];
75#[allow(dead_code)]
76const POSTFIX_OPS: [char; 2] = ['!', '?'];
77
78const INTEGER_ONLY_OPS: [i32; 1] = [0];
79
80pub fn parse(
81 mut text: &str,
82 do_termial: bool,
83 consts: &Consts,
84 locale: &NumFormat,
85) -> Vec<CalculationJob> {
86 let mut jobs = Vec::new();
130 let mut base: Option<CalculationBase> = None;
131 let mut paren_steps: Vec<(u32, Option<i32>, bool)> = Vec::new();
132 let mut current_negative: u32 = 0;
133 let mut last_len = usize::MAX;
134 let mut had_text_before = false;
135 while !text.is_empty() {
136 if last_len == text.len() {
137 panic!("Parser caught in a loop! Text: \"{text}\"")
138 }
139 last_len = text.len();
140
141 text = text.trim_start();
142 if text.len() != last_len {
143 current_negative = 0;
144 had_text_before = false;
145 }
146 let Some(position_of_interest) = text.find(POI_STARTS) else {
148 break;
149 };
150 if position_of_interest != 0 {
151 if let Some(step) = paren_steps.last_mut() {
153 step.2 = true;
154 }
155 current_negative = 0;
156 had_text_before = false;
157 }
158 let had_text =
159 text[..position_of_interest].ends_with(char::is_alphabetic) || had_text_before;
160 had_text_before = false;
161 text = &text[position_of_interest..];
163 if text.starts_with(ESCAPE) {
164 text = &text[1..];
166 let end = if text.starts_with(SPOILER_START) {
167 1
168 } else if text.starts_with(SPOILER_HTML_START) {
169 4
170 } else if text.starts_with(URI_START) {
171 3
172 } else {
173 0
174 };
175 text = &text[end..];
176 continue;
177 } else if text.starts_with(URI_START) {
178 let end = text.find(char::is_whitespace).unwrap_or(text.len());
180 text = &text[end..];
181 continue;
182 } else if text.starts_with(SPOILER_START) {
183 let mut end = 0;
185 loop {
186 if let Some(e) = text[end..].find(SPOILER_END) {
188 if e == 0 {
189 panic!("Parser loop Spoiler! Text \"{text}\"");
190 }
191 end += e;
192 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
194 end += 1;
195 continue;
196 }
197 break;
198 } else {
199 end = 0;
201 break;
202 }
203 }
204 current_negative = 0;
205 text = &text[end + 1..];
206 continue;
207 } else if text.starts_with(SPOILER_HTML_START) {
208 let mut end = 0;
210 loop {
211 if let Some(e) = text[end..].find(SPOILER_HTML_END) {
213 if e == 0 {
214 panic!("Parser loop Spoiler! Text \"{text}\"");
215 }
216 end += e;
217 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
219 end += 1;
220 continue;
221 }
222 break;
223 } else {
224 end = 0;
226 break;
227 }
228 }
229 current_negative = 0;
230 text = &text[end + 4..];
231 continue;
232 } else if text.starts_with(NEGATION) {
233 let end = text.find(|c| c != NEGATION).unwrap_or(text.len());
235 current_negative = end as u32;
236 text = &text[end..];
237 continue;
238 } else if text.starts_with(PAREN_START) {
239 paren_steps.push((current_negative, None, false));
241 if let Some(CalculationBase::Calc(job)) = base.take() {
243 jobs.push(*job);
244 }
245 current_negative = 0;
246 text = &text[1..];
247 continue;
248 } else if text.starts_with(PAREN_END) {
249 text = &text[1..];
251 current_negative = 0;
252 let Some(step) = paren_steps.pop() else {
254 continue;
255 };
256 if step.2 {
258 if let Some(CalculationBase::Calc(job)) = base.take() {
259 jobs.push(*job);
260 }
261 if let Some(step) = paren_steps.last_mut() {
263 step.2 = true;
264 }
265 continue;
266 }
267 let mut had_op = false;
268 if let Some(level) = step.1 {
270 let Some(inner) = base.take() else {
272 if let Some(step) = paren_steps.last_mut() {
274 step.2 = true;
275 }
276 continue;
277 };
278 if let (CalculationBase::Num(Number::Float(_)), true) =
279 (&inner, INTEGER_ONLY_OPS.contains(&level))
280 {
281 continue;
282 }
283 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
284 base: inner,
285 level,
286 negative: 0,
287 })));
288 had_op = true;
289 }
290 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
292 base.take();
293 if let Some(step) = paren_steps.last_mut() {
295 step.2 = true;
296 }
297 continue;
298 };
299 if !levels.is_empty() {
300 for level in levels {
302 let Some(inner) = base.take() else {
304 continue;
305 };
306 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
307 base: inner,
308 level,
309 negative: 0,
310 })));
311 had_op = true;
312 }
313 }
314 if !had_op {
315 match &mut base {
316 Some(CalculationBase::Calc(job)) => job.negative += step.0,
317 Some(CalculationBase::Num(n)) => {
318 if step.0 % 2 != 0 {
319 n.negate();
320 }
321 }
322 None => {}
323 }
324 } else {
325 match &mut base {
326 Some(CalculationBase::Num(n)) => {
327 if step.0 % 2 == 1 {
328 n.negate();
329 }
330 }
331 Some(CalculationBase::Calc(job)) => job.negative += step.0,
332 None => {
333 if let Some(step) = paren_steps.last_mut() {
335 step.2 = true;
336 }
337 }
338 }
339 continue;
340 };
341 } else if text.starts_with(PREFIX_OPS) {
342 let Ok(level) = parse_op(&mut text, true, do_termial) else {
344 parse_num(&mut text, false, true, consts, locale);
346 continue;
347 };
348 if let Some(num) = parse_num(&mut text, false, true, consts, locale) {
350 if let Some(CalculationBase::Calc(job)) = base.take() {
352 if let Some(step) = paren_steps.last_mut() {
354 step.2 = true;
355 }
356 jobs.push(*job);
357 }
358 if let (Number::Float(_), true) = (&num, INTEGER_ONLY_OPS.contains(&level)) {
359 continue;
360 }
361 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
362 base: CalculationBase::Num(num),
363 level,
364 negative: current_negative,
365 })));
366 current_negative = 0;
367 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
368 continue;
369 };
370 for level in levels {
371 let Some(inner) = base.take() else {
373 continue;
374 };
375 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
376 base: inner,
377 level,
378 negative: 0,
379 })));
380 }
381 } else {
382 if text.starts_with(PAREN_START) {
384 paren_steps.push((current_negative, Some(level), false));
385 current_negative = 0;
386 text = &text[1..];
387 }
388 continue;
389 };
390 } else {
391 if text.starts_with('.') && !text[1..].starts_with(char::is_numeric) {
393 text = &text[1..];
395 continue;
396 }
397 let Some(num) = parse_num(&mut text, had_text, false, consts, locale) else {
398 had_text_before = true;
399 let mut end = 1;
401 while !text.is_char_boundary(end) && end < text.len() {
402 end += 1;
403 }
404 text = &text[end.min(text.len())..];
405 continue;
406 };
407 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
409 continue;
410 };
411 if !levels.is_empty() {
412 let levels = levels.into_iter();
413 if let Some(CalculationBase::Calc(job)) = base.take() {
414 if let Some(step) = paren_steps.last_mut() {
416 step.2 = true;
417 }
418 jobs.push(*job);
419 }
420 base = Some(CalculationBase::Num(num));
421 for level in levels {
422 let previous = base.take().unwrap();
423 if let (CalculationBase::Num(Number::Float(_)), true) =
424 (&previous, INTEGER_ONLY_OPS.contains(&level))
425 {
426 continue;
427 }
428 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
429 base: previous,
430 level,
431 negative: 0,
432 })))
433 }
434 if let Some(CalculationBase::Calc(job)) = &mut base {
435 job.negative = current_negative;
436 }
437 } else {
438 if !paren_steps.is_empty() {
440 let mut num = num;
441 if current_negative % 2 == 1 {
442 num.negate();
443 }
444
445 if base.is_none() {
446 base = Some(CalculationBase::Num(num))
447 } else {
448 if let Some(step) = paren_steps.last_mut() {
450 step.2 = true;
451 }
452 }
453 }
454 }
455 current_negative = 0;
456 };
457 if paren_steps.is_empty()
459 && let Some(CalculationBase::Calc(job)) = base.take()
460 {
461 jobs.push(*job);
462 }
463 }
464 if let Some(CalculationBase::Calc(job)) = base.take() {
465 jobs.push(*job);
466 }
467 jobs.sort();
468 jobs.dedup();
469 jobs
470}
471
472enum ParseOpErr {
473 NonOp,
474 InvalidOp,
475}
476
477fn parse_op(text: &mut &str, prefix: bool, do_termial: bool) -> Result<i32, ParseOpErr> {
478 let op = text.chars().next().ok_or(ParseOpErr::NonOp)?;
479 let end = text.find(|c| c != op).unwrap_or(text.len());
480 let res = match op {
481 '!' => {
482 if prefix {
483 if end != 1 {
484 Err(ParseOpErr::InvalidOp)
485 } else {
486 Ok(0)
487 }
488 } else {
489 Ok(end as i32)
490 }
491 }
492 '?' => {
493 if !do_termial {
494 Err(ParseOpErr::NonOp)
495 } else if prefix {
496 Err(ParseOpErr::InvalidOp)
497 } else {
498 Ok(-(end as i32))
499 }
500 }
501 _ => return Err(ParseOpErr::NonOp),
502 };
503 *text = &text[end..];
504 res
505}
506
507fn parse_ops(text: &mut &str, prefix: bool, do_termial: bool) -> Option<Vec<i32>> {
508 let mut res = Vec::new();
509 loop {
510 match parse_op(text, prefix, do_termial) {
511 Ok(op) => res.push(op),
512 Err(ParseOpErr::NonOp) => break,
513 Err(ParseOpErr::InvalidOp) => return None,
514 }
515 }
516 Some(res)
517}
518
519fn parse_num(
520 text: &mut &str,
521 had_text: bool,
522 had_op: bool,
523 consts: &Consts,
524 locale: &NumFormat,
525) -> Option<Number> {
526 let prec = consts.float_precision;
527 if text.starts_with(CONSTANT_STARTS) {
528 let (n, x) = if text.starts_with("pi") {
529 ("pi".len(), PI(prec))
530 } else if text.starts_with("π") {
531 ("π".len(), PI(prec))
532 } else if text.starts_with("phi") {
533 ("phi".len(), PHI(prec))
534 } else if text.starts_with("ɸ") {
535 ("ɸ".len(), PHI(prec))
536 } else if text.starts_with("tau") {
537 ("tau".len(), TAU(prec))
538 } else if text.starts_with("τ") {
539 ("τ".len(), TAU(prec))
540 } else if text.starts_with("e") {
541 ("e".len(), E(prec))
542 } else {
543 return None;
544 };
545 if had_text || text[n..].starts_with(char::is_alphabetic) {
546 return None;
547 }
548 *text = &text[n..];
549 return Some(x);
550 }
551
552 let integer_part = {
553 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
554 let part = &text[..end];
555 *text = &text[end..];
556 part
557 };
558 let decimal_part = if text.starts_with(*locale.decimal()) {
559 *text = &text[1..];
560 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
561 let part = &text[..end];
562 *text = &text[end..];
563 part
564 } else {
565 &text[..0]
566 };
567 let exponent_part = if text.starts_with(['e', 'E']) {
568 *text = &text[1..];
569 let negative = if text.starts_with('+') {
570 *text = &text[1..];
571 false
572 } else if text.starts_with('-') {
573 *text = &text[1..];
574 true
575 } else {
576 false
577 };
578 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
579 let part = &text[..end];
580 *text = &text[end..];
581 (part, negative)
582 } else {
583 (&text[..0], false)
584 };
585 let fraction_part = if !had_op && text.starts_with(['/']) {
586 *text = &text[1..];
587 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
588 let part = &text[..end];
589 *text = &text[end..];
590 part
591 } else {
592 &text[..0]
593 };
594 if text.starts_with(POSTFIX_OPS) && !fraction_part.is_empty() {
595 let n = fraction_part.parse::<Integer>().ok()?;
596 return Some(Number::Exact(n));
597 }
598 if integer_part.is_empty() && decimal_part.is_empty() {
599 return None;
600 }
601 let exponent = if !exponent_part.0.is_empty() {
602 let mut e = exponent_part.0.parse::<Integer>().ok()?;
603 if exponent_part.1 {
604 e *= -1;
605 }
606 e
607 } else {
608 0.into()
609 };
610 let divisor = if !fraction_part.is_empty() {
611 fraction_part.parse::<Integer>().ok()?
612 } else {
613 Integer::ONE.clone()
614 };
615 if exponent >= decimal_part.len() as i64
616 && exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64
617 && (divisor == 1 || exponent >= consts.integer_construction_limit.clone() / 10)
618 {
619 let exponent = exponent - decimal_part.len();
620 let n = format!("{integer_part}{decimal_part}")
621 .parse::<Integer>()
622 .ok()?;
623 let num = (n * Integer::u64_pow_u64(10, exponent.to_u64().unwrap()).complete()) / divisor;
624 Some(Number::Exact(num))
625 } else if exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64 {
626 let x = Float::parse(format!(
627 "{integer_part}.{decimal_part}{}{}{}",
628 if !exponent_part.0.is_empty() { "e" } else { "" },
629 if exponent_part.1 { "-" } else { "" },
630 exponent_part.0
631 ))
632 .ok()?;
633 let x = Float::with_val(prec, x) / divisor;
634 if x.is_integer() {
635 let n = x.to_integer().unwrap();
636 Some(Number::Exact(n))
637 } else if x.is_finite() {
638 Some(Number::Float(x.into()))
639 } else {
640 None
641 }
642 } else {
643 let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?;
644 let x = Float::with_val(prec, x) / divisor;
645 if x.is_finite() {
646 let (b, e) = crate::math::adjust_approximate((x, exponent));
647 Some(Number::Approximate(b.into(), e))
648 } else {
649 None
650 }
651 }
652}
653
654#[cfg(test)]
655mod test {
656 use super::*;
657 use crate::calculation_tasks::CalculationBase::Num;
658 use arbtest::arbtest;
659
660 use crate::recommended::FLOAT_PRECISION;
661
662 #[test]
663 fn test_text_only() {
664 let consts = Consts::default();
665 let jobs = parse(
666 "just some words of encouragement!",
667 true,
668 &consts,
669 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
670 );
671 assert_eq!(jobs, []);
672 }
673 #[test]
674 fn test_factorial() {
675 let consts = Consts::default();
676 let jobs = parse(
677 "a factorial 15!",
678 true,
679 &consts,
680 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
681 );
682 assert_eq!(
683 jobs,
684 [CalculationJob {
685 base: CalculationBase::Num(15.into()),
686 level: 1,
687 negative: 0
688 }]
689 );
690 }
691 #[test]
692 fn test_multifactorial() {
693 let consts = Consts::default();
694 let jobs = parse(
695 "a factorial 15!!! actually a multi",
696 true,
697 &consts,
698 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
699 );
700 assert_eq!(
701 jobs,
702 [CalculationJob {
703 base: CalculationBase::Num(15.into()),
704 level: 3,
705 negative: 0
706 }]
707 );
708 }
709 #[test]
710 fn test_subfactorial() {
711 let consts = Consts::default();
712 let jobs = parse(
713 "a factorial !15 actually a sub",
714 true,
715 &consts,
716 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
717 );
718 assert_eq!(
719 jobs,
720 [CalculationJob {
721 base: CalculationBase::Num(15.into()),
722 level: 0,
723 negative: 0
724 }]
725 );
726 }
727 #[test]
728 fn test_submultifactorial() {
729 let consts = Consts::default();
730 let jobs = parse(
731 "not well defined !!!15",
732 true,
733 &consts,
734 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
735 );
736 assert_eq!(jobs, []);
737 }
738 #[test]
739 fn test_termial() {
740 let consts = Consts::default();
741 let jobs = parse(
742 "a termial 15?",
743 true,
744 &consts,
745 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
746 );
747 assert_eq!(
748 jobs,
749 [CalculationJob {
750 base: CalculationBase::Num(15.into()),
751 level: -1,
752 negative: 0
753 }]
754 );
755 }
756 #[test]
757 fn test_no_termial() {
758 let consts = Consts::default();
759 let jobs = parse(
760 "not enabled 15?",
761 false,
762 &consts,
763 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
764 );
765 assert_eq!(jobs, []);
766 }
767 #[test]
768 fn test_multitermial() {
769 let consts = Consts::default();
770 let jobs = parse(
771 "a termial 15??? actually a multi",
772 true,
773 &consts,
774 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
775 );
776 assert_eq!(
777 jobs,
778 [CalculationJob {
779 base: CalculationBase::Num(15.into()),
780 level: -3,
781 negative: 0
782 }]
783 );
784 }
785 #[test]
786 fn test_subtermial() {
787 let consts = Consts::default();
788 let jobs = parse(
789 "a termial ?15 actually a sub",
790 true,
791 &consts,
792 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
793 );
794 assert_eq!(jobs, []);
795 }
796 #[test]
797 fn test_chain() {
798 let consts = Consts::default();
799 let jobs = parse(
800 "a factorialchain (15!)!",
801 true,
802 &consts,
803 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
804 );
805 assert_eq!(
806 jobs,
807 [CalculationJob {
808 base: CalculationBase::Calc(Box::new(CalculationJob {
809 base: CalculationBase::Num(15.into()),
810 level: 1,
811 negative: 0
812 })),
813 level: 1,
814 negative: 0
815 }]
816 );
817 }
818 #[test]
819 fn test_mixed_chain() {
820 let consts = Consts::default();
821 let jobs = parse(
822 "a factorialchain !(15!)",
823 true,
824 &consts,
825 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
826 );
827 assert_eq!(
828 jobs,
829 [CalculationJob {
830 base: CalculationBase::Calc(Box::new(CalculationJob {
831 base: CalculationBase::Num(15.into()),
832 level: 1,
833 negative: 0
834 })),
835 level: 0,
836 negative: 0
837 }]
838 );
839 }
840 #[test]
841 fn test_postfix_chain() {
842 let consts = Consts::default();
843 let jobs = parse(
844 "a factorialchain -15!?",
845 true,
846 &consts,
847 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
848 );
849 assert_eq!(
850 jobs,
851 [CalculationJob {
852 base: CalculationBase::Calc(Box::new(CalculationJob {
853 base: CalculationBase::Num(15.into()),
854 level: 1,
855 negative: 0
856 })),
857 level: -1,
858 negative: 1
859 }]
860 );
861 }
862 #[test]
863 fn test_negative() {
864 let consts = Consts::default();
865 let jobs = parse(
866 "a factorial ---15!",
867 true,
868 &consts,
869 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
870 );
871 assert_eq!(
872 jobs,
873 [CalculationJob {
874 base: CalculationBase::Num(15.into()),
875 level: 1,
876 negative: 3
877 }]
878 );
879 }
880 #[test]
881 fn test_negative_gap() {
882 let consts = Consts::default();
883 let jobs = parse(
884 "a factorial --- 15!",
885 true,
886 &consts,
887 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
888 );
889 assert_eq!(
890 jobs,
891 [CalculationJob {
892 base: CalculationBase::Num(15.into()),
893 level: 1,
894 negative: 0
895 }]
896 );
897 }
898 #[test]
899 fn test_paren() {
900 let consts = Consts::default();
901 let jobs = parse(
902 "a factorial (15)!",
903 true,
904 &consts,
905 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
906 );
907 assert_eq!(
908 jobs,
909 [CalculationJob {
910 base: CalculationBase::Num(15.into()),
911 level: 1,
912 negative: 0
913 }]
914 );
915 }
916 #[test]
917 fn test_in_paren() {
918 let consts = Consts::default();
919 let jobs = parse(
920 "a factorial (15!)",
921 true,
922 &consts,
923 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
924 );
925 assert_eq!(
926 jobs,
927 [CalculationJob {
928 base: CalculationBase::Num(15.into()),
929 level: 1,
930 negative: 0
931 }]
932 );
933 }
934 #[test]
935 fn test_decimal() {
936 let consts = Consts::default();
937 let jobs = parse(
938 "a factorial 1.5!",
939 true,
940 &consts,
941 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
942 );
943 assert_eq!(
944 jobs,
945 [CalculationJob {
946 base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, 1.5).into()),
947 level: 1,
948 negative: 0
949 }]
950 );
951 }
952 #[test]
953 fn test_paren_negation() {
954 let consts = Consts::default();
955 let jobs = parse(
956 "a factorial -(--(-(-(-3))!))!",
957 true,
958 &consts,
959 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
960 );
961 assert_eq!(
962 jobs,
963 [CalculationJob {
964 base: CalculationBase::Calc(Box::new(CalculationJob {
965 base: CalculationBase::Num(3.into()),
966 level: 1,
967 negative: 3
968 })),
969 level: 1,
970 negative: 1
971 }]
972 );
973 }
974 #[test]
975 fn test_tag() {
976 let consts = Consts::default();
977 let jobs = parse(
978 ">!5 a factorial 15! !<",
979 true,
980 &consts,
981 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
982 );
983 assert_eq!(jobs, []);
984 }
985 #[test]
986 fn test_incomplete_tag() {
987 let consts = Consts::default();
988 let jobs = parse(
989 ">!5 a factorial 15!",
990 true,
991 &consts,
992 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
993 );
994 assert_eq!(
995 jobs,
996 [
997 CalculationJob {
998 base: CalculationBase::Num(5.into()),
999 level: 0,
1000 negative: 0
1001 },
1002 CalculationJob {
1003 base: CalculationBase::Num(15.into()),
1004 level: 1,
1005 negative: 0
1006 }
1007 ]
1008 );
1009 }
1010 #[test]
1011 fn test_escaped_tag() {
1012 let consts = Consts::default();
1013 let jobs = parse(
1014 "\\>!5 a factorial 15! !<",
1015 true,
1016 &consts,
1017 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1018 );
1019 assert_eq!(
1020 jobs,
1021 [
1022 CalculationJob {
1023 base: CalculationBase::Num(5.into()),
1024 level: 0,
1025 negative: 0
1026 },
1027 CalculationJob {
1028 base: CalculationBase::Num(15.into()),
1029 level: 1,
1030 negative: 0
1031 }
1032 ]
1033 );
1034 }
1035 #[test]
1036 fn test_escaped_tag2() {
1037 let consts = Consts::default();
1038 let jobs = parse(
1039 ">!5 a factorial 15! \\!<",
1040 true,
1041 &consts,
1042 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1043 );
1044 assert_eq!(
1045 jobs,
1046 [
1047 CalculationJob {
1048 base: CalculationBase::Num(5.into()),
1049 level: 0,
1050 negative: 0
1051 },
1052 CalculationJob {
1053 base: CalculationBase::Num(15.into()),
1054 level: 1,
1055 negative: 0
1056 }
1057 ]
1058 );
1059 }
1060
1061 #[test]
1062 fn test_url() {
1063 let consts = Consts::default();
1064 let jobs = parse(
1065 "https://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1066 true,
1067 &consts,
1068 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1069 );
1070 assert_eq!(jobs, []);
1071 }
1072
1073 #[test]
1074 fn test_uri_poi_doesnt_cause_infinite_loop() {
1075 let consts = Consts::default();
1076 let jobs = parse(
1077 "84!:",
1078 true,
1079 &consts,
1080 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1081 );
1082 assert_eq!(
1083 jobs,
1084 [CalculationJob {
1085 base: Num(84.into()),
1086 level: 1,
1087 negative: 0
1088 }]
1089 );
1090 }
1091 #[test]
1092 fn test_escaped_url() {
1093 let consts = Consts::default();
1094 let jobs = parse(
1095 "\\://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1096 true,
1097 &consts,
1098 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1099 );
1100 assert_eq!(
1101 jobs,
1102 [CalculationJob {
1103 base: CalculationBase::Num(8743.into()),
1104 level: 1,
1105 negative: 0
1106 }]
1107 );
1108 }
1109
1110 #[test]
1111 fn test_word_in_paren() {
1112 let consts = Consts::default();
1113 let jobs = parse(
1114 "(x-2)! (2 word)! ((x/k)-3)! (,x-4)!",
1115 true,
1116 &consts,
1117 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1118 );
1119 assert_eq!(jobs, []);
1120 }
1121
1122 #[test]
1123 fn test_multi_number_paren() {
1124 let consts = Consts::default();
1125 let jobs = parse(
1126 "(5-2)!",
1127 true,
1128 &consts,
1129 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1130 );
1131 assert_eq!(jobs, []);
1132 }
1133 #[test]
1134 fn test_arbitrary_input() {
1135 let consts = Consts::default();
1136 arbtest(|u| {
1137 let text: &str = u.arbitrary()?;
1138 let _ = parse(
1139 text,
1140 u.arbitrary()?,
1141 &consts,
1142 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1143 );
1144 Ok(())
1145 });
1146 }
1147
1148 #[test]
1149 fn test_constant() {
1150 let consts = Consts::default();
1151 let jobs = parse(
1152 "!espi!",
1153 true,
1154 &consts,
1155 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1156 );
1157 assert_eq!(jobs, []);
1158 let jobs = parse(
1159 "some. pi!",
1160 true,
1161 &consts,
1162 &consts.locales.get("en").unwrap().format().number_format(),
1163 );
1164 assert_eq!(
1165 jobs,
1166 [CalculationJob {
1167 base: CalculationBase::Num(Number::Float(
1168 Float::with_val(FLOAT_PRECISION, factorion_math::rug::float::Constant::Pi)
1169 .into()
1170 )),
1171 level: 1,
1172 negative: 0
1173 }]
1174 );
1175 }
1176
1177 #[test]
1178 fn test_fraction() {
1179 let consts = Consts::default();
1180 let jobs = parse(
1181 "!5/6!",
1182 true,
1183 &consts,
1184 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1185 );
1186 assert_eq!(
1187 jobs,
1188 [
1189 CalculationJob {
1190 base: CalculationBase::Num(Number::Exact(5.into())),
1191 level: 0,
1192 negative: 0
1193 },
1194 CalculationJob {
1195 base: CalculationBase::Num(Number::Exact(6.into())),
1196 level: 1,
1197 negative: 0
1198 }
1199 ]
1200 );
1201 let jobs = parse(
1202 "5/6!",
1203 true,
1204 &consts,
1205 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1206 );
1207 assert_eq!(
1208 jobs,
1209 [CalculationJob {
1210 base: CalculationBase::Num(Number::Exact(6.into())),
1211 level: 1,
1212 negative: 0
1213 }]
1214 );
1215 let jobs = parse(
1216 "(10/2)!",
1217 true,
1218 &consts,
1219 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1220 );
1221 assert_eq!(
1222 jobs,
1223 [CalculationJob {
1224 base: CalculationBase::Num(Number::Exact(5.into())),
1225 level: 1,
1226 negative: 0
1227 },]
1228 );
1229 }
1230
1231 #[test]
1232 fn test_parse_num() {
1233 let consts = Consts::default();
1234 let num = parse_num(
1235 &mut "1.5more !",
1236 false,
1237 false,
1238 &consts,
1239 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1240 );
1241 assert_eq!(
1242 num,
1243 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1244 );
1245 let num = parse_num(
1246 &mut "1,5more !",
1247 false,
1248 false,
1249 &consts,
1250 &NumFormat::V1(&locale::v1::NumFormat { decimal: ',' }),
1251 );
1252 assert_eq!(
1253 num,
1254 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1255 );
1256 let num = parse_num(
1257 &mut ".5more !",
1258 false,
1259 false,
1260 &consts,
1261 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1262 );
1263 assert_eq!(
1264 num,
1265 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1266 );
1267 let num = parse_num(
1268 &mut "1more !",
1269 false,
1270 true,
1271 &consts,
1272 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1273 );
1274 assert_eq!(num, Some(1.into()));
1275 let num = parse_num(
1276 &mut "1.0more !",
1277 true,
1278 false,
1279 &consts,
1280 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1281 );
1282 assert_eq!(num, Some(1.into()));
1283 let num = parse_num(
1284 &mut "1.5e2more !",
1285 false,
1286 false,
1287 &consts,
1288 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1289 );
1290 assert_eq!(num, Some(150.into()));
1291 let num = parse_num(
1292 &mut "1e2more !",
1293 false,
1294 false,
1295 &consts,
1296 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1297 );
1298 assert_eq!(num, Some(100.into()));
1299 let num = parse_num(
1300 &mut "1.531e2more !",
1301 false,
1302 false,
1303 &consts,
1304 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1305 );
1306 let Some(Number::Float(f)) = num else {
1307 panic!("Not a float")
1308 };
1309 assert!(Float::abs(f.as_float().clone() - 153.1) < 0.0000001);
1310 let num = parse_num(
1311 &mut "5e-1more !",
1312 false,
1313 false,
1314 &consts,
1315 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1316 );
1317 assert_eq!(
1318 num,
1319 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1320 );
1321 let num = parse_num(
1322 &mut "e2more !",
1323 true,
1324 false,
1325 &consts,
1326 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1327 );
1328 assert_eq!(num, None);
1329 let num = parse_num(
1330 &mut "es !",
1331 false,
1332 false,
1333 &consts,
1334 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1335 );
1336 assert_eq!(num, None);
1337 let num = parse_num(
1338 &mut "e !",
1339 false,
1340 false,
1341 &consts,
1342 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1343 );
1344 assert_eq!(num, Some(E(FLOAT_PRECISION)));
1345 let num = parse_num(
1346 &mut "pi !",
1347 false,
1348 false,
1349 &consts,
1350 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1351 );
1352 assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1353 let num = parse_num(
1354 &mut "π !",
1355 false,
1356 false,
1357 &consts,
1358 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1359 );
1360 assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1361 let num = parse_num(
1362 &mut "phi !",
1363 false,
1364 false,
1365 &consts,
1366 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1367 );
1368 assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1369 let num = parse_num(
1370 &mut "ɸ !",
1371 false,
1372 false,
1373 &consts,
1374 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1375 );
1376 assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1377 let num = parse_num(
1378 &mut "tau !",
1379 false,
1380 false,
1381 &consts,
1382 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1383 );
1384 assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1385 let num = parse_num(
1386 &mut "τ !",
1387 false,
1388 false,
1389 &consts,
1390 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1391 );
1392 assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1393 let num = parse_num(
1394 &mut "1/2 !",
1395 false,
1396 false,
1397 &consts,
1398 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1399 );
1400 assert_eq!(
1401 num,
1402 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1403 );
1404 let num = parse_num(
1405 &mut "10/2 !",
1406 false,
1407 false,
1408 &consts,
1409 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1410 );
1411 assert_eq!(num, Some(Number::Exact(5.into())));
1412 let num = parse_num(
1413 &mut "1.5/2 !",
1414 false,
1415 false,
1416 &consts,
1417 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1418 );
1419 assert_eq!(
1420 num,
1421 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.75).into()))
1422 );
1423 let num = parse_num(
1424 &mut "10e10000000000/2 !",
1425 false,
1426 false,
1427 &consts,
1428 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1429 );
1430 assert_eq!(
1431 num,
1432 Some(Number::Approximate(
1433 Float::with_val(FLOAT_PRECISION, 5).into(),
1434 10000000000u64.into()
1435 ))
1436 );
1437 let num = parse_num(
1438 &mut "10/2 !",
1439 false,
1440 true,
1441 &consts,
1442 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1443 );
1444 assert_eq!(num, Some(Number::Exact(10.into())));
1445 let num = parse_num(
1446 &mut "10/2!",
1447 false,
1448 false,
1449 &consts,
1450 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1451 );
1452 assert_eq!(num, Some(Number::Exact(2.into())));
1453 }
1454 #[allow(clippy::uninlined_format_args)]
1455 #[test]
1456 fn test_biggest_num() {
1457 let consts = Consts::default();
1458 let num = parse_num(
1459 &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT()).as_str(),
1460 true,
1461 false,
1462 &consts,
1463 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1464 );
1465 assert!(matches!(num, Some(Number::Approximate(_, _))));
1466 let num = parse_num(
1467 &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT() - 1).as_str(),
1468 false,
1469 false,
1470 &consts,
1471 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1472 );
1473 assert!(num.is_some());
1474 }
1475}