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 = || 10_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 'i',
37 'π',
38 'ɸ',
39 'τ',
40 '∞',
41 '^', URI_POI,
43 SPOILER_POI,
44 SPOILER_HTML_POI,
45 PAREN_START,
46 PAREN_END,
47];
48
49const NEGATION: char = '-';
50const PAREN_START: char = '(';
51const PAREN_END: char = ')';
52const ESCAPE: char = '\\';
53const URI_START: &str = "://";
54const URI_POI: char = ':';
55const SPOILER_START: &str = ">!";
56const SPOILER_END: &str = "!<";
57const SPOILER_POI: char = '>';
58const SPOILER_HTML_START: &str = ">!";
59const SPOILER_HTML_END: &str = "!<";
60const SPOILER_HTML_POI: char = '&';
61
62const CONSTANT_STARTS: &[char] = &['p', 'e', 't', 'i', 'π', 'ɸ', 'τ', '∞'];
63static E: fn(u32) -> Number = |prec| Number::Float(Float::with_val(prec, 1).exp().into());
64static PHI: fn(u32) -> Number = |prec| {
65 Number::Float(Float::into(
66 ((1.0 + Float::with_val(prec, 5).sqrt()) as Float) / 2.0,
67 ))
68};
69static PI: fn(u32) -> Number =
70 |prec| Number::Float(Float::with_val(prec, crate::rug::float::Constant::Pi).into());
71static TAU: fn(u32) -> Number = |prec| {
72 Number::Float(Float::into(
73 Float::with_val(prec, crate::rug::float::Constant::Pi) * 2.0,
74 ))
75};
76
77const PREFIX_OPS: [char; 1] = ['!'];
78#[allow(dead_code)]
79const POSTFIX_OPS: [char; 2] = ['!', '?'];
80
81const INTEGER_ONLY_OPS: [i32; 1] = [0];
82
83pub fn parse(
84 mut text: &str,
85 do_termial: bool,
86 consts: &Consts,
87 locale: &NumFormat,
88) -> Vec<CalculationJob> {
89 let mut jobs = Vec::new();
133 let mut base: Option<CalculationBase> = None;
134 let mut paren_steps: Vec<(u32, Option<i32>, bool)> = Vec::new();
135 let mut current_negative: u32 = 0;
136 let mut last_len = usize::MAX;
137 let mut had_text_before = false;
138 while !text.is_empty() {
139 if last_len == text.len() {
140 panic!("Parser caught in a loop! Text: \"{text}\"")
141 }
142 last_len = text.len();
143
144 text = text.trim_start();
145 if text.len() != last_len {
146 current_negative = 0;
147 had_text_before = false;
148 }
149 let Some(position_of_interest) = text.find(POI_STARTS) else {
151 break;
152 };
153 if position_of_interest != 0 {
154 if let Some(step) = paren_steps.last_mut() {
156 step.2 = true;
157 }
158 current_negative = 0;
159 had_text_before = false;
160 }
161 let had_text =
162 text[..position_of_interest].ends_with(char::is_alphabetic) || had_text_before;
163 had_text_before = false;
164 text = &text[position_of_interest..];
166 if text.starts_with(ESCAPE) {
167 text = &text[1..];
169 let end = if text.starts_with(SPOILER_START) {
170 1
171 } else if text.starts_with(SPOILER_HTML_START) {
172 4
173 } else if text.starts_with(URI_START) {
174 3
175 } else {
176 0
177 };
178 text = &text[end..];
179 continue;
180 } else if text.starts_with(URI_START) {
181 let end = text.find(char::is_whitespace).unwrap_or(text.len());
183 text = &text[end..];
184 continue;
185 } else if text.starts_with(SPOILER_START) {
186 let mut end = 0;
188 loop {
189 if let Some(e) = text[end..].find(SPOILER_END) {
191 if e == 0 {
192 panic!("Parser loop Spoiler! Text \"{text}\"");
193 }
194 end += e;
195 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
197 end += 1;
198 continue;
199 }
200 break;
201 } else {
202 end = 0;
204 break;
205 }
206 }
207 current_negative = 0;
208 text = &text[end + 1..];
209 continue;
210 } else if text.starts_with(SPOILER_HTML_START) {
211 let mut end = 0;
213 loop {
214 if let Some(e) = text[end..].find(SPOILER_HTML_END) {
216 if e == 0 {
217 panic!("Parser loop Spoiler! Text \"{text}\"");
218 }
219 end += e;
220 if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
222 end += 1;
223 continue;
224 }
225 break;
226 } else {
227 end = 0;
229 break;
230 }
231 }
232 current_negative = 0;
233 text = &text[end + 4..];
234 continue;
235 } else if text.starts_with(NEGATION) {
236 let end = text.find(|c| c != NEGATION).unwrap_or(text.len());
238 current_negative = end as u32;
239 text = &text[end..];
240 continue;
241 } else if text.starts_with(PAREN_START) {
242 paren_steps.push((current_negative, None, false));
244 if let Some(CalculationBase::Calc(job)) = base.take() {
246 jobs.push(*job);
247 }
248 current_negative = 0;
249 text = &text[1..];
250 continue;
251 } else if text.starts_with(PAREN_END) {
252 text = &text[1..];
254 current_negative = 0;
255 let Some(step) = paren_steps.pop() else {
257 continue;
258 };
259 if step.2 {
261 if let Some(CalculationBase::Calc(job)) = base.take() {
262 jobs.push(*job);
263 }
264 if let Some(step) = paren_steps.last_mut() {
266 step.2 = true;
267 }
268 continue;
269 }
270 let mut had_op = false;
271 if let Some(level) = step.1 {
273 let Some(inner) = base.take() else {
275 if let Some(step) = paren_steps.last_mut() {
277 step.2 = true;
278 }
279 continue;
280 };
281 if let (CalculationBase::Num(Number::Float(_)), true) =
282 (&inner, INTEGER_ONLY_OPS.contains(&level))
283 {
284 continue;
285 }
286 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
287 base: inner,
288 level,
289 negative: 0,
290 })));
291 had_op = true;
292 }
293 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
295 base.take();
296 if let Some(step) = paren_steps.last_mut() {
298 step.2 = true;
299 }
300 continue;
301 };
302 if !levels.is_empty() {
303 for level in levels {
305 let Some(inner) = base.take() else {
307 continue;
308 };
309 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
310 base: inner,
311 level,
312 negative: 0,
313 })));
314 had_op = true;
315 }
316 }
317 if !had_op {
318 match &mut base {
319 Some(CalculationBase::Calc(job)) => job.negative += step.0,
320 Some(CalculationBase::Num(n)) => {
321 if step.0 % 2 != 0 {
322 n.negate();
323 }
324 }
325 None => {}
326 }
327 } else {
328 match &mut base {
329 Some(CalculationBase::Num(n)) => {
330 if step.0 % 2 == 1 {
331 n.negate();
332 }
333 }
334 Some(CalculationBase::Calc(job)) => job.negative += step.0,
335 None => {
336 if let Some(step) = paren_steps.last_mut() {
338 step.2 = true;
339 }
340 }
341 }
342 continue;
343 };
344 } else if text.starts_with(PREFIX_OPS) {
345 let Ok(level) = parse_op(&mut text, true, do_termial) else {
347 parse_num(&mut text, false, true, consts, locale);
349 continue;
350 };
351 if let Some(num) = parse_num(&mut text, false, true, consts, locale) {
353 if let Some(CalculationBase::Calc(job)) = base.take() {
355 if let Some(step) = paren_steps.last_mut() {
357 step.2 = true;
358 }
359 jobs.push(*job);
360 }
361 if let (Number::Float(_), true) = (&num, INTEGER_ONLY_OPS.contains(&level)) {
362 continue;
363 }
364 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
365 base: CalculationBase::Num(num),
366 level,
367 negative: current_negative,
368 })));
369 current_negative = 0;
370 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
371 continue;
372 };
373 for level in levels {
374 let Some(inner) = base.take() else {
376 continue;
377 };
378 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
379 base: inner,
380 level,
381 negative: 0,
382 })));
383 }
384 } else {
385 if text.starts_with(PAREN_START) {
387 paren_steps.push((current_negative, Some(level), false));
388 current_negative = 0;
389 text = &text[1..];
390 }
391 continue;
392 };
393 } else {
394 if text.starts_with('.') && !text[1..].starts_with(char::is_numeric) {
396 text = &text[1..];
398 continue;
399 }
400 let Some(num) = parse_num(&mut text, had_text, false, consts, locale) else {
401 had_text_before = true;
402 let mut end = 1;
404 while !text.is_char_boundary(end) && end < text.len() {
405 end += 1;
406 }
407 text = &text[end.min(text.len())..];
408 continue;
409 };
410 let Some(levels) = parse_ops(&mut text, false, do_termial) else {
412 continue;
413 };
414 if !levels.is_empty() {
415 let levels = levels.into_iter();
416 if let Some(CalculationBase::Calc(job)) = base.take() {
417 if let Some(step) = paren_steps.last_mut() {
419 step.2 = true;
420 }
421 jobs.push(*job);
422 }
423 base = Some(CalculationBase::Num(num));
424 for level in levels {
425 let previous = base.take().unwrap();
426 if let (CalculationBase::Num(Number::Float(_)), true) =
427 (&previous, INTEGER_ONLY_OPS.contains(&level))
428 {
429 continue;
430 }
431 base = Some(CalculationBase::Calc(Box::new(CalculationJob {
432 base: previous,
433 level,
434 negative: 0,
435 })))
436 }
437 if let Some(CalculationBase::Calc(job)) = &mut base {
438 job.negative = current_negative;
439 }
440 } else {
441 if !paren_steps.is_empty() {
443 let mut num = num;
444 if current_negative % 2 == 1 {
445 num.negate();
446 }
447
448 if base.is_none() {
449 base = Some(CalculationBase::Num(num))
450 } else {
451 if let Some(step) = paren_steps.last_mut() {
453 step.2 = true;
454 }
455 }
456 }
457 }
458 current_negative = 0;
459 };
460 if paren_steps.is_empty()
462 && let Some(CalculationBase::Calc(job)) = base.take()
463 {
464 jobs.push(*job);
465 }
466 }
467 if let Some(CalculationBase::Calc(job)) = base.take() {
468 jobs.push(*job);
469 }
470 jobs.sort();
471 jobs.dedup();
472 jobs
473}
474
475enum ParseOpErr {
476 NonOp,
477 InvalidOp,
478}
479
480fn parse_op(text: &mut &str, prefix: bool, do_termial: bool) -> Result<i32, ParseOpErr> {
481 let op = text.chars().next().ok_or(ParseOpErr::NonOp)?;
482 let end = text.find(|c| c != op).unwrap_or(text.len());
483 let res = match op {
484 '!' => {
485 if prefix {
486 if end != 1 {
487 Err(ParseOpErr::InvalidOp)
488 } else {
489 Ok(0)
490 }
491 } else {
492 Ok(end as i32)
493 }
494 }
495 '?' => {
496 if !do_termial {
497 Err(ParseOpErr::NonOp)
498 } else if prefix {
499 Err(ParseOpErr::InvalidOp)
500 } else {
501 Ok(-(end as i32))
502 }
503 }
504 _ => return Err(ParseOpErr::NonOp),
505 };
506 *text = &text[end..];
507 res
508}
509
510fn parse_ops(text: &mut &str, prefix: bool, do_termial: bool) -> Option<Vec<i32>> {
511 let mut res = Vec::new();
512 loop {
513 match parse_op(text, prefix, do_termial) {
514 Ok(op) => res.push(op),
515 Err(ParseOpErr::NonOp) => break,
516 Err(ParseOpErr::InvalidOp) => return None,
517 }
518 }
519 Some(res)
520}
521
522fn parse_num(
523 text: &mut &str,
524 had_text: bool,
525 had_op: bool,
526 consts: &Consts,
527 locale: &NumFormat,
528) -> Option<Number> {
529 let prec = consts.float_precision;
530 if text.starts_with(CONSTANT_STARTS) {
531 let (n, x) = if text.starts_with("pi") {
532 ("pi".len(), PI(prec))
533 } else if text.starts_with("π") {
534 ("π".len(), PI(prec))
535 } else if text.starts_with("phi") {
536 ("phi".len(), PHI(prec))
537 } else if text.starts_with("ɸ") {
538 ("ɸ".len(), PHI(prec))
539 } else if text.starts_with("tau") {
540 ("tau".len(), TAU(prec))
541 } else if text.starts_with("τ") {
542 ("τ".len(), TAU(prec))
543 } else if text.starts_with("e") {
544 ("e".len(), E(prec))
545 } else if text.starts_with("infinity") {
546 ("infinity".len(), Number::ComplexInfinity)
547 } else if text.starts_with("inf") {
548 ("inf".len(), Number::ComplexInfinity)
549 } else if text.starts_with("∞\u{303}") {
550 ("∞\u{303}".len(), Number::ComplexInfinity)
551 } else if text.starts_with("∞") {
552 ("∞".len(), Number::ComplexInfinity)
553 } else {
554 return None;
555 };
556 if had_text || text[n..].starts_with(char::is_alphabetic) {
557 return None;
558 }
559 *text = &text[n..];
560 return Some(x);
561 }
562
563 if text.starts_with("^(") {
564 let orig_text = &text[..];
565 *text = &text[2..];
566 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
567 let part = &text[..end];
568 *text = &text[end..];
569 if text.starts_with(")10") && !text[3..].starts_with(POSTFIX_OPS) {
570 *text = &text[3..];
571 let n = part.parse::<u32>().ok()?;
572 return Some(Number::ApproximateDigitsTower(
573 false,
574 false,
575 n - 1,
576 1.into(),
577 ));
578 } else {
579 *text = orig_text;
581 return None;
582 }
583 }
584
585 if text.starts_with("10^") || text.starts_with("10\\^") {
586 let orig_text = &text[..];
587 if had_op {
588 if &text[2..3] == "^" {
589 *text = &text[3..];
590 } else {
591 *text = &text[4..];
592 }
593 if text.starts_with("(") || text.starts_with("\\(") {
595 *text = &orig_text[2..];
596 }
597 return Some(Number::Exact(10.into()));
598 }
599 let mut cur_parens = 0;
600 let mut depth = 0;
601
602 let mut max_no_paren_level = 0;
603 let mut no_paren_inner = &text[0..];
604
605 while text.starts_with("10^") || text.starts_with("10\\^") {
606 let base_text;
607 if &text[2..3] == "^" {
608 base_text = &text[2..];
609 *text = &text[3..];
610 } else {
611 base_text = &text[3..];
612 *text = &text[4..];
613 }
614 if text.starts_with("\\(") {
615 *text = &text[2..];
616 cur_parens += 1;
617 } else if text.starts_with("(") {
618 *text = &text[1..];
619 cur_parens += 1;
620 }
621 if depth == max_no_paren_level && cur_parens == 0 {
623 max_no_paren_level += 1;
624 no_paren_inner = base_text;
625 }
626 depth += 1;
627 }
628
629 let top = match parse_num_simple(text, had_op, consts, locale, prec) {
630 Some(Number::Exact(n)) => n,
631 Some(Number::Approximate(_, n)) => {
632 depth += 1;
633 n
634 }
635 _ => {
636 *text = &orig_text[3..];
637 return None;
638 }
639 };
640
641 for _ in 0..cur_parens {
642 if text.starts_with("\\)") {
643 *text = &text[2..];
644 } else if text.starts_with(")") {
645 *text = &text[1..];
646 } else {
647 *text = &orig_text[2..];
648 return None;
649 }
650 }
651
652 if max_no_paren_level != 0 && text.starts_with(POSTFIX_OPS) {
654 *text = no_paren_inner;
655 return None;
656 }
657
658 if depth == 1 {
659 return Some(Number::ApproximateDigits(false, top));
660 } else {
661 return Some(Number::ApproximateDigitsTower(false, false, depth - 1, top));
662 }
663 }
664
665 parse_num_simple(text, had_op, consts, locale, prec)
666}
667
668fn parse_num_simple(
669 text: &mut &str,
670 had_op: bool,
671 consts: &Consts<'_>,
672 locale: &NumFormat<'_>,
673 prec: u32,
674) -> Option<crate::calculation_results::CalculationResult> {
675 let integer_part = {
676 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
677 let part = &text[..end];
678 *text = &text[end..];
679 part
680 };
681 let decimal_part = if text.starts_with(*locale.decimal()) {
682 *text = &text[1..];
683 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
684 let part = &text[..end];
685 *text = &text[end..];
686 part
687 } else {
688 &text[..0]
689 };
690 let exponent_part = if text.starts_with(['e', 'E']) {
691 *text = &text[1..];
692 let negative = if text.starts_with('+') {
693 *text = &text[1..];
694 false
695 } else if text.starts_with('-') {
696 *text = &text[1..];
697 true
698 } else {
699 false
700 };
701 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
702 let part = &text[..end];
703 *text = &text[end..];
704 (part, negative)
705 } else if !had_op
706 && (text.trim_start().starts_with("\\*10^")
707 || text.trim_start().starts_with("\\* 10^")
708 || text.trim_start().starts_with("\\*10\\^")
709 || text.trim_start().starts_with("\\* 10\\^")
710 || text.trim_start().starts_with("⨉10^")
711 || text.trim_start().starts_with("⨉ 10^")
712 || text.trim_start().starts_with("⨉10\\^")
713 || text.trim_start().starts_with("⨉ 10\\^")
714 || text.trim_start().starts_with("x10^")
715 || text.trim_start().starts_with("x 10^")
716 || text.trim_start().starts_with("x10\\^")
717 || text.trim_start().starts_with("x 10\\^"))
718 {
719 let pre_orig_text = &text[..];
720 let start = text.find("^").unwrap();
721 let orig_text = &text[start..];
722 *text = &text[start + 1..];
723 let paren = if text.starts_with('(') {
724 *text = &text[1..];
725 true
726 } else {
727 false
728 };
729 let negative = if text.starts_with('+') {
730 *text = &text[1..];
731 false
732 } else if text.starts_with('-') {
733 *text = &text[1..];
734 true
735 } else {
736 false
737 };
738 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
739 let part = &text[..end];
740 *text = &text[end..];
741 if paren {
742 if text.starts_with(')') {
743 *text = &text[1..];
744 } else {
745 *text = orig_text;
746 return None;
747 }
748 }
749 if text.starts_with(POSTFIX_OPS) {
750 if paren {
753 *text = pre_orig_text;
754 } else {
755 *text = orig_text;
756 }
757 return None;
758 }
759 (part, negative)
760 } else {
761 (&text[..0], false)
762 };
763 let fraction_part = if !had_op && text.starts_with(['/']) {
764 *text = &text[1..];
765 let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
766 let part = &text[..end];
767 *text = &text[end..];
768 part
769 } else {
770 &text[..0]
771 };
772 if text.starts_with(POSTFIX_OPS) && !fraction_part.is_empty() {
773 let n = fraction_part.parse::<Integer>().ok()?;
774 return Some(Number::Exact(n));
775 }
776 if integer_part.is_empty() && decimal_part.is_empty() {
777 return None;
778 }
779 let exponent = if !exponent_part.0.is_empty() {
780 let mut e = exponent_part.0.parse::<Integer>().ok()?;
781 if exponent_part.1 {
782 e *= -1;
783 }
784 e
785 } else {
786 0.into()
787 };
788 let divisor = if !fraction_part.is_empty() {
789 fraction_part.parse::<Integer>().ok()?
790 } else {
791 Integer::ONE.clone()
792 };
793 if exponent >= decimal_part.len() as i64
794 && exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64
795 && (divisor == 1 || exponent >= consts.integer_construction_limit.clone() / 10)
796 {
797 let exponent = exponent - decimal_part.len();
798 let n = format!("{integer_part}{decimal_part}")
799 .parse::<Integer>()
800 .ok()?;
801 let num = (n * Integer::u64_pow_u64(10, exponent.to_u64().unwrap()).complete()) / divisor;
802 Some(Number::Exact(num))
803 } else if exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64 {
804 let x = Float::parse(format!(
805 "{integer_part}.{decimal_part}{}{}{}",
806 if !exponent_part.0.is_empty() { "e" } else { "" },
807 if exponent_part.1 { "-" } else { "" },
808 exponent_part.0
809 ))
810 .ok()?;
811 let x = Float::with_val(prec, x) / divisor;
812 if x.is_integer() {
813 let n = x.to_integer().unwrap();
814 Some(Number::Exact(n))
815 } else if x.is_finite() {
816 Some(Number::Float(x.into()))
817 } else {
818 None
819 }
820 } else {
821 let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?;
822 let x = Float::with_val(prec, x) / divisor;
823 if x.is_finite() {
824 let (b, e) = crate::math::adjust_approximate((x, exponent));
825 Some(Number::Approximate(b.into(), e))
826 } else {
827 None
828 }
829 }
830}
831
832#[cfg(test)]
833mod test {
834 use super::*;
835 use crate::calculation_tasks::CalculationBase::Num;
836 use arbtest::arbtest;
837
838 use crate::recommended::FLOAT_PRECISION;
839
840 #[test]
841 fn test_text_only() {
842 let consts = Consts::default();
843 let jobs = parse(
844 "just some words of encouragement!",
845 true,
846 &consts,
847 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
848 );
849 assert_eq!(jobs, []);
850 }
851 #[test]
852 fn test_factorial() {
853 let consts = Consts::default();
854 let jobs = parse(
855 "a factorial 15!",
856 true,
857 &consts,
858 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
859 );
860 assert_eq!(
861 jobs,
862 [CalculationJob {
863 base: CalculationBase::Num(15.into()),
864 level: 1,
865 negative: 0
866 }]
867 );
868 }
869 #[test]
870 fn test_multifactorial() {
871 let consts = Consts::default();
872 let jobs = parse(
873 "a factorial 15!!! actually a multi",
874 true,
875 &consts,
876 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
877 );
878 assert_eq!(
879 jobs,
880 [CalculationJob {
881 base: CalculationBase::Num(15.into()),
882 level: 3,
883 negative: 0
884 }]
885 );
886 }
887 #[test]
888 fn test_subfactorial() {
889 let consts = Consts::default();
890 let jobs = parse(
891 "a factorial !15 actually a sub",
892 true,
893 &consts,
894 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
895 );
896 assert_eq!(
897 jobs,
898 [CalculationJob {
899 base: CalculationBase::Num(15.into()),
900 level: 0,
901 negative: 0
902 }]
903 );
904 }
905 #[test]
906 fn test_submultifactorial() {
907 let consts = Consts::default();
908 let jobs = parse(
909 "not well defined !!!15",
910 true,
911 &consts,
912 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
913 );
914 assert_eq!(jobs, []);
915 }
916 #[test]
917 fn test_termial() {
918 let consts = Consts::default();
919 let jobs = parse(
920 "a termial 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_no_termial() {
936 let consts = Consts::default();
937 let jobs = parse(
938 "not enabled 15?",
939 false,
940 &consts,
941 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
942 );
943 assert_eq!(jobs, []);
944 }
945 #[test]
946 fn test_multitermial() {
947 let consts = Consts::default();
948 let jobs = parse(
949 "a termial 15??? actually a multi",
950 true,
951 &consts,
952 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
953 );
954 assert_eq!(
955 jobs,
956 [CalculationJob {
957 base: CalculationBase::Num(15.into()),
958 level: -3,
959 negative: 0
960 }]
961 );
962 }
963 #[test]
964 fn test_subtermial() {
965 let consts = Consts::default();
966 let jobs = parse(
967 "a termial ?15 actually a sub",
968 true,
969 &consts,
970 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
971 );
972 assert_eq!(jobs, []);
973 }
974 #[test]
975 fn test_chain() {
976 let consts = Consts::default();
977 let jobs = parse(
978 "a factorialchain (15!)!",
979 true,
980 &consts,
981 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
982 );
983 assert_eq!(
984 jobs,
985 [CalculationJob {
986 base: CalculationBase::Calc(Box::new(CalculationJob {
987 base: CalculationBase::Num(15.into()),
988 level: 1,
989 negative: 0
990 })),
991 level: 1,
992 negative: 0
993 }]
994 );
995 }
996 #[test]
997 fn test_mixed_chain() {
998 let consts = Consts::default();
999 let jobs = parse(
1000 "a factorialchain !(15!)",
1001 true,
1002 &consts,
1003 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1004 );
1005 assert_eq!(
1006 jobs,
1007 [CalculationJob {
1008 base: CalculationBase::Calc(Box::new(CalculationJob {
1009 base: CalculationBase::Num(15.into()),
1010 level: 1,
1011 negative: 0
1012 })),
1013 level: 0,
1014 negative: 0
1015 }]
1016 );
1017 }
1018 #[test]
1019 fn test_postfix_chain() {
1020 let consts = Consts::default();
1021 let jobs = parse(
1022 "a factorialchain -15!?",
1023 true,
1024 &consts,
1025 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1026 );
1027 assert_eq!(
1028 jobs,
1029 [CalculationJob {
1030 base: CalculationBase::Calc(Box::new(CalculationJob {
1031 base: CalculationBase::Num(15.into()),
1032 level: 1,
1033 negative: 0
1034 })),
1035 level: -1,
1036 negative: 1
1037 }]
1038 );
1039 }
1040 #[test]
1041 fn test_negative() {
1042 let consts = Consts::default();
1043 let jobs = parse(
1044 "a factorial ---15!",
1045 true,
1046 &consts,
1047 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1048 );
1049 assert_eq!(
1050 jobs,
1051 [CalculationJob {
1052 base: CalculationBase::Num(15.into()),
1053 level: 1,
1054 negative: 3
1055 }]
1056 );
1057 }
1058 #[test]
1059 fn test_negative_gap() {
1060 let consts = Consts::default();
1061 let jobs = parse(
1062 "a factorial --- 15!",
1063 true,
1064 &consts,
1065 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1066 );
1067 assert_eq!(
1068 jobs,
1069 [CalculationJob {
1070 base: CalculationBase::Num(15.into()),
1071 level: 1,
1072 negative: 0
1073 }]
1074 );
1075 }
1076 #[test]
1077 fn test_paren() {
1078 let consts = Consts::default();
1079 let jobs = parse(
1080 "a factorial (15)!",
1081 true,
1082 &consts,
1083 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1084 );
1085 assert_eq!(
1086 jobs,
1087 [CalculationJob {
1088 base: CalculationBase::Num(15.into()),
1089 level: 1,
1090 negative: 0
1091 }]
1092 );
1093 }
1094 #[test]
1095 fn test_in_paren() {
1096 let consts = Consts::default();
1097 let jobs = parse(
1098 "a factorial (15!)",
1099 true,
1100 &consts,
1101 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1102 );
1103 assert_eq!(
1104 jobs,
1105 [CalculationJob {
1106 base: CalculationBase::Num(15.into()),
1107 level: 1,
1108 negative: 0
1109 }]
1110 );
1111 }
1112 #[test]
1113 fn test_decimal() {
1114 let consts = Consts::default();
1115 let jobs = parse(
1116 "a factorial 1.5!",
1117 true,
1118 &consts,
1119 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1120 );
1121 assert_eq!(
1122 jobs,
1123 [CalculationJob {
1124 base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, 1.5).into()),
1125 level: 1,
1126 negative: 0
1127 }]
1128 );
1129 }
1130 #[test]
1131 fn test_paren_negation() {
1132 let consts = Consts::default();
1133 let jobs = parse(
1134 "a factorial -(--(-(-(-3))!))!",
1135 true,
1136 &consts,
1137 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1138 );
1139 assert_eq!(
1140 jobs,
1141 [CalculationJob {
1142 base: CalculationBase::Calc(Box::new(CalculationJob {
1143 base: CalculationBase::Num(3.into()),
1144 level: 1,
1145 negative: 3
1146 })),
1147 level: 1,
1148 negative: 1
1149 }]
1150 );
1151 }
1152 #[test]
1153 fn test_tag() {
1154 let consts = Consts::default();
1155 let jobs = parse(
1156 ">!5 a factorial 15! !<",
1157 true,
1158 &consts,
1159 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1160 );
1161 assert_eq!(jobs, []);
1162 }
1163 #[test]
1164 fn test_incomplete_tag() {
1165 let consts = Consts::default();
1166 let jobs = parse(
1167 ">!5 a factorial 15!",
1168 true,
1169 &consts,
1170 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1171 );
1172 assert_eq!(
1173 jobs,
1174 [
1175 CalculationJob {
1176 base: CalculationBase::Num(5.into()),
1177 level: 0,
1178 negative: 0
1179 },
1180 CalculationJob {
1181 base: CalculationBase::Num(15.into()),
1182 level: 1,
1183 negative: 0
1184 }
1185 ]
1186 );
1187 }
1188 #[test]
1189 fn test_escaped_tag() {
1190 let consts = Consts::default();
1191 let jobs = parse(
1192 "\\>!5 a factorial 15! !<",
1193 true,
1194 &consts,
1195 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1196 );
1197 assert_eq!(
1198 jobs,
1199 [
1200 CalculationJob {
1201 base: CalculationBase::Num(5.into()),
1202 level: 0,
1203 negative: 0
1204 },
1205 CalculationJob {
1206 base: CalculationBase::Num(15.into()),
1207 level: 1,
1208 negative: 0
1209 }
1210 ]
1211 );
1212 }
1213 #[test]
1214 fn test_escaped_tag2() {
1215 let consts = Consts::default();
1216 let jobs = parse(
1217 ">!5 a factorial 15! \\!<",
1218 true,
1219 &consts,
1220 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1221 );
1222 assert_eq!(
1223 jobs,
1224 [
1225 CalculationJob {
1226 base: CalculationBase::Num(5.into()),
1227 level: 0,
1228 negative: 0
1229 },
1230 CalculationJob {
1231 base: CalculationBase::Num(15.into()),
1232 level: 1,
1233 negative: 0
1234 }
1235 ]
1236 );
1237 }
1238
1239 #[test]
1240 fn test_url() {
1241 let consts = Consts::default();
1242 let jobs = parse(
1243 "https://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1244 true,
1245 &consts,
1246 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1247 );
1248 assert_eq!(jobs, []);
1249 }
1250
1251 #[test]
1252 fn test_uri_poi_doesnt_cause_infinite_loop() {
1253 let consts = Consts::default();
1254 let jobs = parse(
1255 "84!:",
1256 true,
1257 &consts,
1258 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1259 );
1260 assert_eq!(
1261 jobs,
1262 [CalculationJob {
1263 base: Num(84.into()),
1264 level: 1,
1265 negative: 0
1266 }]
1267 );
1268 }
1269 #[test]
1270 fn test_escaped_url() {
1271 let consts = Consts::default();
1272 let jobs = parse(
1273 "\\://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1274 true,
1275 &consts,
1276 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1277 );
1278 assert_eq!(
1279 jobs,
1280 [CalculationJob {
1281 base: CalculationBase::Num(8743.into()),
1282 level: 1,
1283 negative: 0
1284 }]
1285 );
1286 }
1287
1288 #[test]
1289 fn test_word_in_paren() {
1290 let consts = Consts::default();
1291 let jobs = parse(
1292 "(x-2)! (2 word)! ((x/k)-3)! (,x-4)!",
1293 true,
1294 &consts,
1295 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1296 );
1297 assert_eq!(jobs, []);
1298 }
1299
1300 #[test]
1301 fn test_multi_number_paren() {
1302 let consts = Consts::default();
1303 let jobs = parse(
1304 "(5-2)!",
1305 true,
1306 &consts,
1307 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1308 );
1309 assert_eq!(jobs, []);
1310 }
1311 #[test]
1312 fn test_arbitrary_input() {
1313 let consts = Consts::default();
1314 arbtest(|u| {
1315 let text: &str = u.arbitrary()?;
1316 let _ = parse(
1317 text,
1318 u.arbitrary()?,
1319 &consts,
1320 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1321 );
1322 Ok(())
1323 });
1324 }
1325
1326 #[test]
1327 fn test_constant() {
1328 let consts = Consts::default();
1329 let jobs = parse(
1330 "!espi!",
1331 true,
1332 &consts,
1333 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1334 );
1335 assert_eq!(jobs, []);
1336 let jobs = parse(
1337 "some. pi!",
1338 true,
1339 &consts,
1340 &consts.locales.get("en").unwrap().format().number_format(),
1341 );
1342 assert_eq!(
1343 jobs,
1344 [CalculationJob {
1345 base: CalculationBase::Num(Number::Float(
1346 Float::with_val(FLOAT_PRECISION, factorion_math::rug::float::Constant::Pi)
1347 .into()
1348 )),
1349 level: 1,
1350 negative: 0
1351 }]
1352 );
1353 }
1354
1355 #[test]
1356 fn test_fraction() {
1357 let consts = Consts::default();
1358 let jobs = parse(
1359 "!5/6!",
1360 true,
1361 &consts,
1362 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1363 );
1364 assert_eq!(
1365 jobs,
1366 [
1367 CalculationJob {
1368 base: CalculationBase::Num(Number::Exact(5.into())),
1369 level: 0,
1370 negative: 0
1371 },
1372 CalculationJob {
1373 base: CalculationBase::Num(Number::Exact(6.into())),
1374 level: 1,
1375 negative: 0
1376 }
1377 ]
1378 );
1379 let jobs = parse(
1380 "5/6!",
1381 true,
1382 &consts,
1383 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1384 );
1385 assert_eq!(
1386 jobs,
1387 [CalculationJob {
1388 base: CalculationBase::Num(Number::Exact(6.into())),
1389 level: 1,
1390 negative: 0
1391 }]
1392 );
1393 let jobs = parse(
1394 "(10/2)!",
1395 true,
1396 &consts,
1397 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1398 );
1399 assert_eq!(
1400 jobs,
1401 [CalculationJob {
1402 base: CalculationBase::Num(Number::Exact(5.into())),
1403 level: 1,
1404 negative: 0
1405 },]
1406 );
1407 }
1408
1409 #[test]
1410 fn test_parse_num() {
1411 let consts = Consts::default();
1412 let num = parse_num(
1413 &mut "1.5more !",
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, 1.5).into()))
1422 );
1423 let num = parse_num(
1424 &mut "1,5more !",
1425 false,
1426 false,
1427 &consts,
1428 &NumFormat::V1(&locale::v1::NumFormat { decimal: ',' }),
1429 );
1430 assert_eq!(
1431 num,
1432 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1433 );
1434 let num = parse_num(
1435 &mut ".5more !",
1436 false,
1437 false,
1438 &consts,
1439 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1440 );
1441 assert_eq!(
1442 num,
1443 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1444 );
1445 let num = parse_num(
1446 &mut "1more !",
1447 false,
1448 true,
1449 &consts,
1450 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1451 );
1452 assert_eq!(num, Some(1.into()));
1453 let num = parse_num(
1454 &mut "1.0more !",
1455 true,
1456 false,
1457 &consts,
1458 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1459 );
1460 assert_eq!(num, Some(1.into()));
1461 let num = parse_num(
1462 &mut "1.5e2more !",
1463 false,
1464 false,
1465 &consts,
1466 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1467 );
1468 assert_eq!(num, Some(150.into()));
1469 let num = parse_num(
1470 &mut "1.5 ⨉ 10^2more !",
1471 false,
1472 false,
1473 &consts,
1474 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1475 );
1476 assert_eq!(num, Some(150.into()));
1477 let num = parse_num(
1478 &mut "1e2more !",
1479 false,
1480 false,
1481 &consts,
1482 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1483 );
1484 assert_eq!(num, Some(100.into()));
1485 let num = parse_num(
1486 &mut "1\\*10^(2)more !",
1487 false,
1488 false,
1489 &consts,
1490 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1491 );
1492 assert_eq!(num, Some(100.into()));
1493 let num = parse_num(
1494 &mut "1.531e2more !",
1495 false,
1496 false,
1497 &consts,
1498 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1499 );
1500 let Some(Number::Float(f)) = num else {
1501 panic!("Not a float")
1502 };
1503 assert!(Float::abs(f.as_float().clone() - 153.1) < 0.0000001);
1504 let num = parse_num(
1505 &mut "5e-1more !",
1506 false,
1507 false,
1508 &consts,
1509 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1510 );
1511 assert_eq!(
1512 num,
1513 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1514 );
1515 let num = parse_num(
1516 &mut "e2more !",
1517 true,
1518 false,
1519 &consts,
1520 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1521 );
1522 assert_eq!(num, None);
1523 let num = parse_num(
1524 &mut "es !",
1525 false,
1526 false,
1527 &consts,
1528 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1529 );
1530 assert_eq!(num, None);
1531 let num = parse_num(
1532 &mut "e !",
1533 false,
1534 false,
1535 &consts,
1536 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1537 );
1538 assert_eq!(num, Some(E(FLOAT_PRECISION)));
1539 let num = parse_num(
1540 &mut "pi !",
1541 false,
1542 false,
1543 &consts,
1544 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1545 );
1546 assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1547 let num = parse_num(
1548 &mut "π !",
1549 false,
1550 false,
1551 &consts,
1552 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1553 );
1554 assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1555 let num = parse_num(
1556 &mut "phi !",
1557 false,
1558 false,
1559 &consts,
1560 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1561 );
1562 assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1563 let num = parse_num(
1564 &mut "ɸ !",
1565 false,
1566 false,
1567 &consts,
1568 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1569 );
1570 assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1571 let num = parse_num(
1572 &mut "tau !",
1573 false,
1574 false,
1575 &consts,
1576 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1577 );
1578 assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1579 let num = parse_num(
1580 &mut "τ !",
1581 false,
1582 false,
1583 &consts,
1584 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1585 );
1586 assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1587 let num = parse_num(
1588 &mut "∞\u{0303} !",
1589 false,
1590 false,
1591 &consts,
1592 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1593 );
1594 assert_eq!(num, Some(Number::ComplexInfinity));
1595 let num = parse_num(
1596 &mut "∞ !",
1597 false,
1598 false,
1599 &consts,
1600 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1601 );
1602 assert_eq!(num, Some(Number::ComplexInfinity));
1603 let num = parse_num(
1604 &mut "1/2 !",
1605 false,
1606 false,
1607 &consts,
1608 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1609 );
1610 assert_eq!(
1611 num,
1612 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1613 );
1614 let num = parse_num(
1615 &mut "10/2 !",
1616 false,
1617 false,
1618 &consts,
1619 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1620 );
1621 assert_eq!(num, Some(Number::Exact(5.into())));
1622 let num = parse_num(
1623 &mut "1.5/2 !",
1624 false,
1625 false,
1626 &consts,
1627 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1628 );
1629 assert_eq!(
1630 num,
1631 Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.75).into()))
1632 );
1633 let num = parse_num(
1634 &mut "10e10000000000/2 !",
1635 false,
1636 false,
1637 &consts,
1638 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1639 );
1640 assert_eq!(
1641 num,
1642 Some(Number::Approximate(
1643 Float::with_val(FLOAT_PRECISION, 5).into(),
1644 10000000000u64.into()
1645 ))
1646 );
1647 let num = parse_num(
1648 &mut "10/2 !",
1649 false,
1650 true,
1651 &consts,
1652 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1653 );
1654 assert_eq!(num, Some(Number::Exact(10.into())));
1655 let num = parse_num(
1656 &mut "10/2!",
1657 false,
1658 false,
1659 &consts,
1660 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1661 );
1662 assert_eq!(num, Some(Number::Exact(2.into())));
1663 let num = parse_num(
1664 &mut "^(11)10!",
1665 false,
1666 false,
1667 &consts,
1668 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1669 );
1670 assert_eq!(num, None);
1671 let num = parse_num(
1672 &mut "^(11)10",
1673 false,
1674 false,
1675 &consts,
1676 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1677 );
1678 assert_eq!(
1679 num,
1680 Some(Number::ApproximateDigitsTower(false, false, 10, 1.into()))
1681 );
1682 let num = parse_num(
1683 &mut "10^10^10^5!",
1684 false,
1685 false,
1686 &consts,
1687 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1688 );
1689 assert_eq!(num, None);
1690 let num = parse_num(
1691 &mut "10^10^10^5",
1692 false,
1693 false,
1694 &consts,
1695 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1696 );
1697 assert_eq!(
1698 num,
1699 Some(Number::ApproximateDigitsTower(false, false, 2, 5.into()))
1700 );
1701 let num = parse_num(
1702 &mut "10^(10\\^10\\^\\(5\\))!",
1703 false,
1704 false,
1705 &consts,
1706 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1707 );
1708 assert_eq!(
1709 num,
1710 Some(Number::ApproximateDigitsTower(false, false, 2, 5.into()))
1711 );
1712 let num = parse_num(
1713 &mut "10^5!",
1714 false,
1715 false,
1716 &consts,
1717 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1718 );
1719 assert_eq!(num, None);
1720 let num = parse_num(
1721 &mut "10^5",
1722 false,
1723 false,
1724 &consts,
1725 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1726 );
1727 assert_eq!(num, Some(Number::ApproximateDigits(false, 5.into())));
1728 let num = parse_num(
1729 &mut "10^5",
1730 false,
1731 true,
1732 &consts,
1733 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1734 );
1735 assert_eq!(num, Some(Number::Exact(10.into())));
1736 }
1737 #[test]
1738 fn test_parse_num_revert() {
1739 let consts = Consts::default();
1741 let mut text = "1 ⨉ 10^(5!)";
1742 let num = parse_num(
1743 &mut text,
1744 false,
1745 false,
1746 &consts,
1747 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1748 );
1749 assert_eq!(num, None);
1750 assert_eq!(text, "^(5!)");
1751 let mut text = "^(10 10";
1752 let num = parse_num(
1753 &mut text,
1754 false,
1755 false,
1756 &consts,
1757 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1758 );
1759 assert_eq!(num, None);
1760 assert_eq!(text, "^(10 10");
1761 let mut text = "^(10)1";
1762 let num = parse_num(
1763 &mut text,
1764 false,
1765 false,
1766 &consts,
1767 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1768 );
1769 assert_eq!(num, None);
1770 assert_eq!(text, "^(10)1");
1771 let mut text = "10^(10^10^\\(5\\)";
1772 let num = parse_num(
1773 &mut text,
1774 false,
1775 false,
1776 &consts,
1777 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1778 );
1779 assert_eq!(num, None);
1780 assert_eq!(text, "^(10^10^\\(5\\)");
1781 let mut text = "10^10^10^\\(5\\)!";
1782 let num = parse_num(
1783 &mut text,
1784 false,
1785 false,
1786 &consts,
1787 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1788 );
1789 assert_eq!(num, None);
1790 assert_eq!(text, "^10^\\(5\\)!");
1791 let mut text = "10^30!";
1792 let num = parse_num(
1793 &mut text,
1794 false,
1795 false,
1796 &consts,
1797 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1798 );
1799 assert_eq!(num, None);
1800 assert_eq!(text, "^30!");
1801 }
1802 #[allow(clippy::uninlined_format_args)]
1803 #[test]
1804 fn test_biggest_num() {
1805 let consts = Consts::default();
1806 let num = parse_num(
1807 &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT()).as_str(),
1808 true,
1809 false,
1810 &consts,
1811 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1812 );
1813 assert!(matches!(num, Some(Number::Approximate(_, _))));
1814 let num = parse_num(
1815 &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT() - 1).as_str(),
1816 false,
1817 false,
1818 &consts,
1819 &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1820 );
1821 assert!(num.is_some());
1822 }
1823}