factorion_lib/
parse.rs

1//! Parses text and extracts calculations
2use std::sync::{LazyLock, OnceLock};
3
4use crate::rug::{Complete, Float, Integer, integer::IntegerExt64};
5
6use crate::{
7    FLOAT_PRECISION,
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 = || 100_000_000u128.into();
16}
17static INTEGER_CONSTRUCTION_LIMIT: OnceLock<Integer> = OnceLock::new();
18
19use crate::AlreadyInit;
20pub fn init(integer_construction_limit: Integer) -> Result<(), AlreadyInit> {
21    static INITIALIZING: std::sync::Mutex<()> = std::sync::Mutex::new(());
22    let _guard = INITIALIZING.lock();
23    INTEGER_CONSTRUCTION_LIMIT
24        .set(integer_construction_limit)
25        .map_err(|_| AlreadyInit)?;
26    Ok(())
27}
28pub fn init_default() -> Result<(), AlreadyInit> {
29    init(recommended::INTEGER_CONSTRUCTION_LIMIT())
30}
31
32const POI_STARTS: &[char] = &[
33    NEGATION,
34    '!', // PREFIX_OPS
35    '.', // Decimal separators
36    ESCAPE,
37    '0', // Digits
38    '1',
39    '2',
40    '3',
41    '4',
42    '5',
43    '6',
44    '7',
45    '8',
46    '9',
47    'p', // Constants
48    'e',
49    't',
50    'π',
51    'ɸ',
52    'τ',
53    URI_POI,
54    SPOILER_POI,
55    SPOILER_HTML_POI,
56    PAREN_START,
57    PAREN_END,
58];
59
60const NEGATION: char = '-';
61const PAREN_START: char = '(';
62const PAREN_END: char = ')';
63const ESCAPE: char = '\\';
64const URI_START: &str = "://";
65const URI_POI: char = ':';
66const SPOILER_START: &str = ">!";
67const SPOILER_END: &str = "!<";
68const SPOILER_POI: char = '>';
69const SPOILER_HTML_START: &str = "&gt;!";
70const SPOILER_HTML_END: &str = "!&lt;";
71const SPOILER_HTML_POI: char = '&';
72
73const CONSTANT_STARTS: &[char] = &['p', 'e', 't', 'π', 'ɸ', 'τ'];
74static E: LazyLock<Number> = LazyLock::new(|| {
75    Number::Float(
76        Float::with_val(
77            *FLOAT_PRECISION
78                .get()
79                .expect("FLOAT_PRECISION uninitialized"),
80            1,
81        )
82        .exp()
83        .into(),
84    )
85});
86static PHI: LazyLock<Number> = LazyLock::new(|| {
87    Number::Float(Float::into(
88        ((1.0
89            + Float::with_val(
90                *FLOAT_PRECISION
91                    .get()
92                    .expect("FLOAT_PRECISION uninitialized"),
93                5,
94            )
95            .sqrt()) as Float)
96            / 2.0,
97    ))
98});
99static PI: LazyLock<Number> = LazyLock::new(|| {
100    Number::Float(
101        Float::with_val(
102            *FLOAT_PRECISION
103                .get()
104                .expect("FLOAT_PRECISION uninitialized"),
105            crate::rug::float::Constant::Pi,
106        )
107        .into(),
108    )
109});
110static TAU: LazyLock<Number> = LazyLock::new(|| {
111    Number::Float(Float::into(
112        Float::with_val(
113            *FLOAT_PRECISION
114                .get()
115                .expect("FLOAT_PRECISION uninitialized"),
116            crate::rug::float::Constant::Pi,
117        ) * 2.0,
118    ))
119});
120
121const PREFIX_OPS: [char; 1] = ['!'];
122#[allow(dead_code)]
123const POSTFIX_OPS: [char; 2] = ['!', '?'];
124
125const INTEGER_ONLY_OPS: [i32; 1] = [0];
126
127pub fn parse(mut text: &str, do_termial: bool) -> Vec<CalculationJob> {
128    // Parsing rules:
129    // - prefix has precedence before suffix (unimplemented)
130    // - anything within a spoiler should be ignored
131    // - operations may be nested through parentheses
132    // - operations can be negated through -
133    // - parens may contain:
134    //   - numbers
135    //   - operations
136    //   - parens
137    //   - whitespace
138    // - operations are:
139    //   - subfactorials !n
140    //   - (multi-)factorials n!+
141    //   - termials n?
142    // - numbers are in order:
143    //   - a string of digits
144    //   - a decimal separator and further digits
145    //   - a base 10 exponent, which is:
146    //     - an e or E followed by
147    //     - optionally a + or -
148    //     - a string of digits
149    // - numbers need to at least have the first or second criteria
150
151    // Parsing:
152    // 1. skip to interesting
153    // 2. If spoiler, skip
154    // 3. If negation, save
155    // 4. If paren start, push (with negation)
156    // 5. If paren end, pop
157    //   1. If had prefix, use base, set base
158    //   2. If has postfix, use base, set base
159    // 6. If prefix
160    //   1. If on number, set base
161    //     1. If has postfix, use base, set base
162    //   2. If on paren, push (with negation and level)
163    // 7. If number, parse
164    //   1. If has postfix, set base
165    //   2. If in parens, set as base
166    // 8. If on toplevel, add base to jobs
167    //
168    // when setting base:
169    // 1. If base is set, add previous to jobs
170    // 2. override base
171    let mut jobs = Vec::new();
172    let mut base: Option<CalculationBase> = None;
173    let mut paren_steps: Vec<(u32, Option<i32>, bool)> = Vec::new();
174    let mut current_negative: u32 = 0;
175    let mut last_len = usize::MAX;
176    let mut had_text_before = false;
177    while !text.is_empty() {
178        if last_len == text.len() {
179            panic!("Parser caught in a loop! Text: \"{text}\"")
180        }
181        last_len = text.len();
182
183        text = text.trim_start();
184        if text.len() != last_len {
185            current_negative = 0;
186        }
187        // Text (1.)
188        let Some(position_of_interest) = text.find(POI_STARTS) else {
189            break;
190        };
191        if position_of_interest != 0 {
192            // poison paren
193            if let Some(step) = paren_steps.last_mut() {
194                step.2 = true;
195            }
196            current_negative = 0;
197            had_text_before = false;
198        }
199        let had_text =
200            text[..position_of_interest].ends_with(char::is_alphabetic) || had_text_before;
201        had_text_before = false;
202        // so we can just ignore everything before
203        text = &text[position_of_interest..];
204        if text.starts_with(ESCAPE) {
205            // Escapes
206            text = &text[1..];
207            let end = if text.starts_with(SPOILER_START) {
208                1
209            } else if text.starts_with(SPOILER_HTML_START) {
210                4
211            } else if text.starts_with(URI_START) {
212                3
213            } else {
214                0
215            };
216            text = &text[end..];
217            continue;
218        } else if text.starts_with(URI_START) {
219            // URI
220            let end = text.find(char::is_whitespace).unwrap_or(text.len());
221            text = &text[end..];
222            continue;
223        } else if text.starts_with(SPOILER_START) {
224            // Spoiler (2.)
225            let mut end = 0;
226            loop {
227                // look for next end tag
228                if let Some(e) = text[end..].find(SPOILER_END) {
229                    if e == 0 {
230                        panic!("Parser loop Spoiler! Text \"{text}\"");
231                    }
232                    end += e;
233                    // is escaped -> look further
234                    if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
235                        end += 1;
236                        continue;
237                    }
238                    break;
239                } else {
240                    // if we find none, we skip only the start (without the !)
241                    end = 0;
242                    break;
243                }
244            }
245            current_negative = 0;
246            text = &text[end + 1..];
247            continue;
248        } else if text.starts_with(SPOILER_HTML_START) {
249            // Spoiler (html) (2.)
250            let mut end = 0;
251            loop {
252                // look for next end tag
253                if let Some(e) = text[end..].find(SPOILER_HTML_END) {
254                    if e == 0 {
255                        panic!("Parser loop Spoiler! Text \"{text}\"");
256                    }
257                    end += e;
258                    // is escaped -> look further
259                    if text[end.saturating_sub(1)..].starts_with(ESCAPE) {
260                        end += 1;
261                        continue;
262                    }
263                    break;
264                } else {
265                    // if we find none, we skip only the start (without the !)
266                    end = 0;
267                    break;
268                }
269            }
270            current_negative = 0;
271            text = &text[end + 4..];
272            continue;
273        } else if text.starts_with(NEGATION) {
274            // Negation (3.)
275            let end = text.find(|c| c != NEGATION).unwrap_or(text.len());
276            current_negative = end as u32;
277            text = &text[end..];
278            continue;
279        } else if text.starts_with(PAREN_START) {
280            // Paren Start (without prefix op) (4.)
281            paren_steps.push((current_negative, None, false));
282            // Submit current base (we won't use it anymore)
283            if let Some(CalculationBase::Calc(job)) = base.take() {
284                jobs.push(*job);
285            }
286            current_negative = 0;
287            text = &text[1..];
288            continue;
289        } else if text.starts_with(PAREN_END) {
290            // Paren End (5.)
291            text = &text[1..];
292            current_negative = 0;
293            // Paren mismatch?
294            let Some(step) = paren_steps.pop() else {
295                continue;
296            };
297            // poisoned paren
298            if step.2 {
299                if let Some(CalculationBase::Calc(job)) = base.take() {
300                    jobs.push(*job);
301                }
302                // no number (maybe var) => poison outer paren
303                if let Some(step) = paren_steps.last_mut() {
304                    step.2 = true;
305                }
306                continue;
307            }
308            let mut had_op = false;
309            // Prefix? (5.2.)
310            if let Some(level) = step.1 {
311                // base available?
312                let Some(inner) = base.take() else {
313                    // no number (maybe var) => poison outer paren
314                    if let Some(step) = paren_steps.last_mut() {
315                        step.2 = true;
316                    }
317                    continue;
318                };
319                if let (CalculationBase::Num(Number::Float(_)), true) =
320                    (&inner, INTEGER_ONLY_OPS.contains(&level))
321                {
322                    continue;
323                }
324                base = Some(CalculationBase::Calc(Box::new(CalculationJob {
325                    base: inner,
326                    level,
327                    negative: 0,
328                })));
329                had_op = true;
330            }
331            // Postfix? (5.1.)
332            let Some(levels) = parse_ops(&mut text, false, do_termial) else {
333                base.take();
334                // no number (maybe var) => poison outer paren
335                if let Some(step) = paren_steps.last_mut() {
336                    step.2 = true;
337                }
338                continue;
339            };
340            if !levels.is_empty() {
341                // Set as base (5.1.2.)
342                for level in levels {
343                    // base available?
344                    let Some(inner) = base.take() else {
345                        continue;
346                    };
347                    base = Some(CalculationBase::Calc(Box::new(CalculationJob {
348                        base: inner,
349                        level,
350                        negative: 0,
351                    })));
352                    had_op = true;
353                }
354            }
355            if !had_op {
356                match &mut base {
357                    Some(CalculationBase::Calc(job)) => job.negative += step.0,
358                    Some(CalculationBase::Num(n)) => {
359                        if step.0 % 2 != 0 {
360                            n.negate();
361                        }
362                    }
363                    None => {}
364                }
365            } else {
366                match &mut base {
367                    Some(CalculationBase::Num(n)) => {
368                        if step.0 % 2 == 1 {
369                            n.negate();
370                        }
371                    }
372                    Some(CalculationBase::Calc(job)) => job.negative += step.0,
373                    None => {
374                        // no number (maybe var) => poison outer paren
375                        if let Some(step) = paren_steps.last_mut() {
376                            step.2 = true;
377                        }
378                    }
379                }
380                continue;
381            };
382        } else if text.starts_with(PREFIX_OPS) {
383            // Prefix OP (6.)
384            let Ok(level) = parse_op(&mut text, true, do_termial) else {
385                // also skip number to prevent stuff like "!!!1!" getting through
386                parse_num(&mut text, false, true);
387                continue;
388            };
389            // On number (6.1.)
390            if let Some(num) = parse_num(&mut text, false, true) {
391                // set base (6.1.2.)
392                if let Some(CalculationBase::Calc(job)) = base.take() {
393                    // multiple number, likely expression => poision paren
394                    if let Some(step) = paren_steps.last_mut() {
395                        step.2 = true;
396                    }
397                    jobs.push(*job);
398                }
399                if let (Number::Float(_), true) = (&num, INTEGER_ONLY_OPS.contains(&level)) {
400                    continue;
401                }
402                base = Some(CalculationBase::Calc(Box::new(CalculationJob {
403                    base: CalculationBase::Num(num),
404                    level,
405                    negative: current_negative,
406                })));
407                current_negative = 0;
408                let Some(levels) = parse_ops(&mut text, false, do_termial) else {
409                    continue;
410                };
411                for level in levels {
412                    // base available?
413                    let Some(inner) = base.take() else {
414                        continue;
415                    };
416                    base = Some(CalculationBase::Calc(Box::new(CalculationJob {
417                        base: inner,
418                        level,
419                        negative: 0,
420                    })));
421                }
422            } else {
423                // on paren? (6.2.)
424                if text.starts_with(PAREN_START) {
425                    paren_steps.push((current_negative, Some(level), false));
426                    current_negative = 0;
427                    text = &text[1..];
428                }
429                continue;
430            };
431        } else {
432            // Number (7.)
433            let Some(num) = parse_num(&mut text, had_text, false) else {
434                had_text_before = true;
435                // advance one char to avoid loop
436                let mut end = 1;
437                while !text.is_char_boundary(end) && end < text.len() {
438                    end += 1;
439                }
440                text = &text[end.min(text.len())..];
441                continue;
442            };
443            // postfix? (7.1.)
444            let Some(levels) = parse_ops(&mut text, false, do_termial) else {
445                continue;
446            };
447            if !levels.is_empty() {
448                let levels = levels.into_iter();
449                if let Some(CalculationBase::Calc(job)) = base.take() {
450                    // multiple number, likely expression => poision paren
451                    if let Some(step) = paren_steps.last_mut() {
452                        step.2 = true;
453                    }
454                    jobs.push(*job);
455                }
456                base = Some(CalculationBase::Num(num));
457                for level in levels {
458                    let previous = base.take().unwrap();
459                    if let (CalculationBase::Num(Number::Float(_)), true) =
460                        (&previous, INTEGER_ONLY_OPS.contains(&level))
461                    {
462                        continue;
463                    }
464                    base = Some(CalculationBase::Calc(Box::new(CalculationJob {
465                        base: previous,
466                        level,
467                        negative: 0,
468                    })))
469                }
470                if let Some(CalculationBase::Calc(job)) = &mut base {
471                    job.negative = current_negative;
472                }
473            } else {
474                // in parens? (7.2.)
475                if !paren_steps.is_empty() {
476                    let mut num = num;
477                    if current_negative % 2 == 1 {
478                        num.negate();
479                    }
480
481                    if base.is_none() {
482                        base = Some(CalculationBase::Num(num))
483                    } else {
484                        // multiple number, likely expression => poision paren
485                        if let Some(step) = paren_steps.last_mut() {
486                            step.2 = true;
487                        }
488                    }
489                }
490            }
491            current_negative = 0;
492        };
493        // toplevel? (8.)
494        if paren_steps.is_empty() {
495            if let Some(CalculationBase::Calc(job)) = base.take() {
496                jobs.push(*job);
497            }
498        }
499    }
500    if let Some(CalculationBase::Calc(job)) = base.take() {
501        jobs.push(*job);
502    }
503    jobs.sort();
504    jobs.dedup();
505    jobs
506}
507
508enum ParseOpErr {
509    NonOp,
510    InvalidOp,
511}
512
513fn parse_op(text: &mut &str, prefix: bool, do_termial: bool) -> Result<i32, ParseOpErr> {
514    let op = text.chars().next().ok_or(ParseOpErr::NonOp)?;
515    let end = text.find(|c| c != op).unwrap_or(text.len());
516    let res = match op {
517        '!' => {
518            if prefix {
519                if end != 1 {
520                    Err(ParseOpErr::InvalidOp)
521                } else {
522                    Ok(0)
523                }
524            } else {
525                Ok(end as i32)
526            }
527        }
528        '?' => {
529            if !do_termial {
530                Err(ParseOpErr::NonOp)
531            } else if prefix {
532                Err(ParseOpErr::InvalidOp)
533            } else {
534                Ok(-(end as i32))
535            }
536        }
537        _ => return Err(ParseOpErr::NonOp),
538    };
539    *text = &text[end..];
540    res
541}
542
543fn parse_ops(text: &mut &str, prefix: bool, do_termial: bool) -> Option<Vec<i32>> {
544    let mut res = Vec::new();
545    loop {
546        match parse_op(text, prefix, do_termial) {
547            Ok(op) => res.push(op),
548            Err(ParseOpErr::NonOp) => break,
549            Err(ParseOpErr::InvalidOp) => return None,
550        }
551    }
552    Some(res)
553}
554
555fn parse_num(text: &mut &str, had_text: bool, had_op: bool) -> Option<Number> {
556    let prec = *FLOAT_PRECISION
557        .get()
558        .expect("FLOAT_PRECISION uninitialized");
559    if text.starts_with(CONSTANT_STARTS) {
560        let (n, x) = if text.starts_with("pi") {
561            ("pi".len(), PI.clone())
562        } else if text.starts_with("π") {
563            ("π".len(), PI.clone())
564        } else if text.starts_with("phi") {
565            ("phi".len(), PHI.clone())
566        } else if text.starts_with("ɸ") {
567            ("ɸ".len(), PHI.clone())
568        } else if text.starts_with("tau") {
569            ("tau".len(), TAU.clone())
570        } else if text.starts_with("τ") {
571            ("τ".len(), TAU.clone())
572        } else if text.starts_with("e") {
573            ("e".len(), E.clone())
574        } else {
575            return None;
576        };
577        if had_text || text[n..].starts_with(char::is_alphabetic) {
578            return None;
579        }
580        *text = &text[n..];
581        return Some(x);
582    }
583
584    let integer_part = {
585        let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
586        let part = &text[..end];
587        *text = &text[end..];
588        part
589    };
590    let decimal_part = if text.starts_with(['.', ',']) {
591        *text = &text[1..];
592        let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
593        let part = &text[..end];
594        *text = &text[end..];
595        part
596    } else {
597        &text[..0]
598    };
599    let exponent_part = if text.starts_with(['e', 'E']) {
600        *text = &text[1..];
601        let negative = if text.starts_with('+') {
602            *text = &text[1..];
603            false
604        } else if text.starts_with('-') {
605            *text = &text[1..];
606            true
607        } else {
608            false
609        };
610        let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
611        let part = &text[..end];
612        *text = &text[end..];
613        (part, negative)
614    } else {
615        (&text[..0], false)
616    };
617    let fraction_part = if !had_op && text.starts_with(['/']) {
618        *text = &text[1..];
619        let end = text.find(|c: char| !c.is_numeric()).unwrap_or(text.len());
620        let part = &text[..end];
621        *text = &text[end..];
622        part
623    } else {
624        &text[..0]
625    };
626    if text.starts_with(POSTFIX_OPS) && !fraction_part.is_empty() {
627        let n = fraction_part.parse::<Integer>().ok()?;
628        return Some(Number::Exact(n));
629    }
630    if integer_part.is_empty() && decimal_part.is_empty() {
631        return None;
632    }
633    let exponent = if !exponent_part.0.is_empty() {
634        let mut e = exponent_part.0.parse::<Integer>().ok()?;
635        if exponent_part.1 {
636            e *= -1;
637        }
638        e
639    } else {
640        0.into()
641    };
642    let divisor = if !fraction_part.is_empty() {
643        fraction_part.parse::<Integer>().ok()?
644    } else {
645        Integer::ONE.clone()
646    };
647    if exponent >= decimal_part.len() as i64
648        && exponent
649            <= INTEGER_CONSTRUCTION_LIMIT
650                .get()
651                .expect("Limits uninitialized, use init")
652                .clone()
653                - integer_part.len() as i64
654        && (divisor == 1
655            || exponent
656                >= INTEGER_CONSTRUCTION_LIMIT
657                    .get()
658                    .expect("Limits uninitialized, use init")
659                    .clone()
660                    / 10)
661    {
662        let exponent = exponent - decimal_part.len();
663        let n = format!("{integer_part}{decimal_part}")
664            .parse::<Integer>()
665            .ok()?;
666        let num = (n * Integer::u64_pow_u64(10, exponent.to_u64().unwrap()).complete()) / divisor;
667        Some(Number::Exact(num))
668    } else if exponent
669        <= INTEGER_CONSTRUCTION_LIMIT
670            .get()
671            .expect("Limits uninitialized, use init")
672            .clone()
673            - integer_part.len() as i64
674    {
675        let x = Float::parse(format!(
676            "{integer_part}.{decimal_part}{}{}{}",
677            if !exponent_part.0.is_empty() { "e" } else { "" },
678            if exponent_part.1 { "-" } else { "" },
679            exponent_part.0
680        ))
681        .ok()?;
682        let x = Float::with_val(prec, x) / divisor;
683        if x.is_integer() {
684            let n = x.to_integer().unwrap();
685            Some(Number::Exact(n))
686        } else if x.is_finite() {
687            Some(Number::Float(x.into()))
688        } else {
689            None
690        }
691    } else {
692        let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?;
693        let x = Float::with_val(prec, x) / divisor;
694        if x.is_finite() {
695            let (b, e) = crate::math::adjust_approximate((x, exponent));
696            Some(Number::Approximate(b.into(), e))
697        } else {
698            None
699        }
700    }
701}
702
703#[cfg(test)]
704mod test {
705    use super::*;
706    use crate::calculation_tasks::CalculationBase::Num;
707    use arbtest::arbtest;
708
709    use crate::recommended::FLOAT_PRECISION;
710
711    #[test]
712    fn test_text_only() {
713        let _ = crate::init_default();
714        let jobs = parse("just some words of encouragement!", true);
715        assert_eq!(jobs, []);
716    }
717    #[test]
718    fn test_factorial() {
719        let _ = crate::init_default();
720        let jobs = parse("a factorial 15!", true);
721        assert_eq!(
722            jobs,
723            [CalculationJob {
724                base: CalculationBase::Num(15.into()),
725                level: 1,
726                negative: 0
727            }]
728        );
729    }
730    #[test]
731    fn test_multifactorial() {
732        let _ = crate::init_default();
733        let jobs = parse("a factorial 15!!! actually a multi", true);
734        assert_eq!(
735            jobs,
736            [CalculationJob {
737                base: CalculationBase::Num(15.into()),
738                level: 3,
739                negative: 0
740            }]
741        );
742    }
743    #[test]
744    fn test_subfactorial() {
745        let _ = crate::init_default();
746        let jobs = parse("a factorial !15 actually a sub", true);
747        assert_eq!(
748            jobs,
749            [CalculationJob {
750                base: CalculationBase::Num(15.into()),
751                level: 0,
752                negative: 0
753            }]
754        );
755    }
756    #[test]
757    fn test_submultifactorial() {
758        let _ = crate::init_default();
759        let jobs = parse("not well defined !!!15", true);
760        assert_eq!(jobs, []);
761    }
762    #[test]
763    fn test_termial() {
764        let _ = crate::init_default();
765        let jobs = parse("a termial 15?", true);
766        assert_eq!(
767            jobs,
768            [CalculationJob {
769                base: CalculationBase::Num(15.into()),
770                level: -1,
771                negative: 0
772            }]
773        );
774    }
775    #[test]
776    fn test_no_termial() {
777        let _ = crate::init_default();
778        let jobs = parse("not enabled 15?", false);
779        assert_eq!(jobs, []);
780    }
781    #[test]
782    fn test_multitermial() {
783        let _ = crate::init_default();
784        let jobs = parse("a termial 15??? actually a multi", true);
785        assert_eq!(
786            jobs,
787            [CalculationJob {
788                base: CalculationBase::Num(15.into()),
789                level: -3,
790                negative: 0
791            }]
792        );
793    }
794    #[test]
795    fn test_subtermial() {
796        let _ = crate::init_default();
797        let jobs = parse("a termial ?15 actually a sub", true);
798        assert_eq!(jobs, []);
799    }
800    #[test]
801    fn test_chain() {
802        let _ = crate::init_default();
803        let jobs = parse("a factorialchain (15!)!", true);
804        assert_eq!(
805            jobs,
806            [CalculationJob {
807                base: CalculationBase::Calc(Box::new(CalculationJob {
808                    base: CalculationBase::Num(15.into()),
809                    level: 1,
810                    negative: 0
811                })),
812                level: 1,
813                negative: 0
814            }]
815        );
816    }
817    #[test]
818    fn test_mixed_chain() {
819        let _ = crate::init_default();
820        let jobs = parse("a factorialchain !(15!)", true);
821        assert_eq!(
822            jobs,
823            [CalculationJob {
824                base: CalculationBase::Calc(Box::new(CalculationJob {
825                    base: CalculationBase::Num(15.into()),
826                    level: 1,
827                    negative: 0
828                })),
829                level: 0,
830                negative: 0
831            }]
832        );
833    }
834    #[test]
835    fn test_postfix_chain() {
836        let _ = crate::init_default();
837        let jobs = parse("a factorialchain -15!?", true);
838        assert_eq!(
839            jobs,
840            [CalculationJob {
841                base: CalculationBase::Calc(Box::new(CalculationJob {
842                    base: CalculationBase::Num(15.into()),
843                    level: 1,
844                    negative: 0
845                })),
846                level: -1,
847                negative: 1
848            }]
849        );
850    }
851    #[test]
852    fn test_negative() {
853        let _ = crate::init_default();
854        let jobs = parse("a factorial ---15!", true);
855        assert_eq!(
856            jobs,
857            [CalculationJob {
858                base: CalculationBase::Num(15.into()),
859                level: 1,
860                negative: 3
861            }]
862        );
863    }
864    #[test]
865    fn test_negative_gap() {
866        let _ = crate::init_default();
867        let jobs = parse("a factorial --- 15!", true);
868        assert_eq!(
869            jobs,
870            [CalculationJob {
871                base: CalculationBase::Num(15.into()),
872                level: 1,
873                negative: 0
874            }]
875        );
876    }
877    #[test]
878    fn test_paren() {
879        let _ = crate::init_default();
880        let jobs = parse("a factorial (15)!", true);
881        assert_eq!(
882            jobs,
883            [CalculationJob {
884                base: CalculationBase::Num(15.into()),
885                level: 1,
886                negative: 0
887            }]
888        );
889    }
890    #[test]
891    fn test_in_paren() {
892        let _ = crate::init_default();
893        let jobs = parse("a factorial (15!)", true);
894        assert_eq!(
895            jobs,
896            [CalculationJob {
897                base: CalculationBase::Num(15.into()),
898                level: 1,
899                negative: 0
900            }]
901        );
902    }
903    #[test]
904    fn test_decimal() {
905        let _ = crate::init_default();
906        let jobs = parse("a factorial 1.5!", true);
907        assert_eq!(
908            jobs,
909            [CalculationJob {
910                base: CalculationBase::Num(Float::with_val(FLOAT_PRECISION, 1.5).into()),
911                level: 1,
912                negative: 0
913            }]
914        );
915    }
916    #[test]
917    fn test_paren_negation() {
918        let _ = crate::init_default();
919        let jobs = parse("a factorial -(--(-(-(-3))!))!", true);
920        assert_eq!(
921            jobs,
922            [CalculationJob {
923                base: CalculationBase::Calc(Box::new(CalculationJob {
924                    base: CalculationBase::Num(3.into()),
925                    level: 1,
926                    negative: 3
927                })),
928                level: 1,
929                negative: 1
930            }]
931        );
932    }
933    #[test]
934    fn test_tag() {
935        let _ = crate::init_default();
936        let jobs = parse(">!5 a factorial 15! !<", true);
937        assert_eq!(jobs, []);
938    }
939    #[test]
940    fn test_incomplete_tag() {
941        let _ = crate::init_default();
942        let jobs = parse(">!5 a factorial 15!", true);
943        assert_eq!(
944            jobs,
945            [
946                CalculationJob {
947                    base: CalculationBase::Num(5.into()),
948                    level: 0,
949                    negative: 0
950                },
951                CalculationJob {
952                    base: CalculationBase::Num(15.into()),
953                    level: 1,
954                    negative: 0
955                }
956            ]
957        );
958    }
959    #[test]
960    fn test_escaped_tag() {
961        let _ = crate::init_default();
962        let jobs = parse("\\>!5 a factorial 15! !<", true);
963        assert_eq!(
964            jobs,
965            [
966                CalculationJob {
967                    base: CalculationBase::Num(5.into()),
968                    level: 0,
969                    negative: 0
970                },
971                CalculationJob {
972                    base: CalculationBase::Num(15.into()),
973                    level: 1,
974                    negative: 0
975                }
976            ]
977        );
978    }
979    #[test]
980    fn test_escaped_tag2() {
981        let _ = crate::init_default();
982        let jobs = parse(">!5 a factorial 15! \\!<", true);
983        assert_eq!(
984            jobs,
985            [
986                CalculationJob {
987                    base: CalculationBase::Num(5.into()),
988                    level: 0,
989                    negative: 0
990                },
991                CalculationJob {
992                    base: CalculationBase::Num(15.into()),
993                    level: 1,
994                    negative: 0
995                }
996            ]
997        );
998    }
999
1000    #[test]
1001    fn test_url() {
1002        let _ = crate::init_default();
1003        let jobs = parse(
1004            "https://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1005            true,
1006        );
1007        assert_eq!(jobs, []);
1008    }
1009
1010    #[test]
1011    fn test_uri_poi_doesnt_cause_infinite_loop() {
1012        let _ = crate::init_default();
1013        let jobs = parse("84!:", true);
1014        assert_eq!(
1015            jobs,
1016            [CalculationJob {
1017                base: Num(84.into()),
1018                level: 1,
1019                negative: 0
1020            }]
1021        );
1022    }
1023    #[test]
1024    fn test_escaped_url() {
1025        let _ = crate::init_default();
1026        let jobs = parse(
1027            "\\://something.somewhere/with/path/and?tag=siufgiufgia3873844hi8743!hfsf",
1028            true,
1029        );
1030        assert_eq!(
1031            jobs,
1032            [CalculationJob {
1033                base: CalculationBase::Num(8743.into()),
1034                level: 1,
1035                negative: 0
1036            }]
1037        );
1038    }
1039
1040    #[test]
1041    fn test_word_in_paren() {
1042        let _ = crate::init_default();
1043        let jobs = parse("(x-2)! (2 word)! ((x/k)-3)! (,x-4)!", true);
1044        assert_eq!(jobs, []);
1045    }
1046
1047    #[test]
1048    fn test_multi_number_paren() {
1049        let _ = crate::init_default();
1050        let jobs = parse("(5-2)!", true);
1051        assert_eq!(jobs, []);
1052    }
1053    #[test]
1054    fn test_arbitrary_input() {
1055        let _ = crate::init_default();
1056        arbtest(|u| {
1057            let text: &str = u.arbitrary()?;
1058            let _ = parse(text, u.arbitrary()?);
1059            Ok(())
1060        });
1061    }
1062
1063    #[test]
1064    fn test_constant() {
1065        let _ = crate::init_default();
1066        let jobs = parse("!espi!", true);
1067        assert_eq!(jobs, []);
1068    }
1069
1070    #[test]
1071    fn test_fraction() {
1072        let _ = crate::init_default();
1073        let jobs = parse("!5/6!", true);
1074        assert_eq!(
1075            jobs,
1076            [
1077                CalculationJob {
1078                    base: CalculationBase::Num(Number::Exact(5.into())),
1079                    level: 0,
1080                    negative: 0
1081                },
1082                CalculationJob {
1083                    base: CalculationBase::Num(Number::Exact(6.into())),
1084                    level: 1,
1085                    negative: 0
1086                }
1087            ]
1088        );
1089        let jobs = parse("5/6!", true);
1090        assert_eq!(
1091            jobs,
1092            [CalculationJob {
1093                base: CalculationBase::Num(Number::Exact(6.into())),
1094                level: 1,
1095                negative: 0
1096            }]
1097        );
1098        let jobs = parse("(10/2)!", true);
1099        assert_eq!(
1100            jobs,
1101            [CalculationJob {
1102                base: CalculationBase::Num(Number::Exact(5.into())),
1103                level: 1,
1104                negative: 0
1105            },]
1106        );
1107    }
1108
1109    #[test]
1110    fn test_parse_num() {
1111        let _ = crate::init_default();
1112        let num = parse_num(&mut "1.5more !", false, false);
1113        assert_eq!(
1114            num,
1115            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1116        );
1117        let num = parse_num(&mut "1,5more !", false, false);
1118        assert_eq!(
1119            num,
1120            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into()))
1121        );
1122        let num = parse_num(&mut ".5more !", false, false);
1123        assert_eq!(
1124            num,
1125            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1126        );
1127        let num = parse_num(&mut "1more !", false, true);
1128        assert_eq!(num, Some(1.into()));
1129        let num = parse_num(&mut "1.0more !", true, false);
1130        assert_eq!(num, Some(1.into()));
1131        let num = parse_num(&mut "1.5e2more !", false, false);
1132        assert_eq!(num, Some(150.into()));
1133        let num = parse_num(&mut "1e2more !", false, false);
1134        assert_eq!(num, Some(100.into()));
1135        let num = parse_num(&mut "1.531e2more !", false, false);
1136        let Some(Number::Float(f)) = num else {
1137            panic!("Not a float")
1138        };
1139        assert!(Float::abs(f.as_float().clone() - 153.1) < 0.0000001);
1140        let num = parse_num(&mut "5e-1more !", false, false);
1141        assert_eq!(
1142            num,
1143            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1144        );
1145        let num = parse_num(&mut "e2more !", true, false);
1146        assert_eq!(num, None);
1147        let num = parse_num(&mut "es !", false, false);
1148        assert_eq!(num, None);
1149        let num = parse_num(&mut "e !", false, false);
1150        assert_eq!(num, Some(E.clone()));
1151        let num = parse_num(&mut "pi !", false, false);
1152        assert_eq!(num, Some(PI.clone()));
1153        let num = parse_num(&mut "π !", false, false);
1154        assert_eq!(num, Some(PI.clone()));
1155        let num = parse_num(&mut "phi !", false, false);
1156        assert_eq!(num, Some(PHI.clone()));
1157        let num = parse_num(&mut "ɸ !", false, false);
1158        assert_eq!(num, Some(PHI.clone()));
1159        let num = parse_num(&mut "tau !", false, false);
1160        assert_eq!(num, Some(TAU.clone()));
1161        let num = parse_num(&mut "τ !", false, false);
1162        assert_eq!(num, Some(TAU.clone()));
1163        let num = parse_num(&mut "1/2 !", false, false);
1164        assert_eq!(
1165            num,
1166            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.5).into()))
1167        );
1168        let num = parse_num(&mut "10/2 !", false, false);
1169        assert_eq!(num, Some(Number::Exact(5.into())));
1170        let num = parse_num(&mut "1.5/2 !", false, false);
1171        assert_eq!(
1172            num,
1173            Some(Number::Float(Float::with_val(FLOAT_PRECISION, 0.75).into()))
1174        );
1175        let num = parse_num(&mut "10e10000000000/2 !", false, false);
1176        assert_eq!(
1177            num,
1178            Some(Number::Approximate(
1179                Float::with_val(FLOAT_PRECISION, 5).into(),
1180                10000000000u64.into()
1181            ))
1182        );
1183        let num = parse_num(&mut "10/2 !", false, true);
1184        assert_eq!(num, Some(Number::Exact(10.into())));
1185        let num = parse_num(&mut "10/2!", false, false);
1186        assert_eq!(num, Some(Number::Exact(2.into())));
1187    }
1188    #[allow(clippy::uninlined_format_args)]
1189    #[test]
1190    fn test_biggest_num() {
1191        let _ = crate::init_default();
1192        let num = parse_num(
1193            &mut format!("9e{}", INTEGER_CONSTRUCTION_LIMIT.get().unwrap()).as_str(),
1194            true,
1195            false,
1196        );
1197        assert!(matches!(num, Some(Number::Approximate(_, _))));
1198        let num = parse_num(
1199            &mut format!(
1200                "9e{}",
1201                INTEGER_CONSTRUCTION_LIMIT.get().unwrap().clone() - 1
1202            )
1203            .as_str(),
1204            false,
1205            false,
1206        );
1207        assert!(num.is_some());
1208    }
1209}