use bstr::{BStr, BString, ByteSlice, Utf8Error};
use std::rc::Rc;
use crate::compiler::LiteralId;
use crate::scanner::{
RuntimeObject, RuntimeObjectHandle, ScanContext, ScanState,
};
use crate::utils::cast;
pub(crate) trait String: Default {
fn new<V: Into<Vec<u8>>>(s: V) -> Self;
fn into_wasm_with_ctx(self, ctx: &mut ScanContext) -> RuntimeStringWasm;
fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self;
}
pub(crate) type RuntimeStringWasm = i64;
#[derive(Debug, PartialEq)]
pub(crate) enum RuntimeString {
Literal(LiteralId),
ScannedDataSlice { offset: usize, length: usize },
Rc(Rc<BString>),
}
impl Default for RuntimeString {
fn default() -> Self {
Self::ScannedDataSlice { offset: 0, length: 0 }
}
}
impl String for RuntimeString {
fn new<S: Into<Vec<u8>>>(s: S) -> Self {
Self::Rc(Rc::new(BString::new(s.into())))
}
fn into_wasm_with_ctx(self, ctx: &mut ScanContext) -> RuntimeStringWasm {
match self {
Self::Literal(id) => i64::from(id) << 2,
Self::Rc(s) => {
let handle: i64 = ctx.store_string(s).into();
(handle << 2) | 1
}
Self::ScannedDataSlice { offset, length } => {
if length >= u16::MAX as usize {
panic!(
"runtime-string slices can't be larger than {}",
u16::MAX
)
}
((offset as i64) << 18) | ((length as i64) << 2) | 2
}
}
}
fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self {
if let ScanState::ScanningData(data) = &ctx.scan_state {
let data = data.as_ref();
let data_start = data.as_ptr() as usize;
let data_end = data_start + data.len();
let s_start = s.as_ptr() as usize;
let s_end = s_start + s.len();
if s_start >= data_start && s_end <= data_end {
return Self::ScannedDataSlice {
offset: s_start - data_start,
length: s.len(),
};
}
}
Self::Rc(Rc::new(BString::from(s)))
}
}
impl RuntimeString {
pub(crate) fn as_bstr<'a>(&'a self, ctx: &'a ScanContext) -> &'a BStr {
match self {
Self::Literal(id) => {
ctx.compiled_rules.lit_pool().get(*id).unwrap()
}
Self::ScannedDataSlice { offset, length } => {
let data = ctx.scanned_data().unwrap();
BStr::new(&data[*offset..*offset + *length])
}
Self::Rc(s) => s.as_bstr(),
}
}
#[inline]
pub(crate) fn to_str<'a>(
&'a self,
ctx: &'a ScanContext,
) -> Result<&'a str, Utf8Error> {
self.as_bstr(ctx).to_str()
}
pub(crate) fn into_wasm(self) -> RuntimeStringWasm {
match self {
Self::Literal(id) => i64::from(id) << 2,
_ => unreachable!(),
}
}
pub(crate) fn from_wasm(
ctx: &mut ScanContext,
s: RuntimeStringWasm,
) -> Self {
match s & 0x3 {
0 => Self::Literal(LiteralId::from((s >> 2) as u32)),
1 => {
let handle = RuntimeObjectHandle::from(s >> 2);
let s = cast!(
ctx.runtime_objects.get(&handle).unwrap(),
RuntimeObject::String
);
Self::Rc(s.clone())
}
2 => Self::ScannedDataSlice {
offset: (s >> 18) as usize,
length: ((s >> 2) & 0xffff) as usize,
},
_ => unreachable!(),
}
}
#[inline]
pub(crate) fn len(&self, ctx: &ScanContext) -> usize {
self.as_bstr(ctx).len()
}
#[inline]
pub(crate) fn eq(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).eq(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn ne(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).ne(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn lt(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).lt(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn gt(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).gt(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn le(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).le(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn ge(&self, other: &Self, ctx: &ScanContext) -> bool {
self.as_bstr(ctx).ge(other.as_bstr(ctx))
}
#[inline]
pub(crate) fn contains(
&self,
other: &Self,
ctx: &ScanContext,
case_insensitive: bool,
) -> bool {
if case_insensitive {
let this = self.as_bstr(ctx).to_lowercase();
let other = other.as_bstr(ctx).to_lowercase();
this.contains_str(other)
} else {
self.as_bstr(ctx).contains_str(other.as_bstr(ctx))
}
}
#[inline]
pub(crate) fn starts_with(
&self,
other: &Self,
ctx: &ScanContext,
case_insensitive: bool,
) -> bool {
if case_insensitive {
let this = self.as_bstr(ctx).to_lowercase();
let other = other.as_bstr(ctx).to_lowercase();
this.starts_with_str(other)
} else {
self.as_bstr(ctx).starts_with_str(other.as_bstr(ctx))
}
}
#[inline]
pub(crate) fn ends_with(
&self,
other: &Self,
ctx: &ScanContext,
case_insensitive: bool,
) -> bool {
if case_insensitive {
let this = self.as_bstr(ctx).to_lowercase();
let other = other.as_bstr(ctx).to_lowercase();
this.ends_with_str(other)
} else {
self.as_bstr(ctx).ends_with_str(other.as_bstr(ctx))
}
}
#[inline]
pub(crate) fn equals(
&self,
other: &Self,
ctx: &ScanContext,
case_insensitive: bool,
) -> bool {
if case_insensitive {
let this = self.as_bstr(ctx).to_lowercase();
let other = other.as_bstr(ctx).to_lowercase();
this.eq(&other)
} else {
self.as_bstr(ctx).eq(other.as_bstr(ctx))
}
}
}
#[derive(Debug, Default)]
pub(crate) struct FixedLenString<const LEN: usize>(RuntimeString);
impl<const LEN: usize> String for FixedLenString<LEN> {
fn new<S: Into<Vec<u8>>>(s: S) -> Self {
let s = s.into();
assert_eq!(
s.len(),
LEN,
"FixedLenString<{}>::new called with invalid length: {}",
LEN,
s.len(),
);
Self(RuntimeString::new(s))
}
fn into_wasm_with_ctx(self, ctx: &mut ScanContext) -> RuntimeStringWasm {
self.0.into_wasm_with_ctx(ctx)
}
fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self {
assert_eq!(
s.len(),
LEN,
"FixedLenString<{}>::new called with invalid length: {}",
LEN,
s.len(),
);
Self(RuntimeString::from_slice(ctx, s))
}
}
#[derive(Debug, Default)]
pub(crate) struct Lowercase<S: String>(S);
impl<S: String> String for Lowercase<S> {
fn new<V: Into<Vec<u8>>>(s: V) -> Self {
Self(S::new(s))
}
fn into_wasm_with_ctx(self, ctx: &mut ScanContext) -> RuntimeStringWasm {
self.0.into_wasm_with_ctx(ctx)
}
fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self {
Self(S::from_slice(ctx, s))
}
}
#[derive(Debug, Default)]
pub(crate) struct Uppercase<S: String>(S);
impl<S: String> String for Uppercase<S> {
fn new<V: Into<Vec<u8>>>(s: V) -> Self {
Self(S::new(s))
}
fn into_wasm_with_ctx(self, ctx: &mut ScanContext) -> RuntimeStringWasm {
self.0.into_wasm_with_ctx(ctx)
}
fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self {
Self(S::from_slice(ctx, s))
}
}