use super::ResolveError;
use crate::value::{Number, Radix};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct Ext {
pub(crate) booleans: bool,
pub(crate) legacy_octal: bool,
pub(crate) sexagesimal: bool,
pub(crate) separators: bool,
pub(crate) int_forms: bool,
}
struct Cur<'a> {
b: &'a [u8],
i: usize,
}
impl<'a> Cur<'a> {
fn new(s: &'a str) -> Self {
Self {
b: s.as_bytes(),
i: 0,
}
}
fn peek(&self) -> Option<u8> {
self.b.get(self.i).copied()
}
fn bump(&mut self) {
self.i = self.i.saturating_add(1);
}
fn eat(&mut self, c: u8) -> bool {
if self.peek() == Some(c) {
self.bump();
true
} else {
false
}
}
fn eat_any(&mut self, set: &[u8]) -> bool {
match self.peek() {
Some(c) if set.contains(&c) => {
self.bump();
true
}
_ => false,
}
}
fn run(&mut self, digit: fn(u8) -> bool, sep: bool) -> usize {
let mut n = 0usize;
while let Some(c) = self.peek() {
if digit(c) {
n = n.saturating_add(1);
} else if !(sep && c == b'_') {
break;
}
self.bump();
}
n
}
fn lead_run(&mut self, digit: fn(u8) -> bool, sep: bool) -> bool {
match self.peek() {
Some(c) if digit(c) => {
self.bump();
self.run(digit, sep);
true
}
_ => false,
}
}
fn done(&self) -> bool {
self.i >= self.b.len()
}
}
fn dec(c: u8) -> bool {
c.is_ascii_digit()
}
fn oct(c: u8) -> bool {
matches!(c, b'0'..=b'7')
}
fn hex(c: u8) -> bool {
c.is_ascii_hexdigit()
}
fn bin(c: u8) -> bool {
matches!(c, b'0' | b'1')
}
fn sign(s: &str) -> (bool, bool, &str) {
if let Some(r) = s.strip_prefix('-') {
(true, true, r)
} else if let Some(r) = s.strip_prefix('+') {
(false, true, r)
} else {
(false, false, s)
}
}
fn all_digits(s: &str, digit: fn(u8) -> bool, sep: bool) -> bool {
let mut c = Cur::new(s);
c.lead_run(digit, sep) && c.done()
}
fn any_digits(s: &str, digit: fn(u8) -> bool, sep: bool) -> bool {
let mut c = Cur::new(s);
c.run(digit, sep) > 0 && c.done()
}
pub(crate) fn core_null(s: &str) -> bool {
matches!(s, "" | "~" | "null" | "Null" | "NULL")
}
pub(crate) fn json_null(s: &str) -> bool {
s == "null"
}
pub(crate) fn core_bool(s: &str) -> Option<bool> {
match s {
"true" | "True" | "TRUE" => Some(true),
"false" | "False" | "FALSE" => Some(false),
_ => None,
}
}
pub(crate) fn json_bool(s: &str) -> Option<bool> {
match s {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}
pub(crate) fn yaml11_bool(s: &str) -> Option<bool> {
yaml11_only_bool(s).or_else(|| core_bool(s))
}
pub(crate) fn yaml11_only_bool(s: &str) -> Option<bool> {
match s {
"y" | "Y" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" => Some(true),
"n" | "N" | "no" | "No" | "NO" | "off" | "Off" | "OFF" => Some(false),
_ => None,
}
}
pub(crate) fn ext_bool(s: &str, ext: Ext) -> Option<bool> {
if ext.booleans {
yaml11_bool(s)
} else {
core_bool(s)
}
}
fn radix_int(neg: bool, radix: Radix, digits: &str) -> Number {
let base: u32 = match radix {
Radix::Oct => 8,
Radix::Hex => 16,
Radix::Dec | Radix::Sexagesimal => 10,
};
let mut mag: Option<u128> = Some(0);
for c in digits.chars() {
let Some(d) = c.to_digit(base) else { continue };
mag = mag
.and_then(|m| m.checked_mul(u128::from(base)))
.and_then(|m| m.checked_add(u128::from(d)));
if mag.is_none() {
break;
}
}
match mag {
Some(m) => Number::from_parts(neg, m),
None => {
let clean: String = digits
.chars()
.filter(|c| *c != '_')
.map(|c| c.to_ascii_lowercase())
.skip_while(|c| *c == '0')
.collect();
Number::big(neg, radix, &clean)
}
}
}
fn sexagesimal_int_syntax(s: &str, sep: bool) -> bool {
let mut c = Cur::new(s);
if !matches!(c.peek(), Some(b'1'..=b'9')) {
return false;
}
c.lead_run(dec, sep);
let mut groups = 0usize;
while c.eat(b':') {
if !base60_group(&mut c) {
return false;
}
groups = groups.saturating_add(1);
}
groups > 0 && c.done()
}
fn base60_group(c: &mut Cur<'_>) -> bool {
if c.eat_any(b"012345") {
c.eat_any(b"0123456789");
true
} else {
c.eat_any(b"6789")
}
}
fn sexagesimal_int(neg: bool, s: &str) -> Number {
let first = s.split(':').next().unwrap_or_default();
let mut mag = radix_int(false, Radix::Dec, first).as_u128();
for g in s.split(':').skip(1) {
let d = radix_int(false, Radix::Dec, g).as_u128();
mag = mag
.zip(d)
.and_then(|(m, d)| m.checked_mul(60)?.checked_add(d));
}
match mag {
Some(m) => Number::from_parts(neg, m),
None => {
let mut text: String = first.chars().filter(|c| *c != '_').collect();
for g in s.split(':').skip(1) {
text.push(':');
if g.len() < 2 {
text.push('0');
}
text.push_str(g);
}
Number::big(neg, Radix::Sexagesimal, &text)
}
}
}
fn binary_int(neg: bool, digits: &str) -> Number {
let clean: Vec<u8> = digits
.bytes()
.filter(|c| *c != b'_')
.skip_while(|c| *c == b'0')
.collect();
let mut mag: Option<u128> = Some(0);
for c in &clean {
mag = mag
.and_then(|m| m.checked_mul(2))
.and_then(|m| m.checked_add(u128::from(*c == b'1')));
}
if let Some(m) = mag {
return Number::from_parts(neg, m);
}
let pad = clean.len().wrapping_neg() % 4;
let bits: Vec<u8> = std::iter::repeat_n(b'0', pad).chain(clean).collect();
let hex: String = bits
.chunks(4)
.map(|g| {
let v = g
.iter()
.fold(0u32, |a, b| a.wrapping_shl(1) | u32::from(*b == b'1'));
char::from_digit(v, 16).unwrap_or('0')
})
.skip_while(|c| *c == '0')
.collect();
Number::big(neg, Radix::Hex, &hex)
}
fn int_form(neg: bool, signed: bool, rest: &str, sep: bool) -> Option<Number> {
if let Some(d) = rest.strip_prefix("0b") {
return any_digits_core(d, bin, sep).then(|| binary_int(neg, d));
}
if !signed {
return None;
}
if let Some(d) = rest.strip_prefix("0x") {
return any_digits_core(d, hex, sep).then(|| radix_int(neg, Radix::Hex, d));
}
None
}
pub(crate) fn core_int(s: &str, ext: Ext) -> Option<Number> {
let sep = ext.separators;
if let Some(d) = s.strip_prefix("0o") {
return any_digits_core(d, oct, sep).then(|| radix_int(false, Radix::Oct, d));
}
if let Some(d) = s.strip_prefix("0x") {
return any_digits_core(d, hex, sep).then(|| radix_int(false, Radix::Hex, d));
}
let (neg, signed, rest) = sign(s);
if ext.int_forms {
if let Some(n) = int_form(neg, signed, rest, sep) {
return Some(n);
}
}
if ext.legacy_octal {
if let Some(d) = rest.strip_prefix('0') {
if any_digits(d, oct, sep) || (sep && !d.is_empty() && d.bytes().all(|c| c == b'_')) {
return Some(radix_int(neg, Radix::Oct, d));
}
}
}
if ext.sexagesimal && sexagesimal_int_syntax(rest, sep) {
return Some(sexagesimal_int(neg, rest));
}
all_digits(rest, dec, sep).then(|| radix_int(neg, Radix::Dec, rest))
}
fn any_digits_core(d: &str, digit: fn(u8) -> bool, sep: bool) -> bool {
if sep {
any_digits(d, digit, true)
} else {
all_digits(d, digit, false)
}
}
pub(crate) fn json_int(s: &str) -> Option<Number> {
let neg = s.starts_with('-');
let rest = s.strip_prefix('-').unwrap_or(s);
let mut c = Cur::new(rest);
json_int_part(&mut c);
(c.i > 0 && c.done()).then(|| radix_int(neg, Radix::Dec, rest))
}
fn json_int_part(c: &mut Cur<'_>) -> bool {
if c.eat(b'0') {
true
} else if matches!(c.peek(), Some(b'1'..=b'9')) {
c.run(dec, false);
true
} else {
false
}
}
fn parse_float(neg: bool, body: &str) -> Result<f64, ResolveError> {
let clean: String = body.chars().filter(|c| *c != '_').collect();
let v = clean.parse::<f64>().ok();
debug_assert!(v.is_some(), "validated float text failed to parse: {clean}");
let v = v.ok_or(ResolveError::Internal)?;
Ok(if neg { -v } else { v })
}
fn exponent(c: &mut Cur<'_>) -> bool {
if c.eat_any(b"eE") {
c.eat_any(b"+-");
c.run(dec, false) > 0
} else {
true
}
}
fn core_float_syntax(s: &str, sep: bool) -> bool {
let mut c = Cur::new(s);
if c.eat(b'.') {
if !c.lead_run(dec, sep) {
return false;
}
} else {
if !c.lead_run(dec, sep) {
return false;
}
if c.eat(b'.') {
c.run(dec, sep);
}
}
exponent(&mut c) && c.done()
}
fn sexagesimal_float_syntax(s: &str, sep: bool) -> bool {
let mut c = Cur::new(s);
if !c.lead_run(dec, sep) {
return false;
}
let mut groups = 0usize;
while c.eat(b':') {
if !base60_group(&mut c) {
return false;
}
groups = groups.saturating_add(1);
}
if groups == 0 || !c.eat(b'.') {
return false;
}
c.run(dec, sep);
c.done()
}
fn sexagesimal_float(neg: bool, s: &str) -> Result<f64, ResolveError> {
let (whole, frac) = s.split_once('.').unwrap_or((s, ""));
let mut groups = whole.split(':');
let mut acc = parse_float(false, groups.next().unwrap_or_default())?;
for g in groups {
acc = acc.mul_add(60.0, parse_float(false, g)?);
}
let frac: String = frac.chars().filter(|c| *c != '_').collect();
let frac = if frac.is_empty() {
0.0
} else {
parse_float(false, &format!("0.{frac}"))?
};
let v = acc + frac;
Ok(if neg { -v } else { v })
}
pub(crate) fn core_float(s: &str, ext: Ext) -> Option<Result<f64, ResolveError>> {
if matches!(s, ".nan" | ".NaN" | ".NAN") {
return Some(Ok(f64::NAN));
}
let (neg, _, rest) = sign(s);
if matches!(rest, ".inf" | ".Inf" | ".INF") {
return Some(Ok(if neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
}));
}
if core_float_syntax(rest, ext.separators) {
return Some(parse_float(neg, rest));
}
if ext.sexagesimal && sexagesimal_float_syntax(rest, ext.separators) {
return Some(sexagesimal_float(neg, rest));
}
None
}
pub(crate) fn json_float(s: &str) -> Option<Result<f64, ResolveError>> {
let neg = s.starts_with('-');
let rest = s.strip_prefix('-').unwrap_or(s);
let mut c = Cur::new(rest);
if !json_int_part(&mut c) {
return None;
}
if c.eat(b'.') {
c.run(dec, false);
}
if !(exponent(&mut c) && c.done()) {
return None;
}
Some(parse_float(neg, rest))
}