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