use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer};
use crate::source::ReadChar;
use bitflags::bitflags;
use boa_ast::PositionGroup;
use boa_interner::Interner;
use regress::Flags;
use std::fmt::{Display, Write};
use std::str::{self, FromStr};
const MAXIMUM_REGEX_FLAGS: usize = 8;
#[derive(Debug, Clone, Copy)]
pub(super) struct RegexLiteral {
init_with_eq: bool,
}
impl RegexLiteral {
pub(super) fn new(init_with_eq: bool) -> Self {
Self { init_with_eq }
}
}
impl<R> Tokenizer<R> for RegexLiteral {
fn lex(
&mut self,
cursor: &mut Cursor<R>,
start_pos: PositionGroup,
interner: &mut Interner,
) -> Result<Token, Error>
where
R: ReadChar,
{
let mut body = Vec::new();
if self.init_with_eq {
body.push(u32::from(b'='));
}
let mut is_class_char = false;
loop {
match cursor.next_char()? {
None => {
return Err(Error::syntax(
"abrupt end on regular expression",
cursor.pos(),
));
}
Some(b) => {
match b {
0x2F if !is_class_char => break, 0x5B => {
is_class_char = true;
body.push(b);
}
0x5D if is_class_char => {
is_class_char = false;
body.push(b);
}
0xA | 0xD | 0x2028 | 0x2029 => {
return Err(Error::syntax(
"new lines are not allowed in regular expressions",
cursor.pos(),
));
}
0x5C => {
body.push(b);
if let Some(sc) = cursor.next_char()? {
match sc {
0xA | 0xD | 0x2028 | 0x2029 => {
return Err(Error::syntax(
"new lines are not allowed in regular expressions",
cursor.pos(),
));
}
b => body.push(b),
}
} else {
return Err(Error::syntax(
"abrupt end on regular expression",
cursor.pos(),
));
}
}
_ => body.push(b),
}
}
}
}
let mut flags: [u32; MAXIMUM_REGEX_FLAGS] = [0; MAXIMUM_REGEX_FLAGS];
let n = cursor.take_array_alphabetic(&mut flags)?;
if n > MAXIMUM_REGEX_FLAGS {
return Err(Error::syntax(
"Invalid regular expression: too many flags",
start_pos,
));
}
let flags: RegExpFlags =
RegExpFlags::try_from(&flags[..n]).map_err(|e| Error::syntax(e, start_pos))?;
let mut body_utf16 = Vec::with_capacity(body.len());
#[allow(clippy::cast_possible_truncation)]
for cp in &body {
let cp = *cp;
if cp <= 0xFFFF {
body_utf16.push(cp as u16);
} else {
let cp = cp - 0x1_0000;
let high = 0xD800 | ((cp >> 10) as u16);
let low = 0xDC00 | ((cp as u16) & 0x3FF);
body_utf16.push(high);
body_utf16.push(low);
}
}
drop(
regress::backends::try_parse(body.into_iter(), flags.into()).map_err(|error| {
Error::syntax(
format!("Invalid regular expression literal: {error}"),
start_pos,
)
})?,
);
Ok(Token::new_by_position_group(
TokenKind::regular_expression_literal(
interner.get_or_intern(body_utf16.as_slice()),
interner.get_or_intern(flags.to_string().as_str()),
),
start_pos,
cursor.pos_group(),
))
}
}
bitflags! {
#[derive(Debug, Default, Copy, Clone)]
pub struct RegExpFlags: u8 {
const GLOBAL = 0b0000_0001;
const IGNORE_CASE = 0b0000_0010;
const MULTILINE = 0b0000_0100;
const DOT_ALL = 0b0000_1000;
const UNICODE = 0b0001_0000;
const STICKY = 0b0010_0000;
const HAS_INDICES = 0b0100_0000;
const UNICODE_SETS = 0b1000_0000;
}
}
impl TryFrom<&[u32]> for RegExpFlags {
type Error = String;
fn try_from(value: &[u32]) -> Result<Self, Self::Error> {
let mut flags = Self::default();
for c in value {
let c = char::from_u32(*c)
.ok_or_else(|| format!("Invalid regular expression flag: {c}"))?;
let new_flag = match c {
'g' => Self::GLOBAL,
'i' => Self::IGNORE_CASE,
'm' => Self::MULTILINE,
's' => Self::DOT_ALL,
'u' => Self::UNICODE,
'y' => Self::STICKY,
'd' => Self::HAS_INDICES,
'v' => Self::UNICODE_SETS,
_ => return Err(format!("invalid regular expression flag {c}")),
};
if flags.contains(new_flag) {
return Err(format!("repeated regular expression flag {c}"));
}
flags.insert(new_flag);
}
if flags.contains(Self::UNICODE) && flags.contains(Self::UNICODE_SETS) {
return Err("cannot use both 'u' and 'v' flags".into());
}
Ok(flags)
}
}
impl FromStr for RegExpFlags {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut flags = Self::default();
for c in s.bytes() {
let new_flag = match c {
b'g' => Self::GLOBAL,
b'i' => Self::IGNORE_CASE,
b'm' => Self::MULTILINE,
b's' => Self::DOT_ALL,
b'u' => Self::UNICODE,
b'y' => Self::STICKY,
b'd' => Self::HAS_INDICES,
b'v' => Self::UNICODE_SETS,
_ => return Err(format!("invalid regular expression flag {}", char::from(c))),
};
if flags.contains(new_flag) {
return Err(format!(
"repeated regular expression flag {}",
char::from(c)
));
}
flags.insert(new_flag);
}
if flags.contains(Self::UNICODE) && flags.contains(Self::UNICODE_SETS) {
return Err("cannot use both 'u' and 'v' flags".into());
}
Ok(flags)
}
}
impl Display for RegExpFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.contains(Self::HAS_INDICES) {
f.write_char('d')?;
}
if self.contains(Self::GLOBAL) {
f.write_char('g')?;
}
if self.contains(Self::IGNORE_CASE) {
f.write_char('i')?;
}
if self.contains(Self::MULTILINE) {
f.write_char('m')?;
}
if self.contains(Self::DOT_ALL) {
f.write_char('s')?;
}
if self.contains(Self::UNICODE) {
f.write_char('u')?;
}
if self.contains(Self::STICKY) {
f.write_char('y')?;
}
if self.contains(Self::UNICODE_SETS) {
f.write_char('v')?;
}
Ok(())
}
}
impl From<RegExpFlags> for Flags {
fn from(value: RegExpFlags) -> Self {
Self {
icase: value.contains(RegExpFlags::IGNORE_CASE),
multiline: value.contains(RegExpFlags::MULTILINE),
dot_all: value.contains(RegExpFlags::DOT_ALL),
unicode: value.contains(RegExpFlags::UNICODE),
unicode_sets: value.contains(RegExpFlags::UNICODE_SETS),
..Self::default()
}
}
}