use super::{Cursor, Error, TokenKind, Tokenizer};
use crate::{
builtins::BigInt,
profiler::BoaProfiler,
syntax::{
ast::{Position, Span},
lexer::{token::Numeric, Token},
},
};
use std::{io::Read, str::FromStr};
#[derive(Debug, Clone, Copy)]
pub(super) struct NumberLiteral {
init: char,
strict_mode: bool,
}
impl NumberLiteral {
pub(super) fn new(init: char, strict_mode: bool) -> Self {
Self { init, strict_mode }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumericKind {
Rational,
Integer(u32),
BigInt(u32),
}
impl NumericKind {
fn base(self) -> u32 {
match self {
Self::Rational => 10,
Self::Integer(base) => base,
Self::BigInt(base) => base,
}
}
fn to_bigint(self) -> Self {
match self {
Self::Rational => unreachable!("can not convert rational number to BigInt"),
Self::Integer(base) => Self::BigInt(base),
Self::BigInt(base) => Self::BigInt(base),
}
}
}
fn take_signed_integer<R>(
buf: &mut String,
cursor: &mut Cursor<R>,
kind: &NumericKind,
) -> Result<(), Error>
where
R: Read,
{
match cursor.next_char()? {
Some('+') => {
buf.push('+');
if !cursor.next_is_pred(&|c: char| c.is_digit(kind.base()))? {
return Err(Error::syntax("No digit found after + symbol", cursor.pos()));
}
}
Some('-') => {
buf.push('-');
if !cursor.next_is_pred(&|c: char| c.is_digit(kind.base()))? {
return Err(Error::syntax("No digit found after - symbol", cursor.pos()));
}
}
Some(c) if c.is_digit(kind.base()) => buf.push(c),
Some(c) => {
return Err(Error::syntax(
format!(
"When lexing exponential value found unexpected char: '{}'",
c
),
cursor.pos(),
));
}
None => {
return Err(Error::syntax(
"Abrupt end: No exponential value found",
cursor.pos(),
));
}
}
cursor.take_while_pred(buf, &|c: char| c.is_digit(kind.base()))?;
Ok(())
}
fn check_after_numeric_literal<R>(cursor: &mut Cursor<R>) -> Result<(), Error>
where
R: Read,
{
let pred = |ch: char| ch.is_ascii_alphanumeric() || ch == '$' || ch == '_';
if cursor.next_is_pred(&pred)? {
Err(Error::syntax(
"a numeric literal must not be followed by an alphanumeric, $ or _ characters",
cursor.pos(),
))
} else {
Ok(())
}
}
impl<R> Tokenizer<R> for NumberLiteral {
fn lex(&mut self, cursor: &mut Cursor<R>, start_pos: Position) -> Result<Token, Error>
where
R: Read,
{
let _timer = BoaProfiler::global().start_event("NumberLiteral", "Lexing");
let mut buf = self.init.to_string();
let mut kind = NumericKind::Integer(10);
let c = cursor.peek();
if self.init == '0' {
if let Some(ch) = c? {
match ch {
'x' | 'X' => {
cursor.next_char()?.expect("x or X character vanished");
buf.pop();
kind = NumericKind::Integer(16);
}
'o' | 'O' => {
cursor.next_char()?.expect("o or O character vanished");
buf.pop();
kind = NumericKind::Integer(8);
}
'b' | 'B' => {
cursor.next_char()?.expect("b or B character vanished");
buf.pop();
kind = NumericKind::Integer(2);
}
'n' => {
cursor.next_char()?.expect("n character vanished");
return Ok(Token::new(
TokenKind::NumericLiteral(Numeric::BigInt(0.into())),
Span::new(start_pos, cursor.pos()),
));
}
ch => {
if ch.is_digit(8) {
if self.strict_mode {
return Err(Error::syntax(
"implicit octal literals are not allowed in strict mode",
start_pos,
));
} else {
buf.pop();
buf.push(cursor.next_char()?.expect("'0' character vanished"));
kind = NumericKind::Integer(8);
}
} else if ch.is_digit(10) {
if self.strict_mode {
return Err(Error::syntax(
"leading 0's are not allowed in strict mode",
start_pos,
));
} else {
buf.push(cursor.next_char()?.expect("Number digit vanished"));
}
} }
}
} else {
return Ok(Token::new(
TokenKind::NumericLiteral(Numeric::Integer(0)),
Span::new(start_pos, cursor.pos()),
));
}
}
cursor.take_while_pred(&mut buf, &|c: char| c.is_digit(kind.base()))?;
match cursor.peek()? {
Some('n') => {
cursor.next_char()?.expect("n character vanished");
kind = kind.to_bigint();
}
Some('.') => {
if kind.base() == 10 {
cursor.next_char()?.expect(". token vanished");
buf.push('.'); kind = NumericKind::Rational;
cursor.take_while_pred(&mut buf, &|c: char| c.is_digit(kind.base()))?;
match cursor.peek()? {
Some('e') | Some('E') => {
cursor.next_char()?.expect("e or E token vanished");
buf.push('E');
take_signed_integer(&mut buf, cursor, &kind)?;
}
Some(_) | None => {
}
}
}
}
Some('e') | Some('E') => {
kind = NumericKind::Rational;
cursor.next_char()?.expect("e or E character vanished"); buf.push('E');
take_signed_integer(&mut buf, cursor, &kind)?;
}
Some(_) | None => {
}
}
check_after_numeric_literal(cursor)?;
let num = match kind {
NumericKind::BigInt(base) => {
Numeric::BigInt(
BigInt::from_string_radix(&buf, base).expect("Could not convert to BigInt")
)
}
NumericKind::Rational => {
let val = f64::from_str(&buf).expect("Failed to parse float after checks");
let int_val = val as i32;
#[allow(clippy::float_cmp)]
if (int_val as f64) == val {
Numeric::Integer(int_val)
} else {
Numeric::Rational(val)
}
},
NumericKind::Integer(base) => {
if let Ok(num) = i32::from_str_radix(&buf, base) {
Numeric::Integer(num)
} else {
let b = f64::from(base);
let mut result = 0.0_f64;
for c in buf.chars() {
let digit = f64::from(c.to_digit(base).expect("could not parse digit after already checking validity"));
result = result * b + digit;
}
Numeric::Rational(result)
}
}
};
Ok(Token::new(
TokenKind::NumericLiteral(num),
Span::new(start_pos, cursor.pos()),
))
}
}