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