simple_config 0.134.0

A config language for humans that is not self-describing.
Documentation
pub(crate) struct Number {
	pub positive: bool,
	pub numerator: u128,
	pub base2_exponent: i16,
	pub base10_exponent: i16,
}

impl Number {
	fn to_int(&self, source: &str) -> Result<u128, crate::DeserializeErrorKind> {
		let mut n = self.numerator;

		if self.base2_exponent < 0 {
			n = n.checked_shr(-self.base2_exponent as u32)
				.ok_or_else(|| crate::DeserializeErrorKind::IntFraction(source.into()))?;
		} else if self.base2_exponent > 0 {
			n = n.checked_shl(self.base2_exponent as u32)
				.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(source.into()))?;
		}

		if self.base10_exponent < 0 {
			let pow = 10u128.checked_pow((-self.base10_exponent) as u32)
				.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(source.into()))?;
			if n % pow != 0 {
				return Err(crate::DeserializeErrorKind::IntFraction(source.into()));
			}
			n /= pow;
		} else if self.base10_exponent > 0 {
			let pow = 10u128.checked_pow(self.base10_exponent as u32)
				.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(source.into()))?;
			n = n.checked_mul(pow)
				.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(source.into()))?;
		}

		Ok(n)
	}

	pub fn to_sint<
		T: std::convert::TryFrom<i128> + std::ops::Neg
	>(
		&self,
		source: &str,
	) -> Result<T, crate::DeserializeErrorKind>
	where
		<T as std::convert::TryFrom<i128>>::Error: std::fmt::Debug,
		<T as std::ops::Neg>::Output: std::convert::Into<T>,
	{
		let mut i;
		if !self.positive && self.numerator == (i128::max_value() as u128) + 1 {
			i = i128::min_value();
		} else {
			i = self.to_uint(source)?;
			if !self.positive {
				i = (-i).into();
			}
		}

		return T::try_from(i)
			.map_err(|_| crate::DeserializeErrorKind::IntOverflow(source.into()))
	}

	pub fn to_uint<T: std::convert::TryFrom<u128>>(&self, source: &str)
		-> Result<T, crate::DeserializeErrorKind>
	where
		<T as std::convert::TryFrom<u128>>::Error: std::fmt::Debug,
	{
		T::try_from(self.to_int(source)?)
			.map_err(|_| crate::DeserializeErrorKind::IntOverflow(source.into()))
	}

	pub fn to_f64(&self) -> f64 {
		let mut n = self.numerator as f64;
		if !self.positive {
			n = -n;
		}
		n *= 2f64.powi(self.base2_exponent.into());
		n *= 10f64.powi(self.base10_exponent.into());
		n
	}
}

pub(crate) fn parse_num(mut s: &str, location: crate::Location) -> Result<Number, crate::de::DeserializeError> {
	let positive = if s.starts_with('-') {
		s = &s[1..];
		false
	} else {
		true
	};

	if s.is_empty() {
		return Err(crate::de::DeserializeErrorKind::Invalid(
			"Can't parse \"\" as a number.".into())
			.at(location))
	}

	let mut bytes = s.as_bytes();

	let (base, consume) = match (bytes[0], bytes.get(1)) {
		(b'0', Some(b'b')) => (2, 2),
		(b'0', Some(b'd')) => (10, 2),
		(b'0', Some(b'o')) => (8, 2),
		(b'0', Some(b'x')) => (16, 2),
		(b'0', _) => (10, 1),
		_ => (10, 0),
	};

	bytes = &bytes[consume..];

	let (mut n, len) = parse_num_base(base, bytes)
		.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
	bytes = &bytes[len as usize..];

	let mut base2_exponent = 0i16;
	let mut base10_exponent = 0i16;

	if bytes.get(0) == Some(&b'.') {
		let (d, d_len) = parse_num_base(base, &bytes[1..])
			.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
		n = (base as u128).checked_pow(d_len as u32)
			.and_then(|e| e.checked_mul(n))
			.and_then(|n| n.checked_add(d))
			.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;

		match base {
			2 => {
				base2_exponent = base2_exponent.checked_sub(d_len)
					.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
			}
			8 => {
				base2_exponent = d_len.checked_mul(3)
					.and_then(|e| base2_exponent.checked_sub(e))
					.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
			}
			10 => {
				base10_exponent = base10_exponent.checked_sub(d_len)
					.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
			}
			16 => {
				base2_exponent = d_len.checked_mul(4)
					.and_then(|e| base2_exponent.checked_sub(e))
					.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
			}
			_ => unreachable!(),
		}
		bytes = &bytes[1 + d_len as usize..];
	}

	let (b2, b10) = parse_num_exponent(base, bytes, location)?;
	base2_exponent = base2_exponent.checked_add(b2)
		.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;
	base10_exponent = base10_exponent.checked_add(b10)
		.ok_or_else(|| crate::DeserializeErrorKind::IntOverflow(s.into()).at(location))?;

	Ok(Number {
		positive,
		numerator: n,
		base2_exponent,
		base10_exponent,
	})
}

fn parse_num_base(base: u8, bytes: &[u8]) -> Option<(u128, i16)> {
	let mut n = 0u128;
	let mut len = 0i16;

	while let Some(c) = bytes.get(len as usize) {
		match c {
			b'_' => {}
			b'0'..=b'9' |
			b'a'..=b'f' |
			b'A'..=b'F' => {
				if let Some(d) = (*c as char).to_digit(base.into()) {
					n = n.checked_mul(base.into())?
						.checked_add(d.into())?;
				} else {
					break
				}
			}
			_ => break,
		}
		len += 1;
	}

	Some((n, len))
}

fn parse_num_exponent(base: u8, bytes: &[u8], mut location: crate::Location) -> Result<(i16, i16), crate::de::DeserializeError> {
	let s = unsafe { crate::from_utf8_unchecked(bytes) };

	let mut bindec = move |s: &str, bin, dec| {
		match s.len() {
			0 => Ok((0, dec)),
			1 if bytes[1] == b'i' => Ok((bin, 0)),
			_ => {
				location.add_columns(2);
				Err(crate::de::DeserializeErrorKind::NumTrailing(s.into()).at(location))
			}
		}
	};

	match s.chars().next() {
		Some('E') => bindec(&s[1..], 60, 18),
		Some('P') => bindec(&s[1..], 50, 15),
		Some('T') => bindec(&s[1..], 40, 12),
		Some('G') => bindec(&s[1..], 30, 9),
		Some('M') => bindec(&s[1..], 20, 6),
		Some('K') => bindec(&s[1..], 10, 3), // Accept this as kilo is the only >1 lower case.
		Some('k') => bindec(&s[1..], 10, 3),
		Some('m') => bindec(&s[1..], 10, 3),
		Some('u') => bindec(&s[1..], 20, 6),
		Some('µ') => bindec(&s['µ'.len_utf8()..], 30, 9),
		Some('n') => bindec(&s[1..], 40, 12),
		Some('p') => bindec(&s[1..], 50, 15),
		Some('f') => bindec(&s[1..], 60, 18),
		Some('a') => bindec(&s[1..], 70, 21),
		Some('e') => {
			if base != 10 {
				return Err(crate::de::DeserializeErrorKind::NumExpBase.at(location))
			}

			location.add_columns(1);

			let e = s[1..].parse()
				.map_err(|e| crate::DeserializeErrorKind::NumExpInvalid(e).at(location))?;
			Ok((0, e))
		}
		Some(_) => Err(crate::de::DeserializeErrorKind::NumTrailing(s.into()).at(location)),
		None => Ok((0, 0)),
	}
}