use crate::{PResult, Parser, unescape};
use alloy_primitives::U256;
use num_bigint::{BigInt, BigUint};
use num_rational::Ratio;
use num_traits::{Num, Signed, Zero};
use solar_ast::{token::*, *};
use solar_interface::{ByteSymbol, Session, Symbol, diagnostics::ErrorGuaranteed, kw};
use std::{borrow::Cow, fmt};
impl<'sess, 'ast> Parser<'sess, 'ast> {
#[instrument(level = "trace", skip_all)]
pub fn parse_lit(
&mut self,
with_subdenomination: bool,
) -> PResult<'sess, (Lit<'ast>, Option<SubDenomination>)> {
self.parse_spanned(|this| this.parse_lit_inner(with_subdenomination)).map(
|(span, (symbol, kind, subdenomination))| (Lit { span, symbol, kind }, subdenomination),
)
}
pub fn parse_subdenomination(&mut self) -> Option<SubDenomination> {
let sub = self.subdenomination();
if sub.is_some() {
self.bump();
}
sub
}
fn subdenomination(&self) -> Option<SubDenomination> {
match self.token.ident()?.name {
kw::Wei => Some(SubDenomination::Ether(EtherSubDenomination::Wei)),
kw::Gwei => Some(SubDenomination::Ether(EtherSubDenomination::Gwei)),
kw::Ether => Some(SubDenomination::Ether(EtherSubDenomination::Ether)),
kw::Seconds => Some(SubDenomination::Time(TimeSubDenomination::Seconds)),
kw::Minutes => Some(SubDenomination::Time(TimeSubDenomination::Minutes)),
kw::Hours => Some(SubDenomination::Time(TimeSubDenomination::Hours)),
kw::Days => Some(SubDenomination::Time(TimeSubDenomination::Days)),
kw::Weeks => Some(SubDenomination::Time(TimeSubDenomination::Weeks)),
kw::Years => Some(SubDenomination::Time(TimeSubDenomination::Years)),
_ => None,
}
}
pub(super) fn expect_no_subdenomination(&mut self) {
if let Some(_sub) = self.parse_subdenomination() {
let span = self.prev_token.span;
self.dcx().err("subdenominations aren't allowed here").span(span).emit();
}
}
fn parse_lit_inner(
&mut self,
with_subdenomination: bool,
) -> PResult<'sess, (Symbol, LitKind<'ast>, Option<SubDenomination>)> {
let lo = self.token.span;
if let TokenKind::Ident(symbol @ (kw::True | kw::False)) = self.token.kind {
self.bump();
let mut subdenomination =
if with_subdenomination { self.parse_subdenomination() } else { None };
self.subdenom_error(&mut subdenomination, lo.to(self.prev_token.span));
return Ok((symbol, LitKind::Bool(symbol != kw::False), subdenomination));
}
if !self.check_lit() {
return self.unexpected();
}
let Some(lit) = self.token.lit() else {
unreachable!("check_lit() returned true for non-literal token");
};
self.bump();
let mut subdenomination =
if with_subdenomination { self.parse_subdenomination() } else { None };
let result = match lit.kind {
TokenLitKind::Integer => self.parse_lit_int(lit.symbol, subdenomination),
TokenLitKind::Rational => self.parse_lit_rational(lit.symbol, subdenomination),
TokenLitKind::Str | TokenLitKind::UnicodeStr | TokenLitKind::HexStr => {
self.subdenom_error(&mut subdenomination, lo.to(self.prev_token.span));
self.parse_lit_str(lit)
}
TokenLitKind::Err(guar) => Ok(LitKind::Err(guar)),
};
let kind =
result.unwrap_or_else(|e| LitKind::Err(e.span(lo.to(self.prev_token.span)).emit()));
Ok((lit.symbol, kind, subdenomination))
}
fn subdenom_error(&mut self, slot: &mut Option<SubDenomination>, span: Span) {
if slot.is_some() {
*slot = None;
let msg = "sub-denominations are only allowed on number and rational literals";
self.dcx().err(msg).span(span).emit();
}
}
fn parse_lit_int(
&mut self,
symbol: Symbol,
subdenomination: Option<SubDenomination>,
) -> PResult<'sess, LitKind<'ast>> {
use LitError::*;
match parse_integer(self.sess, symbol, subdenomination) {
Ok(l) => Ok(l),
Err(e @ (IntegerLeadingZeros | IntegerTooLarge)) => Err(self.dcx().err(e.to_string())),
Err(EmptyInteger) => Ok(LitKind::Err(ErrorGuaranteed::new_unchecked())),
Err(e @ ParseInteger(_)) => panic!("failed to parse integer literal {symbol:?}: {e}"),
Err(
e @ (InvalidRational | EmptyRational | EmptyExponent | ParseRational(_)
| ParseExponent(_) | RationalTooLarge | ExponentTooLarge),
) => panic!("this error shouldn't happen for normal integer literals: {e}"),
}
}
fn parse_lit_rational(
&mut self,
symbol: Symbol,
subdenomination: Option<SubDenomination>,
) -> PResult<'sess, LitKind<'ast>> {
use LitError::*;
match parse_rational(self.sess, symbol, subdenomination) {
Ok(l) => Ok(l),
Err(
e @ (EmptyRational | IntegerTooLarge | RationalTooLarge | ExponentTooLarge
| IntegerLeadingZeros),
) => Err(self.dcx().err(e.to_string())),
Err(InvalidRational | EmptyExponent) => {
Ok(LitKind::Err(ErrorGuaranteed::new_unchecked()))
}
Err(e @ (ParseExponent(_) | ParseInteger(_) | ParseRational(_) | EmptyInteger)) => {
panic!("failed to parse rational literal {symbol:?}: {e}")
}
}
}
fn parse_lit_str(&mut self, lit: TokenLit) -> PResult<'sess, LitKind<'ast>> {
let mode = match lit.kind {
TokenLitKind::Str => StrKind::Str,
TokenLitKind::UnicodeStr => StrKind::Unicode,
TokenLitKind::HexStr => StrKind::Hex,
_ => unreachable!(),
};
let span = self.prev_token.span;
let (mut value, _) =
unescape::parse_string_literal(lit.symbol.as_str(), mode, span, self.sess);
let mut extra = vec![];
while let Some(TokenLit { symbol, kind }) = self.token.lit() {
if kind != lit.kind {
break;
}
extra.push((self.token.span, symbol));
let (parsed, _) =
unescape::parse_string_literal(symbol.as_str(), mode, self.token.span, self.sess);
value.to_mut().extend_from_slice(&parsed);
self.bump();
}
let kind = match lit.kind {
TokenLitKind::Str => StrKind::Str,
TokenLitKind::UnicodeStr => StrKind::Unicode,
TokenLitKind::HexStr => StrKind::Hex,
_ => unreachable!(),
};
let extra = self.alloc_vec(extra);
Ok(LitKind::Str(kind, ByteSymbol::intern(&value), extra))
}
}
#[derive(Debug, PartialEq, Eq)]
enum LitError {
InvalidRational,
EmptyInteger,
EmptyRational,
EmptyExponent,
ParseInteger(num_bigint::ParseBigIntError),
ParseRational(num_bigint::ParseBigIntError),
ParseExponent(std::num::ParseIntError),
IntegerTooLarge,
RationalTooLarge,
ExponentTooLarge,
IntegerLeadingZeros,
}
impl fmt::Display for LitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidRational => write!(f, "invalid rational literal"),
Self::EmptyInteger => write!(f, "empty integer"),
Self::EmptyRational => write!(f, "empty rational"),
Self::EmptyExponent => write!(f, "empty exponent"),
Self::ParseInteger(e) => write!(f, "failed to parse integer: {e}"),
Self::ParseRational(e) => write!(f, "failed to parse rational: {e}"),
Self::ParseExponent(e) => write!(f, "failed to parse exponent: {e}"),
Self::IntegerTooLarge => write!(f, "integer part too large"),
Self::RationalTooLarge => write!(f, "rational part too large"),
Self::ExponentTooLarge => write!(f, "exponent too large"),
Self::IntegerLeadingZeros => write!(f, "leading zeros are not allowed in integers"),
}
}
}
fn parse_integer<'ast>(
sess: &Session,
symbol: Symbol,
subdenomination: Option<SubDenomination>,
) -> Result<LitKind<'ast>, LitError> {
let s = &strip_underscores(symbol, sess)[..];
let base = match s.as_bytes() {
[b'0', b'x', ..] => Base::Hexadecimal,
[b'0', b'o', ..] => Base::Octal,
[b'0', b'b', ..] => Base::Binary,
_ => Base::Decimal,
};
if base == Base::Decimal && s.starts_with('0') && s.len() > 1 {
return Err(LitError::IntegerLeadingZeros);
}
if base == Base::Hexadecimal
&& s.len() == 42
&& let Ok(address) = s.parse()
{
return Ok(LitKind::Address(address));
}
let start = if base == Base::Decimal { 0 } else { 2 };
let s = &s[start..];
if s.is_empty() {
return Err(LitError::EmptyInteger);
}
let mut n = u256_from_str_radix(s, base)?;
if let Some(subdenomination) = subdenomination {
n = n.checked_mul(U256::from(subdenomination.value())).ok_or(LitError::IntegerTooLarge)?;
}
Ok(LitKind::Number(n))
}
fn parse_rational<'ast>(
sess: &Session,
symbol: Symbol,
subdenomination: Option<SubDenomination>,
) -> Result<LitKind<'ast>, LitError> {
let s = &strip_underscores(symbol, sess)[..];
debug_assert!(!s.is_empty());
if matches!(s.get(..2), Some("0b" | "0o" | "0x")) {
return Err(LitError::InvalidRational);
}
let (mut int, rat, exp) = match (s.find('.'), s.find(['e', 'E'])) {
(None, None) => (s, None, None),
(Some(dot), None) => {
let (int, rat) = split_at_exclusive(s, dot);
(int, Some(rat), None)
}
(None, Some(exp)) => {
let (int, exp) = split_at_exclusive(s, exp);
(int, None, Some(exp))
}
(Some(dot), Some(exp)) => {
if exp < dot {
return Err(LitError::InvalidRational);
}
let (int, rest) = split_at_exclusive(s, dot);
let (rat, exp) = split_at_exclusive(rest, exp - dot - 1);
(int, Some(rat), Some(exp))
}
};
if cfg!(debug_assertions) {
let mut reconstructed = String::from(int);
if let Some(rat) = rat {
reconstructed.push('.');
reconstructed.push_str(rat);
}
if let Some(exp) = exp {
let e = if s.contains('E') { 'E' } else { 'e' };
reconstructed.push(e);
reconstructed.push_str(exp);
}
assert_eq!(reconstructed, s, "{int:?} + {rat:?} + {exp:?}");
}
if int.is_empty() {
int = "0";
}
if rat.is_some_and(str::is_empty) {
return Err(LitError::EmptyRational);
}
if exp.is_some_and(str::is_empty) {
return Err(LitError::EmptyExponent);
}
if int.starts_with('0') && int.len() > 1 {
return Err(LitError::IntegerLeadingZeros);
}
let rat = rat.map(|rat| rat.trim_end_matches('0'));
let (int_s, is_rat) = match rat {
Some(rat) => (&[int, rat].concat()[..], true),
None => (int, false),
};
let int = big_int_from_str_radix(int_s, Base::Decimal, is_rat)?;
let fract_len = rat.map_or(0, str::len);
let fract_len = u16::try_from(fract_len).map_err(|_| LitError::RationalTooLarge)?;
let denominator = pow10(fract_len as u32);
let mut number = Ratio::new(int, denominator);
if number.is_zero() {
return Ok(LitKind::Number(U256::ZERO));
}
if let Some(exp) = exp {
let exp = exp.parse::<i32>().map_err(|e| match *e.kind() {
std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow => {
LitError::ExponentTooLarge
}
_ => LitError::ParseExponent(e),
})?;
let exp_abs = exp.unsigned_abs();
let power = || pow10(exp_abs);
if exp.is_negative() {
if !fits_precision_base_10(&number.denom().abs().into_parts().1, exp_abs) {
return Err(LitError::ExponentTooLarge);
}
number /= power();
} else if exp > 0 {
if !fits_precision_base_10(&number.numer().abs().into_parts().1, exp_abs) {
return Err(LitError::ExponentTooLarge);
}
number *= power();
}
}
if let Some(subdenomination) = subdenomination {
number *= BigInt::from(subdenomination.value());
}
if number.is_integer() {
big_to_u256(number.to_integer(), true).map(LitKind::Number)
} else {
let (numer, denom) = number.into_raw();
Ok(LitKind::Rational(Ratio::new(big_to_u256(numer, true)?, big_to_u256(denom, true)?)))
}
}
type Primitive = u128;
const MAX_BITS: u32 = 4096;
#[inline]
const fn max_digits<const BITS: u32>(base: Base) -> usize {
if matches!(base, Base::Binary) {
return BITS as usize;
}
match BITS {
Primitive::BITS => match base {
Base::Binary => BITS as usize,
Base::Octal => 43,
Base::Decimal => 39,
Base::Hexadecimal => 33,
},
MAX_BITS => match base {
Base::Binary => BITS as usize,
Base::Octal => 1366,
Base::Decimal => 1234,
Base::Hexadecimal => 1025,
},
_ => panic!("unknown bits"),
}
}
fn u256_from_str_radix(s: &str, base: Base) -> Result<U256, LitError> {
if s.len() <= max_digits::<{ Primitive::BITS }>(base)
&& let Ok(n) = Primitive::from_str_radix(s, base as u32)
{
return Ok(U256::from(n));
}
U256::from_str_radix(s, base as u64).map_err(|_| LitError::IntegerTooLarge)
}
fn big_int_from_str_radix(s: &str, base: Base, rat: bool) -> Result<BigInt, LitError> {
if s.len() > max_digits::<MAX_BITS>(base) {
return Err(if rat { LitError::RationalTooLarge } else { LitError::IntegerTooLarge });
}
if s.len() <= max_digits::<{ Primitive::BITS }>(base)
&& let Ok(n) = Primitive::from_str_radix(s, base as u32)
{
return Ok(BigInt::from(n));
}
BigInt::from_str_radix(s, base as u32)
.map_err(|e| if rat { LitError::ParseRational(e) } else { LitError::ParseInteger(e) })
}
fn big_to_u256(big: BigInt, rat: bool) -> Result<U256, LitError> {
let (_, limbs) = big.to_u64_digits();
U256::checked_from_limbs_slice(&limbs).ok_or(if rat {
LitError::RationalTooLarge
} else {
LitError::IntegerTooLarge
})
}
fn pow10(exp: u32) -> BigInt {
if let Some(n) = 10usize.checked_pow(exp) {
BigInt::from(n)
} else {
BigInt::from(10u64).pow(exp)
}
}
fn fits_precision_base_10(mantissa: &BigUint, exp: u32) -> bool {
fits_precision_base_x(mantissa, std::f64::consts::LOG2_10, exp)
}
fn fits_precision_base_x(mantissa: &BigUint, log_2_of_base: f64, exp: u32) -> bool {
if mantissa.is_zero() {
return true;
}
let max = MAX_BITS as u64;
let bits = mantissa.bits();
if bits > max {
return false;
}
let bits_needed = bits + f64::floor(log_2_of_base * exp as f64) as u64;
bits_needed <= max
}
#[track_caller]
fn split_at_exclusive(s: &str, idx: usize) -> (&str, &str) {
(&s[..idx], &s[idx + 1..])
}
#[inline]
fn strip_underscores(symbol: Symbol, sess: &Session) -> Cow<'_, str> {
let s = symbol.as_str_in(sess);
if s.contains('_') { Cow::Owned(strip_underscores_slow(s)) } else { Cow::Borrowed(s) }
}
#[inline(never)]
#[cold]
fn strip_underscores_slow(s: &str) -> String {
let mut s = s.to_string();
s.retain(|c| c != '_');
s
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Lexer;
use alloy_primitives::{Address, address};
use solar_interface::Session;
#[track_caller]
fn lex_literal(src: &str, should_fail: bool, f: impl FnOnce(&Session, Symbol)) {
let sess =
Session::builder().with_buffer_emitter(Default::default()).single_threaded().build();
sess.enter_sequential(|| {
let file = sess.source_map().new_source_file("test".to_string(), src).unwrap();
let tokens = Lexer::from_source_file(&sess, &file).into_tokens();
let diags = sess.dcx.emitted_diagnostics().unwrap();
assert_eq!(tokens.len(), 1, "expected exactly 1 token: {tokens:?}\n{diags}");
assert_eq!(
sess.dcx.has_errors().is_err(),
should_fail,
"{src:?} -> {tokens:?};\n{diags}",
);
f(&sess, tokens[0].lit().expect("not a literal").symbol)
});
}
#[test]
fn integer() {
use LitError::*;
#[track_caller]
fn check_int(src: &str, expected: Result<&str, LitError>) {
lex_literal(src, false, |sess, symbol| {
let res = match parse_integer(sess, symbol, None) {
Ok(LitKind::Number(n)) => Ok(n),
Ok(x) => panic!("not a number: {x:?} ({src:?})"),
Err(e) => Err(e),
};
let expected = match expected {
Ok(s) => Ok(U256::from_str_radix(s, 10).unwrap()),
Err(e) => Err(e),
};
assert_eq!(res, expected, "{src:?}");
});
}
#[track_caller]
fn check_address(src: &str, expected: Result<Address, &str>) {
lex_literal(src, false, |sess, symbol| match expected {
Ok(address) => match parse_integer(sess, symbol, None) {
Ok(LitKind::Address(a)) => assert_eq!(a, address, "{src:?}"),
e => panic!("not an address: {e:?} ({src:?})"),
},
Err(int) => match parse_integer(sess, symbol, None) {
Ok(LitKind::Number(n)) => {
assert_eq!(n, U256::from_str_radix(int, 10).unwrap(), "{src:?}")
}
e => panic!("not an integer: {e:?} ({src:?})"),
},
});
}
check_int("00", Err(IntegerLeadingZeros));
check_int("01", Err(IntegerLeadingZeros));
check_int("00", Err(IntegerLeadingZeros));
check_int("001", Err(IntegerLeadingZeros));
check_int("000", Err(IntegerLeadingZeros));
check_int("0001", Err(IntegerLeadingZeros));
check_int("0", Ok("0"));
check_int("1", Ok("1"));
check_int("10", Ok("10"));
check_int("0x10", Ok("16"));
check_address("0x00000000000000000000000000000000000000", Err("0"));
check_address("0x000000000000000000000000000000000000000", Err("0"));
check_address("0x0000000000000000000000000000000000000000", Ok(Address::ZERO));
check_address("0x00000000000000000000000000000000000000000", Err("0"));
check_address("0x000000000000000000000000000000000000000000", Err("0"));
check_address("0x0000000000000000000000000000000000000001", Ok(Address::with_last_byte(1)));
check_address(
"0x52908400098527886E0F7030069857D2E4169EE7",
Ok(address!("0x52908400098527886E0F7030069857D2E4169EE7")),
);
check_address(
"0x52908400098527886E0F7030069857D2E4169Ee7",
Ok(address!("0x52908400098527886E0F7030069857D2E4169EE7")),
);
check_address(
"0x8617E340B3D01FA5F11F306F4090FD50E238070D",
Ok(address!("0x8617E340B3D01FA5F11F306F4090FD50E238070D")),
);
check_address(
"0xde709f2102306220921060314715629080e2fb77",
Ok(address!("0xde709f2102306220921060314715629080e2fb77")),
);
check_address(
"0x27b1fdb04752bbc536007a920d24acb045561c26",
Ok(address!("0x27b1fdb04752bbc536007a920d24acb045561c26")),
);
check_address(
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
Ok(address!("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")),
);
check_address(
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
Ok(address!("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")),
);
check_address(
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
Ok(address!("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")),
);
check_address(
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
Ok(address!("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")),
);
}
#[test]
fn rational() {
use LitError::*;
#[track_caller]
fn check_int_full(src: &str, should_fail_lexing: bool, expected: Result<&str, LitError>) {
lex_literal(src, should_fail_lexing, |sess, symbol| {
let res = match parse_rational(sess, symbol, None) {
Ok(LitKind::Number(r)) => Ok(r),
Ok(x) => panic!("not a number: {x:?} ({src:?})"),
Err(e) => Err(e),
};
let expected = match expected {
Ok(s) => Ok(U256::from_str_radix(s, 10).unwrap()),
Err(e) => Err(e),
};
assert_eq!(res, expected, "{src:?}");
});
}
#[track_caller]
fn check_int(src: &str, expected: Result<&str, LitError>) {
check_int_full(src, false, expected);
}
#[track_caller]
fn check_rat(src: &str, expected: Result<&str, LitError>) {
lex_literal(src, false, |sess, symbol| {
let res = match parse_rational(sess, symbol, None) {
Ok(LitKind::Rational(r)) => Ok(r),
Ok(x) => panic!("not a number: {x:?} ({src:?})"),
Err(e) => Err(e),
};
let expected = match expected {
Ok(s) => Ok(Ratio::from_str_radix(s, 10).unwrap()),
Err(e) => Err(e),
};
assert_eq!(res, expected, "{src:?}");
});
}
#[track_caller]
fn zeros(before: &str, zeros: usize) -> String {
before.to_string() + &"0".repeat(zeros)
}
check_int("00", Err(IntegerLeadingZeros));
check_int("0_0", Err(IntegerLeadingZeros));
check_int("01", Err(IntegerLeadingZeros));
check_int("0_1", Err(IntegerLeadingZeros));
check_int("00", Err(IntegerLeadingZeros));
check_int("001", Err(IntegerLeadingZeros));
check_int("000", Err(IntegerLeadingZeros));
check_int("0001", Err(IntegerLeadingZeros));
check_int("0.", Err(EmptyRational));
check_int("0", Ok("0"));
check_int("0e0", Ok("0"));
check_int("0.0", Ok("0"));
check_int("0.00", Ok("0"));
check_int("0.0e0", Ok("0"));
check_int("0.00e0", Ok("0"));
check_int("0.0e00", Ok("0"));
check_int("0.00e00", Ok("0"));
check_int("0.0e-0", Ok("0"));
check_int("0.00e-0", Ok("0"));
check_int("0.0e-00", Ok("0"));
check_int("0.00e-00", Ok("0"));
check_int("0.0e1", Ok("0"));
check_int("0.00e1", Ok("0"));
check_int("0.00e01", Ok("0"));
check_int("0e999999", Ok("0"));
check_int("0E123456789", Ok("0"));
check_int(".0", Ok("0"));
check_int(".00", Ok("0"));
check_int(".0e0", Ok("0"));
check_int(".00e0", Ok("0"));
check_int(".0e00", Ok("0"));
check_int(".00e00", Ok("0"));
check_int(".0e-0", Ok("0"));
check_int(".00e-0", Ok("0"));
check_int(".0e-00", Ok("0"));
check_int(".00e-00", Ok("0"));
check_int(".0e1", Ok("0"));
check_int(".00e1", Ok("0"));
check_int(".00e01", Ok("0"));
check_int("1", Ok("1"));
check_int("1e0", Ok("1"));
check_int("1.0", Ok("1"));
check_int("1.00", Ok("1"));
check_int("1.0e0", Ok("1"));
check_int("1.00e0", Ok("1"));
check_int("1.0e00", Ok("1"));
check_int("1.00e00", Ok("1"));
check_int("1.0e-0", Ok("1"));
check_int("1.00e-0", Ok("1"));
check_int("1.0e-00", Ok("1"));
check_int("1.00e-00", Ok("1"));
check_int_full("0b", true, Err(InvalidRational));
check_int_full("0b0", true, Err(InvalidRational));
check_int_full("0b01", true, Err(InvalidRational));
check_int_full("0b01.0", true, Err(InvalidRational));
check_int_full("0b01.0e1", true, Err(InvalidRational));
check_int_full("0b0e", true, Err(InvalidRational));
check_int_full("0o", true, Err(InvalidRational));
check_int_full("0o0", true, Err(InvalidRational));
check_int_full("0o01", true, Err(InvalidRational));
check_int_full("0o01.0", true, Err(InvalidRational));
check_int_full("0o01.0e1", true, Err(InvalidRational));
check_int_full("0o0e", true, Err(InvalidRational));
check_int_full("0x", true, Err(InvalidRational));
check_int_full("0x0", false, Err(InvalidRational));
check_int_full("0x01", false, Err(InvalidRational));
check_int_full("0x01.0", true, Err(InvalidRational));
check_int_full("0x01.0e1", true, Err(InvalidRational));
check_int_full("0x0e", false, Err(InvalidRational));
check_int_full("0x0e.0", true, Err(InvalidRational));
check_int_full("0x0e.0e1", true, Err(InvalidRational));
check_int("1e1", Ok("10"));
check_int("1.0e1", Ok("10"));
check_int("1.00e1", Ok("10"));
check_int("1.00e01", Ok("10"));
check_int("1.1e1", Ok("11"));
check_int("1.10e1", Ok("11"));
check_int("1.100e1", Ok("11"));
check_int("1.2e1", Ok("12"));
check_int("1.200e1", Ok("12"));
check_int("1e10", Ok(&zeros("1", 10)));
check_int("1.0e10", Ok(&zeros("1", 10)));
check_int("1.1e10", Ok(&zeros("11", 9)));
check_int("10e-1", Ok("1"));
check_rat("1e-1", Ok("1/10"));
check_rat("1e-2", Ok("1/100"));
check_rat("1e-3", Ok("1/1000"));
check_rat("1.0e-1", Ok("1/10"));
check_rat("1.0e-2", Ok("1/100"));
check_rat("1.0e-3", Ok("1/1000"));
check_rat("1.1e-1", Ok("11/100"));
check_rat("1.1e-2", Ok("11/1000"));
check_rat("1.1e-3", Ok("11/10000"));
check_rat("1.1", Ok("11/10"));
check_rat("1.10", Ok("11/10"));
check_rat("1.100", Ok("11/10"));
check_rat("1.2", Ok("12/10"));
check_rat("1.20", Ok("12/10"));
}
}