use serde::Deserializer;
use serde::Serializer;
use std::borrow::Borrow;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::Arc;
use url::Url;
use v8::NewStringType;
static EMPTY_STRING: v8::OneByteConst =
v8::String::create_external_onebyte_const("".as_bytes());
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct FastStaticString {
s: &'static v8::OneByteConst,
}
impl FastStaticString {
pub const fn new(s: &'static v8::OneByteConst) -> Self {
FastStaticString { s }
}
pub fn as_str(&self) -> &'static str {
self.s.as_ref()
}
pub fn as_bytes(&self) -> &'static [u8] {
self.s.as_ref()
}
#[doc(hidden)]
pub const fn create_external_onebyte_const(
s: &'static [u8],
) -> v8::OneByteConst {
v8::String::create_external_onebyte_const(s)
}
pub fn v8_string<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
) -> v8::Local<'s, v8::String> {
FastString::from(*self).v8_string(scope)
}
pub const fn into_v8_const_ptr(&self) -> *const v8::OneByteConst {
self.s as _
}
}
impl From<&'static v8::OneByteConst> for FastStaticString {
fn from(s: &'static v8::OneByteConst) -> Self {
Self::new(s)
}
}
impl From<FastStaticString> for *const v8::OneByteConst {
fn from(val: FastStaticString) -> Self {
val.into_v8_const_ptr()
}
}
impl Hash for FastStaticString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl AsRef<str> for FastStaticString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for FastStaticString {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Borrow<str> for FastStaticString {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Debug for FastStaticString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl Default for FastStaticString {
fn default() -> Self {
FastStaticString { s: &EMPTY_STRING }
}
}
impl PartialEq for FastStaticString {
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for FastStaticString {}
impl Display for FastStaticString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub struct FastString {
inner: FastStringInner,
}
enum FastStringInner {
Static(&'static str),
StaticAscii(&'static str),
StaticConst(FastStaticString),
Owned(Box<str>),
Arc(Arc<str>),
}
impl FastString {
pub const fn from_static(s: &'static str) -> Self {
if s.is_ascii() {
Self {
inner: FastStringInner::StaticAscii(s),
}
} else {
Self {
inner: FastStringInner::Static(s),
}
}
}
pub unsafe fn from_ascii_static_unchecked(s: &'static str) -> Self {
debug_assert!(
s.is_ascii(),
"use `from_non_ascii_static_unsafe` for non-ASCII strings",
);
Self {
inner: FastStringInner::StaticAscii(s),
}
}
pub fn from_non_ascii_static(s: &'static str) -> Self {
Self {
inner: FastStringInner::Static(s),
}
}
pub fn as_static_str(&self) -> Option<&'static str> {
match self.inner {
FastStringInner::Static(s) => Some(s),
FastStringInner::StaticAscii(s) => Some(s),
FastStringInner::StaticConst(s) => Some(s.as_str()),
_ => None,
}
}
pub fn into_cheap_copy(self) -> (Self, Self) {
match self.inner {
FastStringInner::Owned(s) => {
let s: Arc<str> = s.into();
(
Self {
inner: FastStringInner::Arc(s.clone()),
},
Self {
inner: FastStringInner::Arc(s),
},
)
}
_ => (self.try_clone().unwrap(), self),
}
}
pub fn try_clone(&self) -> Option<Self> {
match &self.inner {
FastStringInner::Static(s) => Some(Self {
inner: FastStringInner::Static(s),
}),
FastStringInner::StaticAscii(s) => Some(Self {
inner: FastStringInner::StaticAscii(s),
}),
FastStringInner::StaticConst(s) => Some(Self {
inner: FastStringInner::StaticConst(*s),
}),
FastStringInner::Arc(s) => Some(Self {
inner: FastStringInner::Arc(s.clone()),
}),
FastStringInner::Owned(_s) => None,
}
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[inline(always)]
pub fn as_str(&self) -> &str {
match &self.inner {
FastStringInner::Arc(s) => s,
FastStringInner::Owned(s) => s,
FastStringInner::Static(s) => s,
FastStringInner::StaticAscii(s) => s,
FastStringInner::StaticConst(s) => s.as_str(),
}
}
pub fn v8_string<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
) -> v8::Local<'a, v8::String> {
match self.inner {
FastStringInner::StaticAscii(s) => {
v8::String::new_external_onebyte_static(scope, s.as_bytes()).unwrap()
}
FastStringInner::StaticConst(s) => {
v8::String::new_from_onebyte_const(scope, s.s).unwrap()
}
_ => {
v8::String::new_from_utf8(scope, self.as_bytes(), NewStringType::Normal)
.unwrap()
}
}
}
pub fn truncate(&mut self, index: usize) {
match &mut self.inner {
FastStringInner::Static(b) => {
self.inner = FastStringInner::Static(&b[..index])
}
FastStringInner::StaticAscii(b) => {
self.inner = FastStringInner::StaticAscii(&b[..index])
}
FastStringInner::StaticConst(b) => {
self.inner = FastStringInner::StaticAscii(&b.as_str()[..index])
}
FastStringInner::Owned(b) => {
self.inner = FastStringInner::Owned(b[..index].to_owned().into())
}
FastStringInner::Arc(s) => {
self.inner = FastStringInner::Arc(s[..index].to_owned().into())
}
}
}
}
impl Hash for FastString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl AsRef<str> for FastString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for FastString {
fn as_ref(&self) -> &[u8] {
self.as_str().as_ref()
}
}
impl AsRef<OsStr> for FastString {
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
impl Deref for FastString {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Borrow<str> for FastString {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Debug for FastString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl Default for FastString {
fn default() -> Self {
Self {
inner: FastStringInner::StaticConst(FastStaticString::default()),
}
}
}
impl PartialEq for FastString {
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for FastString {}
impl Display for FastString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl From<FastStaticString> for FastString {
fn from(value: FastStaticString) -> Self {
Self {
inner: FastStringInner::StaticConst(value),
}
}
}
impl From<Url> for FastString {
fn from(value: Url) -> Self {
let s: String = value.into();
s.into()
}
}
impl From<String> for FastString {
fn from(value: String) -> Self {
Self {
inner: FastStringInner::Owned(value.into_boxed_str()),
}
}
}
impl From<Arc<str>> for FastString {
fn from(value: Arc<str>) -> Self {
Self {
inner: FastStringInner::Arc(value),
}
}
}
impl From<FastString> for Arc<str> {
fn from(value: FastString) -> Self {
use FastStringInner::*;
match value.inner {
Static(text) | StaticAscii(text) => text.into(),
StaticConst(text) => text.as_ref().into(),
Owned(text) => text.into(),
Arc(text) => text,
}
}
}
impl serde::Serialize for FastString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
type DeserializeProxy<'de> = &'de str;
impl<'de> serde::Deserialize<'de> for FastString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
DeserializeProxy::<'de>::deserialize(deserializer)
.map(|v| v.to_owned().into())
}
}
#[macro_export]
macro_rules! ascii_str_include {
($file:expr) => {{
const STR: $crate::v8::OneByteConst =
$crate::FastStaticString::create_external_onebyte_const(
::std::include_str!($file).as_bytes(),
);
let s: &'static $crate::v8::OneByteConst = &STR;
$crate::FastStaticString::new(s)
}};
}
#[macro_export]
macro_rules! ascii_str {
($str:expr) => {{
const C: $crate::v8::OneByteConst =
$crate::FastStaticString::create_external_onebyte_const($str.as_bytes());
$crate::FastStaticString::new(&C)
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __op_name_fast {
($op:ident) => {{
const LITERAL: &'static [u8] = stringify!($op).as_bytes();
const STR: $crate::v8::OneByteConst =
$crate::FastStaticString::create_external_onebyte_const(LITERAL);
let s: &'static $crate::v8::OneByteConst = &STR;
(stringify!($op), $crate::FastStaticString::new(s))
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_eq() {
let s: FastString = ascii_str!("Testing").into();
assert_eq!("Testing", s.as_str());
let s2 = FastString::from_static("Testing");
assert_eq!(s, s2);
let (s1, s2) = s.into_cheap_copy();
assert_eq!("Testing", s1.as_str());
assert_eq!("Testing", s2.as_str());
let s = FastString::from("Testing".to_owned());
assert_eq!("Testing", s.as_str());
let (s1, s2) = s.into_cheap_copy();
assert_eq!("Testing", s1.as_str());
assert_eq!("Testing", s2.as_str());
}
#[test]
fn truncate() {
let mut s = "123456".to_owned();
s.truncate(3);
let mut code: FastString = ascii_str!("123456").into();
code.truncate(3);
assert_eq!(s, code.as_ref());
let mut code: FastString = "123456".to_owned().into();
code.truncate(3);
assert_eq!(s, code.as_ref());
let arc_str: Arc<str> = "123456".into();
let mut code: FastString = arc_str.into();
code.truncate(3);
assert_eq!(s, code.as_ref());
}
#[test]
fn test_large_include() {
}
#[test]
fn test_const() {
const _: (&str, FastStaticString) = __op_name_fast!(op_name);
const _: FastStaticString = ascii_str!("hmm");
const _: FastStaticString = ascii_str!(concat!("hmm", "hmmmmm"));
const _: FastStaticString = ascii_str_include!("Cargo.toml");
const _: FastStaticString = ascii_str_include!(concat!("./", "Cargo.toml"));
}
}