use crate::Float;
use crate::float::conversion::string::set_str::{overflow, set_str_helper};
use alloc::vec::Vec;
use core::cmp::Ordering::{self, *};
use malachite_base::num::basic::traits::{Infinity, NaN, NegativeInfinity, NegativeZero, Zero};
use malachite_base::rounding_modes::RoundingMode;
const MAX_BASE: u8 = 62;
const fn is_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
const fn digit_value_in_base(c: u8, base: u8) -> Option<u8> {
let digit = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'z' => {
if base >= 37 {
c - b'a' + 36
} else {
c - b'a' + 10
}
}
b'A'..=b'Z' => c - b'A' + 10,
_ => return None,
};
if digit < base { Some(digit) } else { None }
}
fn starts_with_ignore_case(s: &[u8], prefix: &[u8]) -> bool {
let prefix_len = prefix.len();
s.len() >= prefix_len
&& s[..prefix_len]
.iter()
.zip(prefix)
.all(|(&c, &p)| c.to_ascii_lowercase() == p)
}
fn read_exponent(s: &[u8]) -> (i64, usize) {
let mut i = 0;
let negative = s.first() == Some(&b'-');
if negative || s.first() == Some(&b'+') {
i = 1;
}
let start = i;
let mut exp = 0i64;
while let Some(&c) = s.get(i)
&& c.is_ascii_digit()
{
exp = exp
.saturating_mul(10)
.saturating_add(i64::from(c - b'0') * if negative { -1 } else { 1 });
i += 1;
}
if i == start { (0, 0) } else { (exp, i) }
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ParsedString {
Invalid,
NaN,
Infinity(bool),
Zero(bool),
Finite(bool, u8, Vec<u8>, i64, i64),
Overflow(bool),
}
fn parse_string(s: &[u8], mut base: u8) -> (ParsedString, usize) {
let at = |i: usize| s.get(i).copied().unwrap_or(0);
let mut i = 0;
while at(i) != 0 && is_space(at(i)) {
i += 1;
}
let sign = at(i) != b'-';
if at(i) == b'-' || at(i) == b'+' {
i += 1;
}
let nan = if starts_with_ignore_case(&s[i..], b"@nan@") {
i += 5;
true
} else if base <= 16 && starts_with_ignore_case(&s[i..], b"nan") {
i += 3;
true
} else {
false
};
if nan {
if at(i) == b'(' {
let mut j = i + 1;
while at(j) != b')' {
if !at(j).is_ascii_alphanumeric() && at(j) != b'_' {
break;
}
j += 1;
}
if at(j) == b')' {
i = j + 1;
}
}
return (ParsedString::NaN, i);
}
let s_tail = &s[i..];
if starts_with_ignore_case(s_tail, b"@inf@") {
return (ParsedString::Infinity(sign), i + 5);
} else if base <= 16 {
if starts_with_ignore_case(s_tail, b"infinity") {
return (ParsedString::Infinity(sign), i + 8);
} else if starts_with_ignore_case(s_tail, b"inf") {
return (ParsedString::Infinity(sign), i + 3);
}
}
let mut prefix_index = None;
if (base == 0 || base == 16) && at(i) == b'0' && (at(i + 1) | 0x20) == b'x' {
prefix_index = Some(i);
base = 16;
i += 2;
}
if (base == 0 || base == 2) && at(i) == b'0' && (at(i + 1) | 0x20) == b'b' {
prefix_index = Some(i);
base = 2;
i += 2;
}
if base == 0 {
base = 10;
}
let mut digits;
let mut exp_base;
let mut start = i;
loop {
digits = Vec::new();
let mut point = false;
exp_base = 0i64;
i = start;
loop {
let c = at(i);
i += 1;
if c == b'.' {
if point {
break;
}
point = true;
continue;
}
let Some(d) = digit_value_in_base(c, base) else {
break;
};
digits.push(d);
if !point {
exp_base += 1;
}
}
i -= 1;
if !digits.is_empty() {
break;
}
let Some(p) = prefix_index else {
return (ParsedString::Invalid, 0);
};
start = p;
prefix_index = None;
}
let mut exp_bin = 0i64;
let mut overflow = false;
let c = at(i);
if (c == b'@' || (base <= 10 && (c | 0x20) == b'e')) && !is_space(at(i + 1)) {
let (read_exp, len) = read_exponent(&s[i + 1..]);
if len != 0 {
i += 1 + len;
}
match read_exp.checked_add(exp_base) {
Some(sum) => exp_base = sum,
None => overflow = true,
}
} else if (base == 2 || base == 16) && (c | 0x20) == b'p' && !is_space(at(i + 1)) {
let (read_exp, len) = read_exponent(&s[i + 1..]);
if len != 0 {
i += 1 + len;
}
exp_bin = read_exp;
}
let mut leading = 0;
while leading < digits.len() && digits[leading] == 0 {
leading += 1;
exp_base = exp_base.saturating_sub(1);
}
digits.drain(..leading);
while digits.last() == Some(&0) {
digits.pop();
}
if digits.is_empty() {
return (ParsedString::Zero(sign), i);
}
if overflow {
return (ParsedString::Overflow(sign), i);
}
(
ParsedString::Finite(sign, base, digits, exp_base, exp_bin),
i,
)
}
pub fn strtofr(s: &str, base: u8, prec: u64, rm: RoundingMode) -> (Float, Ordering, usize) {
assert!(base == 0 || (2..=MAX_BASE).contains(&base));
assert_ne!(prec, 0);
match parse_string(s.as_bytes(), base) {
(ParsedString::Invalid, _) => (Float::ZERO, Equal, 0),
(ParsedString::NaN, len) => (Float::NAN, Equal, len),
(ParsedString::Infinity(sign), len) => (
if sign {
Float::INFINITY
} else {
Float::NEGATIVE_INFINITY
},
Equal,
len,
),
(ParsedString::Zero(sign), len) => (
if sign {
Float::ZERO
} else {
Float::NEGATIVE_ZERO
},
Equal,
len,
),
(ParsedString::Overflow(sign), len) => {
let (x, o) = overflow(sign, prec, rm);
(x, o, len)
}
(ParsedString::Finite(sign, base, digits, exp_base, exp_bin), len) => {
let (x, o) = set_str_helper(sign, &digits, base, exp_base, exp_bin, prec, rm);
(x, o, len)
}
}
}
pub fn set_str(s: &str, base: u8, prec: u64, rm: RoundingMode) -> Option<(Float, Ordering)> {
if s.is_empty() {
return None;
}
let (x, o, len) = strtofr(s, base, prec, rm);
if len == s.len() { Some((x, o)) } else { None }
}