factorion_lib/
parse.rs

1//! Parses text and extracts calculations
2
3use 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    '!', // PREFIX_OPS
21    '.', // Decimal separators
22    ESCAPE,
23    '0', // Digits
24    '1',
25    '2',
26    '3',
27    '4',
28    '5',
29    '6',
30    '7',
31    '8',
32    '9',
33    'p', // Constants
34    'e',
35    't',
36    'i',
37    'π',
38    'ɸ',
39    'τ',
40    '∞',
41    '^', // Tetration
42    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 = "&gt;!";
59const SPOILER_HTML_END: &str = "!&lt;";
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    // Parsing rules:
90    // - prefix has precedence before suffix (unimplemented)
91    // - anything within a spoiler should be ignored
92    // - operations may be nested through parentheses
93    // - operations can be negated through -
94    // - parens may contain:
95    //   - numbers
96    //   - operations
97    //   - parens
98    //   - whitespace
99    // - operations are:
100    //   - subfactorials !n
101    //   - (multi-)factorials n!+
102    //   - termials n?
103    // - numbers are in order:
104    //   - a string of digits
105    //   - a decimal separator and further digits
106    //   - a base 10 exponent, which is:
107    //     - an e or E followed by
108    //     - optionally a + or -
109    //     - a string of digits
110    // - numbers need to at least have the first or second criteria
111
112    // Parsing:
113    // 1. skip to interesting
114    // 2. If spoiler, skip
115    // 3. If negation, save
116    // 4. If paren start, push (with negation)
117    // 5. If paren end, pop
118    //   1. If had prefix, use base, set base
119    //   2. If has postfix, use base, set base
120    // 6. If prefix
121    //   1. If on number, set base
122    //     1. If has postfix, use base, set base
123    //   2. If on paren, push (with negation and level)
124    // 7. If number, parse
125    //   1. If has postfix, set base
126    //   2. If in parens, set as base
127    // 8. If on toplevel, add base to jobs
128    //
129    // when setting base:
130    // 1. If base is set, add previous to jobs
131    // 2. override base
132    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        // Text (1.)
150        let Some(position_of_interest) = text.find(POI_STARTS) else {
151            break;
152        };
153        if position_of_interest != 0 {
154            // poison paren
155            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        // so we can just ignore everything before
165        text = &text[position_of_interest..];
166        if text.starts_with(ESCAPE) {
167            // Escapes
168            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            // URI
182            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            // Spoiler (2.)
187            let mut end = 0;
188            loop {
189                // look for next end tag
190                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                    // is escaped -> look further
196                    if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
197                        end += 1;
198                        continue;
199                    }
200                    break;
201                } else {
202                    // if we find none, we skip only the start (without the !)
203                    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            // Spoiler (html) (2.)
212            let mut end = 0;
213            loop {
214                // look for next end tag
215                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                    // is escaped -> look further
221                    if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
222                        end += 1;
223                        continue;
224                    }
225                    break;
226                } else {
227                    // if we find none, we skip only the start (without the !)
228                    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            // Negation (3.)
237            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 Start (without prefix op) (4.)
243            paren_steps.push((current_negative, None, false));
244            // Submit current base (we won't use it anymore)
245            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            // Paren End (5.)
253            text = &text[1..];
254            current_negative = 0;
255            // Paren mismatch?
256            let Some(step) = paren_steps.pop() else {
257                continue;
258            };
259            // poisoned paren
260            if step.2 {
261                if let Some(CalculationBase::Calc(job)) = base.take() {
262                    jobs.push(*job);
263                }
264                // no number (maybe var) => poison outer paren
265                if let Some(step) = paren_steps.last_mut() {
266                    step.2 = true;
267                }
268                continue;
269            }
270            let mut had_op = false;
271            // Prefix? (5.2.)
272            if let Some(level) = step.1 {
273                // base available?
274                let Some(inner) = base.take() else {
275                    // no number (maybe var) => poison outer paren
276                    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            // Postfix? (5.1.)
294            let Some(levels) = parse_ops(&mut text, false, do_termial) else {
295                base.take();
296                // no number (maybe var) => poison outer paren
297                if let Some(step) = paren_steps.last_mut() {
298                    step.2 = true;
299                }
300                continue;
301            };
302            if !levels.is_empty() {
303                // Set as base (5.1.2.)
304                for level in levels {
305                    // base available?
306                    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                        // no number (maybe var) => poison outer paren
337                        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            // Prefix OP (6.)
346            let Ok(level) = parse_op(&mut text, true, do_termial) else {
347                // also skip number to prevent stuff like "!!!1!" getting through
348                parse_num(&mut text, false, true, consts, locale);
349                continue;
350            };
351            // On number (6.1.)
352            if let Some(num) = parse_num(&mut text, false, true, consts, locale) {
353                // set base (6.1.2.)
354                if let Some(CalculationBase::Calc(job)) = base.take() {
355                    // multiple number, likely expression => poision paren
356                    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                    // base available?
375                    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                // on paren? (6.2.)
386                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            // Number (7.)
395            if text.starts_with('.') && !text[1..].starts_with(char::is_numeric) {
396                // Is a period
397                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                // advance one char to avoid loop
403                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            // postfix? (7.1.)
411            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                    // multiple number, likely expression => poision paren
418                    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                // in parens? (7.2.)
442                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                        // multiple number, likely expression => poision paren
452                        if let Some(step) = paren_steps.last_mut() {
453                            step.2 = true;
454                        }
455                    }
456                }
457            }
458            current_negative = 0;
459        };
460        // toplevel? (8.)
461        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            // Skip ^ only (because ret None)
580            *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            // Don't skip power if op won't be in exponent, so it will be skipped by tetration parsing
594            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            // we're at base and pushed a paren
622            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 postfix op in exponent, ingore that it's in exponent
653        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            //  10^(50)! => (10^50)!, 10^50! => 50!
751            // if !paren text ohne 10^ else text mit 10^
752            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        // Note that we want one extra character when we get None, as in such a situation a char will always be skipped
1740        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}