Skip to main content

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 = || 200_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
569            .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || &c == locale.decimal())
570            .unwrap_or(text.len());
571        let part = &text[..end];
572        *text = &text[end..];
573        if text.starts_with(")10")
574            && !text[3..].starts_with(POSTFIX_OPS)
575            && !text[3..].starts_with(char::is_numeric)
576            // Intentionally not allowing decimal
577            && !(text[3..].starts_with(SEPARATORS) && text[4..].starts_with(char::is_numeric))
578        {
579            *text = &text[3..];
580            let part = part.replace(SEPARATORS, "");
581            let n = part.parse::<Integer>().ok()?;
582            match n.to_usize() {
583                Some(0) => return Some(1.into()),
584                Some(1) => return Some(10.into()),
585                Some(2) => return Some(Number::Exact(10_000_000_000u64.into())),
586                _ => {
587                    return Some(Number::ApproximateDigitsTower(
588                        false,
589                        false,
590                        n - 1,
591                        1.into(),
592                    ));
593                }
594            }
595        } else {
596            // Skip ^ only (because ret None)
597            *text = orig_text;
598            return None;
599        }
600    }
601
602    if text.starts_with("10^") || text.starts_with("10\\^") {
603        let orig_text = &text[..];
604        if had_op {
605            if &text[2..3] == "^" {
606                *text = &text[3..];
607            } else {
608                *text = &text[4..];
609            }
610            // Don't skip power if op won't be in exponent, so it will be skipped by tetration parsing
611            if text.starts_with("(") || text.starts_with("\\(") {
612                *text = &orig_text[2..];
613            }
614            return Some(Number::Exact(10.into()));
615        }
616        let mut cur_parens = 0;
617        let mut depth = 0;
618
619        let mut max_no_paren_level = 0;
620        let mut no_paren_inner = &text[0..];
621
622        while text.starts_with("10^") || text.starts_with("10\\^") {
623            let base_text;
624            if &text[2..3] == "^" {
625                base_text = &text[2..];
626                *text = &text[3..];
627            } else {
628                base_text = &text[3..];
629                *text = &text[4..];
630            }
631            if text.starts_with("\\(") {
632                *text = &text[2..];
633                cur_parens += 1;
634            } else if text.starts_with("(") {
635                *text = &text[1..];
636                cur_parens += 1;
637            }
638            // we're at base and pushed a paren
639            if depth == max_no_paren_level && cur_parens == 0 {
640                max_no_paren_level += 1;
641                no_paren_inner = base_text;
642            }
643            depth += 1;
644        }
645
646        let top = match parse_num_simple(text, had_op, consts, locale, prec) {
647            Some(Number::Exact(n)) => n,
648            Some(Number::Approximate(_, n)) => {
649                depth += 1;
650                n
651            }
652            _ => {
653                *text = &orig_text[3..];
654                return None;
655            }
656        };
657
658        for _ in 0..cur_parens {
659            if text.starts_with("\\)") {
660                *text = &text[2..];
661            } else if text.starts_with(")") {
662                *text = &text[1..];
663            } else {
664                *text = &orig_text[2..];
665                return None;
666            }
667        }
668
669        // If postfix op in exponent, ingore that it's in exponent
670        if max_no_paren_level != 0 && text.starts_with(POSTFIX_OPS) {
671            *text = no_paren_inner;
672            return None;
673        }
674
675        if depth == 1 {
676            if top < consts.integer_construction_limit {
677                return Some(Number::Exact(
678                    Integer::u64_pow_u64(10, top.to_u64().unwrap()).complete(),
679                ));
680            }
681            return Some(Number::ApproximateDigits(false, top));
682        } else {
683            return Some(Number::ApproximateDigitsTower(
684                false,
685                false,
686                (depth - 1).into(),
687                top,
688            ));
689        }
690    }
691
692    parse_num_simple(text, had_op, consts, locale, prec)
693}
694
695fn parse_num_simple(
696    text: &mut &str,
697    had_op: bool,
698    consts: &Consts<'_>,
699    locale: &NumFormat<'_>,
700    prec: u32,
701) -> Option<crate::calculation_results::CalculationResult> {
702    let integer_part = {
703        let end = text
704            .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || &c == locale.decimal())
705            .unwrap_or(text.len());
706        let part = &text[..end];
707        *text = &text[end..];
708        part
709    };
710    let decimal_part = if text.starts_with(*locale.decimal()) {
711        *text = &text[1..];
712        let end = text
713            .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || &c == locale.decimal())
714            .unwrap_or(text.len());
715        let part = &text[..end];
716        *text = &text[end..];
717        part
718    } else {
719        &text[..0]
720    };
721    let exponent_part = if text.starts_with(['e', 'E']) {
722        *text = &text[1..];
723        let negative = if text.starts_with('+') {
724            *text = &text[1..];
725            false
726        } else if text.starts_with('-') {
727            *text = &text[1..];
728            true
729        } else {
730            false
731        };
732        let end = text
733            .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || &c == locale.decimal())
734            .unwrap_or(text.len());
735        let part = &text[..end];
736        *text = &text[end..];
737        (part, negative)
738    } else if !had_op
739        && (text.trim_start().starts_with("\\*10^")
740            || text.trim_start().starts_with("\\* 10^")
741            || text.trim_start().starts_with("\\*10\\^")
742            || text.trim_start().starts_with("\\* 10\\^")
743            || text.trim_start().starts_with("⨉10^")
744            || text.trim_start().starts_with("⨉ 10^")
745            || text.trim_start().starts_with("⨉10\\^")
746            || text.trim_start().starts_with("⨉ 10\\^")
747            || text.trim_start().starts_with("x10^")
748            || text.trim_start().starts_with("x 10^")
749            || text.trim_start().starts_with("x10\\^")
750            || text.trim_start().starts_with("x 10\\^"))
751    {
752        let pre_orig_text = &text[..];
753        let start = text.find("^").unwrap();
754        let orig_text = &text[start..];
755        *text = &text[start + 1..];
756        let paren = if text.starts_with('(') {
757            *text = &text[1..];
758            true
759        } else {
760            false
761        };
762        let negative = if text.starts_with('+') {
763            *text = &text[1..];
764            false
765        } else if text.starts_with('-') {
766            *text = &text[1..];
767            true
768        } else {
769            false
770        };
771        let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
772        let part = &text[..end];
773        *text = &text[end..];
774        if paren {
775            if text.starts_with(')') {
776                *text = &text[1..];
777            } else {
778                *text = orig_text;
779                return None;
780            }
781        }
782        if text.starts_with(POSTFIX_OPS) {
783            //  10^(50)! => (10^50)!, 10^50! => 50!
784            // if !paren text ohne 10^ else text mit 10^
785            if paren {
786                *text = pre_orig_text;
787            } else {
788                *text = orig_text;
789            }
790            return None;
791        }
792        (part, negative)
793    } else {
794        (&text[..0], false)
795    };
796    let fraction_part = if !had_op && text.starts_with(['/']) {
797        *text = &text[1..];
798        let end = text
799            .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || &c == locale.decimal())
800            .unwrap_or(text.len());
801        let part = &text[..end];
802        *text = &text[end..];
803        part
804    } else {
805        &text[..0]
806    };
807    if text.starts_with(POSTFIX_OPS) && !fraction_part.is_empty() {
808        let fraction_part = fraction_part.replace(SEPARATORS, "");
809        let n = fraction_part.parse::<Integer>().ok()?;
810        return Some(Number::Exact(n));
811    }
812    if integer_part.is_empty() && decimal_part.is_empty() {
813        return None;
814    }
815    let exponent = if !exponent_part.0.is_empty() {
816        let exponent_part = (exponent_part.0.replace(SEPARATORS, ""), exponent_part.1);
817        let mut e = exponent_part.0.parse::<Integer>().ok()?;
818        if exponent_part.1 {
819            e *= -1;
820        }
821        e
822    } else {
823        0.into()
824    };
825    let divisor = if !fraction_part.is_empty() {
826        let fraction_part = fraction_part.replace(SEPARATORS, "");
827        fraction_part.parse::<Integer>().ok()?
828    } else {
829        Integer::ONE.clone()
830    };
831    let integer_part = integer_part.replace(SEPARATORS, "");
832    let decimal_part = decimal_part.replace(SEPARATORS, "");
833    if exponent >= decimal_part.len() as i64
834        && exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64
835        && (divisor == 1 || exponent >= consts.integer_construction_limit.clone() / 10)
836    {
837        let exponent = exponent - decimal_part.len();
838        let n = format!("{integer_part}{decimal_part}")
839            .parse::<Integer>()
840            .ok()?;
841        let num = (n * Integer::u64_pow_u64(10, exponent.to_u64().unwrap()).complete()) / divisor;
842        Some(Number::Exact(num))
843    } else if exponent <= consts.integer_construction_limit.clone() - integer_part.len() as i64 {
844        let x = Float::parse(format!(
845            "{integer_part}.{decimal_part}{}{}{}",
846            if !exponent_part.0.is_empty() { "e" } else { "" },
847            if exponent_part.1 { "-" } else { "" },
848            exponent_part.0
849        ))
850        .ok()?;
851        let x = Float::with_val(prec, x) / divisor;
852        if x.is_integer() {
853            let n = x.to_integer().unwrap();
854            Some(Number::Exact(n))
855        } else if x.is_finite() {
856            Some(Number::Float(x.into()))
857        } else {
858            None
859        }
860    } else {
861        let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?;
862        let x = Float::with_val(prec, x) / divisor;
863        if x.is_finite() {
864            let (b, e) = crate::math::adjust_approximate((x, exponent));
865            Some(Number::Approximate(b.into(), e))
866        } else {
867            None
868        }
869    }
870}
871
872#[cfg(test)]
873mod test {
874    use super::*;
875    use crate::calculation_tasks::CalculationBase::Num;
876    use arbtest::arbtest;
877
878    use crate::recommended::FLOAT_PRECISION;
879
880    #[test]
881    fn test_text_only() {
882        let consts = Consts::default();
883        let jobs = parse(
884            "just some words of encouragement!",
885            true,
886            &consts,
887            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
888        );
889        assert_eq!(jobs, []);
890    }
891    #[test]
892    fn test_factorial() {
893        let consts = Consts::default();
894        let jobs = parse(
895            "a factorial 15!",
896            true,
897            &consts,
898            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
899        );
900        assert_eq!(
901            jobs,
902            [CalculationJob {
903                base: CalculationBase::Num(15.into()),
904                level: 1,
905                negative: 0
906            }]
907        );
908    }
909    #[test]
910    fn test_multifactorial() {
911        let consts = Consts::default();
912        let jobs = parse(
913            "a factorial 15!!! actually a multi",
914            true,
915            &consts,
916            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
917        );
918        assert_eq!(
919            jobs,
920            [CalculationJob {
921                base: CalculationBase::Num(15.into()),
922                level: 3,
923                negative: 0
924            }]
925        );
926    }
927    #[test]
928    fn test_subfactorial() {
929        let consts = Consts::default();
930        let jobs = parse(
931            "a factorial !15 actually a sub",
932            true,
933            &consts,
934            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
935        );
936        assert_eq!(
937            jobs,
938            [CalculationJob {
939                base: CalculationBase::Num(15.into()),
940                level: 0,
941                negative: 0
942            }]
943        );
944    }
945    #[test]
946    fn test_submultifactorial() {
947        let consts = Consts::default();
948        let jobs = parse(
949            "not well defined !!!15",
950            true,
951            &consts,
952            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
953        );
954        assert_eq!(jobs, []);
955    }
956    #[test]
957    fn test_termial() {
958        let consts = Consts::default();
959        let jobs = parse(
960            "a termial 15?",
961            true,
962            &consts,
963            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
964        );
965        assert_eq!(
966            jobs,
967            [CalculationJob {
968                base: CalculationBase::Num(15.into()),
969                level: -1,
970                negative: 0
971            }]
972        );
973    }
974    #[test]
975    fn test_no_termial() {
976        let consts = Consts::default();
977        let jobs = parse(
978            "not enabled 15?",
979            false,
980            &consts,
981            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
982        );
983        assert_eq!(jobs, []);
984    }
985    #[test]
986    fn test_multitermial() {
987        let consts = Consts::default();
988        let jobs = parse(
989            "a termial 15??? actually a multi",
990            true,
991            &consts,
992            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
993        );
994        assert_eq!(
995            jobs,
996            [CalculationJob {
997                base: CalculationBase::Num(15.into()),
998                level: -3,
999                negative: 0
1000            }]
1001        );
1002    }
1003    #[test]
1004    fn test_subtermial() {
1005        let consts = Consts::default();
1006        let jobs = parse(
1007            "a termial ?15 actually a sub",
1008            true,
1009            &consts,
1010            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1011        );
1012        assert_eq!(jobs, []);
1013    }
1014    #[test]
1015    fn test_chain() {
1016        let consts = Consts::default();
1017        let jobs = parse(
1018            "a factorialchain (15!)!",
1019            true,
1020            &consts,
1021            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1022        );
1023        assert_eq!(
1024            jobs,
1025            [CalculationJob {
1026                base: CalculationBase::Calc(Box::new(CalculationJob {
1027                    base: CalculationBase::Num(15.into()),
1028                    level: 1,
1029                    negative: 0
1030                })),
1031                level: 1,
1032                negative: 0
1033            }]
1034        );
1035    }
1036    #[test]
1037    fn test_mixed_chain() {
1038        let consts = Consts::default();
1039        let jobs = parse(
1040            "a factorialchain !(15!)",
1041            true,
1042            &consts,
1043            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1044        );
1045        assert_eq!(
1046            jobs,
1047            [CalculationJob {
1048                base: CalculationBase::Calc(Box::new(CalculationJob {
1049                    base: CalculationBase::Num(15.into()),
1050                    level: 1,
1051                    negative: 0
1052                })),
1053                level: 0,
1054                negative: 0
1055            }]
1056        );
1057    }
1058    #[test]
1059    fn test_postfix_chain() {
1060        let consts = Consts::default();
1061        let jobs = parse(
1062            "a factorialchain -15!?",
1063            true,
1064            &consts,
1065            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1066        );
1067        assert_eq!(
1068            jobs,
1069            [CalculationJob {
1070                base: CalculationBase::Calc(Box::new(CalculationJob {
1071                    base: CalculationBase::Num(15.into()),
1072                    level: 1,
1073                    negative: 0
1074                })),
1075                level: -1,
1076                negative: 1
1077            }]
1078        );
1079    }
1080    #[test]
1081    fn test_negative() {
1082        let consts = Consts::default();
1083        let jobs = parse(
1084            "a factorial ---15!",
1085            true,
1086            &consts,
1087            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1088        );
1089        assert_eq!(
1090            jobs,
1091            [CalculationJob {
1092                base: CalculationBase::Num(15.into()),
1093                level: 1,
1094                negative: 3
1095            }]
1096        );
1097    }
1098    #[test]
1099    fn test_negative_gap() {
1100        let consts = Consts::default();
1101        let jobs = parse(
1102            "a factorial --- 15!",
1103            true,
1104            &consts,
1105            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1106        );
1107        assert_eq!(
1108            jobs,
1109            [CalculationJob {
1110                base: CalculationBase::Num(15.into()),
1111                level: 1,
1112                negative: 0
1113            }]
1114        );
1115    }
1116    #[test]
1117    fn test_paren() {
1118        let consts = Consts::default();
1119        let jobs = parse(
1120            "a factorial (15)!",
1121            true,
1122            &consts,
1123            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1124        );
1125        assert_eq!(
1126            jobs,
1127            [CalculationJob {
1128                base: CalculationBase::Num(15.into()),
1129                level: 1,
1130                negative: 0
1131            }]
1132        );
1133    }
1134    #[test]
1135    fn test_in_paren() {
1136        let consts = Consts::default();
1137        let jobs = parse(
1138            "a factorial (15!)",
1139            true,
1140            &consts,
1141            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1142        );
1143        assert_eq!(
1144            jobs,
1145            [CalculationJob {
1146                base: CalculationBase::Num(15.into()),
1147                level: 1,
1148                negative: 0
1149            }]
1150        );
1151    }
1152    #[test]
1153    fn test_decimal() {
1154        let consts = Consts::default();
1155        let jobs = parse(
1156            "a factorial 1.5!",
1157            true,
1158            &consts,
1159            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1160        );
1161        assert_eq!(
1162            jobs,
1163            [CalculationJob {
1164                base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, 1.5).into()),
1165                level: 1,
1166                negative: 0
1167            }]
1168        );
1169    }
1170    #[test]
1171    fn test_negative_decimal() {
1172        let consts = Consts::default();
1173        let jobs = parse(
1174            "a factorial (-1.5)!",
1175            true,
1176            &consts,
1177            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1178        );
1179        assert_eq!(
1180            jobs,
1181            [CalculationJob {
1182                base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, -1.5).into()),
1183                level: 1,
1184                negative: 0
1185            }]
1186        );
1187    }
1188    #[test]
1189    fn test_paren_negation() {
1190        let consts = Consts::default();
1191        let jobs = parse(
1192            "a factorial -(--(-(-(-3))!))!",
1193            true,
1194            &consts,
1195            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1196        );
1197        assert_eq!(
1198            jobs,
1199            [CalculationJob {
1200                base: CalculationBase::Calc(Box::new(CalculationJob {
1201                    base: CalculationBase::Num(3.into()),
1202                    level: 1,
1203                    negative: 3
1204                })),
1205                level: 1,
1206                negative: 1
1207            }]
1208        );
1209    }
1210    #[test]
1211    fn test_tag() {
1212        let consts = Consts::default();
1213        let jobs = parse(
1214            ">!5 a factorial 15! !<",
1215            true,
1216            &consts,
1217            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1218        );
1219        assert_eq!(jobs, []);
1220    }
1221    #[test]
1222    fn test_incomplete_tag() {
1223        let consts = Consts::default();
1224        let jobs = parse(
1225            ">!5 a factorial 15!",
1226            true,
1227            &consts,
1228            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1229        );
1230        assert_eq!(
1231            jobs,
1232            [
1233                CalculationJob {
1234                    base: CalculationBase::Num(5.into()),
1235                    level: 0,
1236                    negative: 0
1237                },
1238                CalculationJob {
1239                    base: CalculationBase::Num(15.into()),
1240                    level: 1,
1241                    negative: 0
1242                }
1243            ]
1244        );
1245    }
1246    #[test]
1247    fn test_escaped_tag() {
1248        let consts = Consts::default();
1249        let jobs = parse(
1250            "\\>!5 a factorial 15! !<",
1251            true,
1252            &consts,
1253            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1254        );
1255        assert_eq!(
1256            jobs,
1257            [
1258                CalculationJob {
1259                    base: CalculationBase::Num(5.into()),
1260                    level: 0,
1261                    negative: 0
1262                },
1263                CalculationJob {
1264                    base: CalculationBase::Num(15.into()),
1265                    level: 1,
1266                    negative: 0
1267                }
1268            ]
1269        );
1270    }
1271    #[test]
1272    fn test_escaped_tag2() {
1273        let consts = Consts::default();
1274        let jobs = parse(
1275            ">!5 a factorial 15! \\!<",
1276            true,
1277            &consts,
1278            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1279        );
1280        assert_eq!(
1281            jobs,
1282            [
1283                CalculationJob {
1284                    base: CalculationBase::Num(5.into()),
1285                    level: 0,
1286                    negative: 0
1287                },
1288                CalculationJob {
1289                    base: CalculationBase::Num(15.into()),
1290                    level: 1,
1291                    negative: 0
1292                }
1293            ]
1294        );
1295    }
1296
1297    #[test]
1298    fn test_url() {
1299        let consts = Consts::default();
1300        let jobs = parse(
1301            "https://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1302            true,
1303            &consts,
1304            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1305        );
1306        assert_eq!(jobs, []);
1307    }
1308
1309    #[test]
1310    fn test_uri_poi_doesnt_cause_infinite_loop() {
1311        let consts = Consts::default();
1312        let jobs = parse(
1313            "84!:",
1314            true,
1315            &consts,
1316            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1317        );
1318        assert_eq!(
1319            jobs,
1320            [CalculationJob {
1321                base: Num(84.into()),
1322                level: 1,
1323                negative: 0
1324            }]
1325        );
1326    }
1327    #[test]
1328    fn test_escaped_url() {
1329        let consts = Consts::default();
1330        let jobs = parse(
1331            "\\://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1332            true,
1333            &consts,
1334            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1335        );
1336        assert_eq!(
1337            jobs,
1338            [CalculationJob {
1339                base: CalculationBase::Num(8743.into()),
1340                level: 1,
1341                negative: 0
1342            }]
1343        );
1344    }
1345
1346    #[test]
1347    fn test_word_in_paren() {
1348        let consts = Consts::default();
1349        let jobs = parse(
1350            "(x-2)! (2 word)! ((x/k)-3)! (,x-4)!",
1351            true,
1352            &consts,
1353            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1354        );
1355        assert_eq!(jobs, []);
1356    }
1357
1358    #[test]
1359    fn test_multi_number_paren() {
1360        let consts = Consts::default();
1361        let jobs = parse(
1362            "(5-2)!",
1363            true,
1364            &consts,
1365            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1366        );
1367        assert_eq!(jobs, []);
1368    }
1369    #[test]
1370    fn test_arbitrary_input() {
1371        let consts = Consts::default();
1372        arbtest(|u| {
1373            let text: &str = u.arbitrary()?;
1374            let _ = parse(
1375                text,
1376                u.arbitrary()?,
1377                &consts,
1378                &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1379            );
1380            Ok(())
1381        });
1382    }
1383
1384    #[test]
1385    fn test_constant() {
1386        let consts = Consts::default();
1387        let jobs = parse(
1388            "!espi!",
1389            true,
1390            &consts,
1391            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1392        );
1393        assert_eq!(jobs, []);
1394        let jobs = parse(
1395            "some. pi!",
1396            true,
1397            &consts,
1398            &consts.locales.get("en").unwrap().format().number_format(),
1399        );
1400        assert_eq!(
1401            jobs,
1402            [CalculationJob {
1403                base: CalculationBase::Num(Number::Float(
1404                    Float::with_val(FLOAT_PRECISION, factorion_math::rug::float::Constant::Pi)
1405                        .into()
1406                )),
1407                level: 1,
1408                negative: 0
1409            }]
1410        );
1411    }
1412
1413    #[test]
1414    fn test_fraction() {
1415        let consts = Consts::default();
1416        let jobs = parse(
1417            "!5/6!",
1418            true,
1419            &consts,
1420            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1421        );
1422        assert_eq!(
1423            jobs,
1424            [
1425                CalculationJob {
1426                    base: CalculationBase::Num(Number::Exact(5.into())),
1427                    level: 0,
1428                    negative: 0
1429                },
1430                CalculationJob {
1431                    base: CalculationBase::Num(Number::Exact(6.into())),
1432                    level: 1,
1433                    negative: 0
1434                }
1435            ]
1436        );
1437        let jobs = parse(
1438            "5/6!",
1439            true,
1440            &consts,
1441            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1442        );
1443        assert_eq!(
1444            jobs,
1445            [CalculationJob {
1446                base: CalculationBase::Num(Number::Exact(6.into())),
1447                level: 1,
1448                negative: 0
1449            }]
1450        );
1451        let jobs = parse(
1452            "(10/2)!",
1453            true,
1454            &consts,
1455            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1456        );
1457        assert_eq!(
1458            jobs,
1459            [CalculationJob {
1460                base: CalculationBase::Num(Number::Exact(5.into())),
1461                level: 1,
1462                negative: 0
1463            },]
1464        );
1465    }
1466
1467    #[test]
1468    fn test_parse_num() {
1469        let consts = Consts::default();
1470        let num = parse_num(
1471            &mut "1.5more !",
1472            false,
1473            false,
1474            &consts,
1475            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1476        );
1477        assert_eq!(
1478            num,
1479            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1480        );
1481        let num = parse_num(
1482            &mut "1,5more !",
1483            false,
1484            false,
1485            &consts,
1486            &NumFormat::V1(&locale::v1::NumFormat { decimal: ',' }),
1487        );
1488        assert_eq!(
1489            num,
1490            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1491        );
1492        let num = parse_num(
1493            &mut ".5more !",
1494            false,
1495            false,
1496            &consts,
1497            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1498        );
1499        let num = parse_num(
1500            &mut "1more !",
1501            false,
1502            true,
1503            &consts,
1504            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1505        );
1506        assert_eq!(num, Some(1.into()));
1507        let num = parse_num(
1508            &mut "1_000more !",
1509            false,
1510            true,
1511            &consts,
1512            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1513        );
1514        assert_eq!(num, Some(1000.into()));
1515        let num = parse_num(
1516            &mut "1,000more !",
1517            false,
1518            true,
1519            &consts,
1520            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1521        );
1522        assert_eq!(num, Some(1000.into()));
1523        let num = parse_num(
1524            &mut "1'000more !",
1525            false,
1526            true,
1527            &consts,
1528            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1529        );
1530        assert_eq!(num, Some(1000.into()));
1531        let num = parse_num(
1532            &mut "1.000more !",
1533            false,
1534            true,
1535            &consts,
1536            &NumFormat::V1(&locale::v1::NumFormat { decimal: ',' }),
1537        );
1538        assert_eq!(num, Some(1000.into()));
1539        let num = parse_num(
1540            &mut "1.000more !",
1541            false,
1542            true,
1543            &consts,
1544            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1545        );
1546        assert_eq!(num, Some(1.into()));
1547        let num = parse_num(
1548            &mut "1.0more !",
1549            true,
1550            false,
1551            &consts,
1552            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1553        );
1554        assert_eq!(num, Some(1.into()));
1555        let num = parse_num(
1556            &mut "1.5e2more !",
1557            false,
1558            false,
1559            &consts,
1560            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1561        );
1562        assert_eq!(num, Some(150.into()));
1563        let num = parse_num(
1564            &mut "1.5 ⨉ 10^2more !",
1565            false,
1566            false,
1567            &consts,
1568            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1569        );
1570        assert_eq!(num, Some(150.into()));
1571        let num = parse_num(
1572            &mut "1e2more !",
1573            false,
1574            false,
1575            &consts,
1576            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1577        );
1578        assert_eq!(num, Some(100.into()));
1579        let num = parse_num(
1580            &mut "1\\*10^(2)more !",
1581            false,
1582            false,
1583            &consts,
1584            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1585        );
1586        assert_eq!(num, Some(100.into()));
1587        let num = parse_num(
1588            &mut "1.531e2more !",
1589            false,
1590            false,
1591            &consts,
1592            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1593        );
1594        let Some(Number::Float(f)) = num else {
1595            panic!("Not a float")
1596        };
1597        assert!(Float::abs(f.as_float().clone() - 153.1) < 0.0000001);
1598        let num = parse_num(
1599            &mut "5e-1more !",
1600            false,
1601            false,
1602            &consts,
1603            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1604        );
1605        assert_eq!(
1606            num,
1607            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1608        );
1609        let num = parse_num(
1610            &mut "e2more !",
1611            true,
1612            false,
1613            &consts,
1614            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1615        );
1616        assert_eq!(num, None);
1617        let num = parse_num(
1618            &mut "es !",
1619            false,
1620            false,
1621            &consts,
1622            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1623        );
1624        assert_eq!(num, None);
1625        let num = parse_num(
1626            &mut "e !",
1627            false,
1628            false,
1629            &consts,
1630            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1631        );
1632        assert_eq!(num, Some(E(FLOAT_PRECISION)));
1633        let num = parse_num(
1634            &mut "pi !",
1635            false,
1636            false,
1637            &consts,
1638            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1639        );
1640        assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1641        let num = parse_num(
1642            &mut "π !",
1643            false,
1644            false,
1645            &consts,
1646            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1647        );
1648        assert_eq!(num, Some(PI(FLOAT_PRECISION)));
1649        let num = parse_num(
1650            &mut "phi !",
1651            false,
1652            false,
1653            &consts,
1654            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1655        );
1656        assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1657        let num = parse_num(
1658            &mut "ɸ !",
1659            false,
1660            false,
1661            &consts,
1662            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1663        );
1664        assert_eq!(num, Some(PHI(FLOAT_PRECISION)));
1665        let num = parse_num(
1666            &mut "tau !",
1667            false,
1668            false,
1669            &consts,
1670            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1671        );
1672        assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1673        let num = parse_num(
1674            &mut "τ !",
1675            false,
1676            false,
1677            &consts,
1678            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1679        );
1680        assert_eq!(num, Some(TAU(FLOAT_PRECISION)));
1681        let num = parse_num(
1682            &mut "∞\u{0303} !",
1683            false,
1684            false,
1685            &consts,
1686            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1687        );
1688        assert_eq!(num, Some(Number::ComplexInfinity));
1689        let num = parse_num(
1690            &mut "∞ !",
1691            false,
1692            false,
1693            &consts,
1694            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1695        );
1696        assert_eq!(num, Some(Number::ComplexInfinity));
1697        let num = parse_num(
1698            &mut "1/2 !",
1699            false,
1700            false,
1701            &consts,
1702            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1703        );
1704        assert_eq!(
1705            num,
1706            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1707        );
1708        let num = parse_num(
1709            &mut "10/2 !",
1710            false,
1711            false,
1712            &consts,
1713            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1714        );
1715        assert_eq!(num, Some(Number::Exact(5.into())));
1716        let num = parse_num(
1717            &mut "1.5/2 !",
1718            false,
1719            false,
1720            &consts,
1721            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1722        );
1723        assert_eq!(
1724            num,
1725            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.75).into()))
1726        );
1727        let num = parse_num(
1728            &mut "10e10000000000/2 !",
1729            false,
1730            false,
1731            &consts,
1732            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1733        );
1734        assert_eq!(
1735            num,
1736            Some(Number::Approximate(
1737                Float::with_val(FLOAT_PRECISION, 5).into(),
1738                10000000000u64.into()
1739            ))
1740        );
1741        let num = parse_num(
1742            &mut "10/2 !",
1743            false,
1744            true,
1745            &consts,
1746            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1747        );
1748        assert_eq!(num, Some(Number::Exact(10.into())));
1749        let num = parse_num(
1750            &mut "10/2!",
1751            false,
1752            false,
1753            &consts,
1754            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1755        );
1756        assert_eq!(num, Some(Number::Exact(2.into())));
1757        let num = parse_num(
1758            &mut "^(11)10!",
1759            false,
1760            false,
1761            &consts,
1762            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1763        );
1764        assert_eq!(num, None);
1765        let num = parse_num(
1766            &mut "^(50)100!",
1767            false,
1768            false,
1769            &consts,
1770            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1771        );
1772        assert_eq!(num, None);
1773        let num = parse_num(
1774            &mut "^(50)1000!",
1775            false,
1776            false,
1777            &consts,
1778            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1779        );
1780        assert_eq!(num, None);
1781        let num = parse_num(
1782            &mut "^(50)10,000!",
1783            false,
1784            false,
1785            &consts,
1786            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1787        );
1788        assert_eq!(num, None);
1789        let num = parse_num(
1790            &mut "^(11)10",
1791            false,
1792            false,
1793            &consts,
1794            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1795        );
1796        assert_eq!(
1797            num,
1798            Some(Number::ApproximateDigitsTower(
1799                false,
1800                false,
1801                10.into(),
1802                1.into()
1803            ))
1804        );
1805        let num = parse_num(
1806            &mut "^(11,000)10",
1807            false,
1808            false,
1809            &consts,
1810            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1811        );
1812        assert_eq!(
1813            num,
1814            Some(Number::ApproximateDigitsTower(
1815                false,
1816                false,
1817                10999.into(),
1818                1.into()
1819            ))
1820        );
1821        let num = parse_num(
1822            &mut "10^10^10^5!",
1823            false,
1824            false,
1825            &consts,
1826            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1827        );
1828        assert_eq!(num, None);
1829        let num = parse_num(
1830            &mut "10^10^10^5",
1831            false,
1832            false,
1833            &consts,
1834            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1835        );
1836        assert_eq!(
1837            num,
1838            Some(Number::ApproximateDigitsTower(
1839                false,
1840                false,
1841                2.into(),
1842                5.into()
1843            ))
1844        );
1845        let num = parse_num(
1846            &mut "10^(10\\^10\\^\\(5\\))!",
1847            false,
1848            false,
1849            &consts,
1850            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1851        );
1852        assert_eq!(
1853            num,
1854            Some(Number::ApproximateDigitsTower(
1855                false,
1856                false,
1857                2.into(),
1858                5.into()
1859            ))
1860        );
1861        let num = parse_num(
1862            &mut "10^50000000000",
1863            false,
1864            false,
1865            &consts,
1866            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1867        );
1868        assert_eq!(
1869            num,
1870            Some(Number::ApproximateDigits(false, 50000000000u64.into()))
1871        );
1872        let num = parse_num(
1873            &mut "10^5!",
1874            false,
1875            false,
1876            &consts,
1877            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1878        );
1879        assert_eq!(num, None);
1880        let num = parse_num(
1881            &mut "10^5",
1882            false,
1883            false,
1884            &consts,
1885            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1886        );
1887        assert_eq!(num, Some(Number::Exact(100000.into())));
1888        let num = parse_num(
1889            &mut "10^5",
1890            false,
1891            true,
1892            &consts,
1893            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1894        );
1895        assert_eq!(num, Some(Number::Exact(10.into())));
1896    }
1897    #[test]
1898    fn test_parse_num_revert() {
1899        // Note that we want one extra character when we get None, as in such a situation a char will always be skipped
1900        let consts = Consts::default();
1901        let mut text = "1 ⨉ 10^(5!)";
1902        let num = parse_num(
1903            &mut text,
1904            false,
1905            false,
1906            &consts,
1907            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1908        );
1909        assert_eq!(num, None);
1910        assert_eq!(text, "^(5!)");
1911        let mut text = "^(10 10";
1912        let num = parse_num(
1913            &mut text,
1914            false,
1915            false,
1916            &consts,
1917            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1918        );
1919        assert_eq!(num, None);
1920        assert_eq!(text, "^(10 10");
1921        let mut text = "^(10)1";
1922        let num = parse_num(
1923            &mut text,
1924            false,
1925            false,
1926            &consts,
1927            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1928        );
1929        assert_eq!(num, None);
1930        assert_eq!(text, "^(10)1");
1931        let mut text = "10^(10^10^\\(5\\)";
1932        let num = parse_num(
1933            &mut text,
1934            false,
1935            false,
1936            &consts,
1937            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1938        );
1939        assert_eq!(num, None);
1940        assert_eq!(text, "^(10^10^\\(5\\)");
1941        let mut text = "10^10^10^\\(5\\)!";
1942        let num = parse_num(
1943            &mut text,
1944            false,
1945            false,
1946            &consts,
1947            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1948        );
1949        assert_eq!(num, None);
1950        assert_eq!(text, "^10^\\(5\\)!");
1951        let mut text = "10^30!";
1952        let num = parse_num(
1953            &mut text,
1954            false,
1955            false,
1956            &consts,
1957            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1958        );
1959        assert_eq!(num, None);
1960        assert_eq!(text, "^30!");
1961    }
1962
1963    #[test]
1964    fn test_parse_num_absorb() {
1965        // Note that we want one extra character when we get None, as in such a situation a char will always be skipped
1966        let consts = Consts::default();
1967    }
1968
1969    #[allow(clippy::uninlined_format_args)]
1970    #[test]
1971    fn test_biggest_num() {
1972        let consts = Consts::default();
1973        let num = parse_num(
1974            &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT()).as_str(),
1975            true,
1976            false,
1977            &consts,
1978            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1979        );
1980        assert!(matches!(num, Some(Number::Approximate(_, _))));
1981        let num = parse_num(
1982            &mut format!("9e{}", recommended::INTEGER_CONSTRUCTION_LIMIT() - 1).as_str(),
1983            false,
1984            false,
1985            &consts,
1986            &NumFormat::V1(&locale::v1::NumFormat { decimal: '.' }),
1987        );
1988        assert!(num.is_some());
1989    }
1990}