#![allow(non_snake_case)]
use std::sync::Arc;
#[derive(Clone, Eq)]
pub struct GoString {
buf: Arc<str>,
off: u32,
len: u32,
}
const _: () = assert!(std::mem::size_of::<GoString>() == 24);
impl Default for GoString {
fn default() -> Self {
GoString { buf: Arc::from(""), off: 0, len: 0 }
}
}
impl GoString {
pub fn new() -> Self { Self::default() }
pub fn len(&self) -> i64 { self.len as i64 }
pub fn is_empty(&self) -> bool { self.len == 0 }
pub fn as_str(&self) -> &str {
let start = self.off as usize;
let end = start + self.len as usize;
&self.buf[start..end]
}
pub fn as_bytes(&self) -> &[u8] {
let start = self.off as usize;
let end = start + self.len as usize;
&self.buf.as_bytes()[start..end]
}
pub fn slice(&self, i: impl Into<i64>, j: impl Into<i64>) -> GoString {
let i = i.into();
let j = j.into();
let lo = i as u32;
let hi = j as u32;
if lo > hi || hi > self.len {
panic!("runtime error: slice bounds out of range [{}:{}] with length {}", i, j, self.len);
}
GoString {
buf: Arc::clone(&self.buf),
off: self.off + lo,
len: hi - lo,
}
}
pub fn at(&self, i: impl Into<i64>) -> u8 {
let i = i.into();
let idx = i as u32;
if idx >= self.len {
panic!("runtime error: index out of range [{}] with length {}", i, self.len);
}
self.buf.as_bytes()[(self.off + idx) as usize]
}
pub fn cat(&self, other: impl AsRef<str>) -> GoString {
let other = other.as_ref();
let mut buf = std::string::String::with_capacity(self.as_str().len() + other.len());
buf.push_str(self.as_str());
buf.push_str(other);
GoString::from(buf)
}
pub fn concat<S: AsRef<str>>(parts: &[S]) -> GoString {
let total: usize = parts.iter().map(|s| s.as_ref().len()).sum();
let mut buf = std::string::String::with_capacity(total);
for s in parts { buf.push_str(s.as_ref()); }
GoString::from(buf)
}
pub fn index(&self, sub: impl AsRef<str>) -> i64 {
self.as_str().find(sub.as_ref()).map(|i| i as i64).unwrap_or(-1)
}
pub fn has_prefix(&self, p: impl AsRef<str>) -> bool {
self.as_str().starts_with(p.as_ref())
}
pub fn has_suffix(&self, s: impl AsRef<str>) -> bool {
self.as_str().ends_with(s.as_ref())
}
pub fn String(&self) -> GoString { self.clone() }
pub fn to_bytes(&self) -> crate::_slice::slice<u8> {
crate::_slice::slice(self.as_bytes().to_vec())
}
}
impl From<&str> for GoString {
fn from(s: &str) -> Self {
let len = s.len() as u32;
GoString { buf: Arc::from(s), off: 0, len }
}
}
impl From<std::string::String> for GoString {
fn from(s: std::string::String) -> Self {
let len = s.len() as u32;
GoString { buf: Arc::from(s), off: 0, len }
}
}
impl From<&std::string::String> for GoString {
fn from(s: &std::string::String) -> Self { GoString::from(s.as_str()) }
}
impl From<Arc<str>> for GoString {
fn from(a: Arc<str>) -> Self {
let len = a.len() as u32;
GoString { buf: a, off: 0, len }
}
}
impl From<char> for GoString {
fn from(c: char) -> Self { GoString::from(c.to_string()) }
}
impl<'a> From<std::borrow::Cow<'a, str>> for GoString {
fn from(c: std::borrow::Cow<'a, str>) -> Self { GoString::from(c.into_owned()) }
}
impl From<Vec<u8>> for GoString {
fn from(v: Vec<u8>) -> Self {
match std::string::String::from_utf8(v) {
Ok(s) => GoString::from(s),
Err(e) => GoString::from(std::string::String::from_utf8_lossy(&e.into_bytes()).into_owned()),
}
}
}
impl From<&[u8]> for GoString {
fn from(b: &[u8]) -> Self {
GoString::from(std::string::String::from_utf8_lossy(b).into_owned())
}
}
impl From<crate::_slice::slice<u8>> for GoString {
fn from(s: crate::_slice::slice<u8>) -> Self { GoString::from(s.into_vec()) }
}
impl std::ops::Deref for GoString {
type Target = str;
fn deref(&self) -> &str { self.as_str() }
}
impl AsRef<str> for GoString { fn as_ref(&self) -> &str { self.as_str() } }
impl AsRef<[u8]> for GoString { fn as_ref(&self) -> &[u8] { self.as_bytes() } }
impl std::borrow::Borrow<str> for GoString {
fn borrow(&self) -> &str { self.as_str() }
}
impl PartialEq for GoString {
fn eq(&self, other: &GoString) -> bool { self.as_str() == other.as_str() }
}
impl PartialEq<str> for GoString { fn eq(&self, o: &str) -> bool { self.as_str() == o } }
impl PartialEq<&str> for GoString { fn eq(&self, o: &&str) -> bool { self.as_str() == *o } }
impl PartialEq<std::string::String> for GoString {
fn eq(&self, o: &std::string::String) -> bool { self.as_str() == o.as_str() }
}
impl PartialEq<GoString> for str {
fn eq(&self, o: &GoString) -> bool { self == o.as_str() }
}
impl PartialEq<GoString> for &str {
fn eq(&self, o: &GoString) -> bool { *self == o.as_str() }
}
impl PartialEq<GoString> for std::string::String {
fn eq(&self, o: &GoString) -> bool { self.as_str() == o.as_str() }
}
impl Ord for GoString {
fn cmp(&self, other: &GoString) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialOrd for GoString {
fn partial_cmp(&self, other: &GoString) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
}
impl std::hash::Hash for GoString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl std::ops::Index<i64> for GoString {
type Output = u8;
fn index(&self, i: i64) -> &u8 {
if i < 0 || (i as u64) >= self.len as u64 {
panic!("runtime error: index out of range [{}] with length {}", i, self.len);
}
&self.buf.as_bytes()[(self.off + i as u32) as usize]
}
}
macro_rules! impl_range_index {
($($r:ty),+ $(,)?) => { $(
impl std::ops::Index<$r> for GoString {
type Output = str;
fn index(&self, r: $r) -> &str { &self.as_str()[r] }
}
)+ };
}
impl_range_index!(
std::ops::Range<usize>,
std::ops::RangeTo<usize>,
std::ops::RangeFrom<usize>,
std::ops::RangeFull,
std::ops::RangeInclusive<usize>,
std::ops::RangeToInclusive<usize>,
);
impl std::ops::Add for GoString {
type Output = GoString;
fn add(self, rhs: GoString) -> GoString { self.cat(&rhs) }
}
impl std::ops::Add<&GoString> for GoString {
type Output = GoString;
fn add(self, rhs: &GoString) -> GoString { self.cat(rhs) }
}
impl std::ops::Add<&str> for GoString {
type Output = GoString;
fn add(self, rhs: &str) -> GoString { self.cat(rhs) }
}
impl std::ops::Add<GoString> for &str {
type Output = GoString;
fn add(self, rhs: GoString) -> GoString { GoString::from(self).cat(&rhs) }
}
impl std::ops::Add<&GoString> for &str {
type Output = GoString;
fn add(self, rhs: &GoString) -> GoString { GoString::from(self).cat(rhs) }
}
impl std::ops::AddAssign<&str> for GoString {
fn add_assign(&mut self, rhs: &str) { *self = self.cat(rhs); }
}
impl std::ops::AddAssign<GoString> for GoString {
fn add_assign(&mut self, rhs: GoString) { *self = self.cat(&rhs); }
}
impl std::ops::AddAssign<&GoString> for GoString {
fn add_assign(&mut self, rhs: &GoString) { *self = self.cat(rhs); }
}
impl std::fmt::Display for GoString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::fmt::Debug for GoString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl<'a> IntoIterator for &'a GoString {
type Item = char;
type IntoIter = std::str::Chars<'a>;
fn into_iter(self) -> Self::IntoIter { self.as_str().chars() }
}
#[macro_export]
macro_rules! cat {
($($s:expr),+ $(,)?) => {{
$crate::gostring::GoString::concat(&[$( ($s).as_ref() ),+] as &[&str])
}};
}
#[macro_export]
macro_rules! string {
($s:literal) => {{
static __GOISH_STRING: ::std::sync::OnceLock<$crate::gostring::GoString>
= ::std::sync::OnceLock::new();
__GOISH_STRING.get_or_init(|| $crate::gostring::GoString::from($s))
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_matches() {
assert_eq!(std::mem::size_of::<GoString>(), 24);
}
#[test]
fn basic_construct() {
let s: GoString = "hello".into();
assert_eq!(s.len(), 5);
assert_eq!(s.as_str(), "hello");
assert_eq!(s, "hello");
}
#[test]
fn slice_is_o1() {
let s: GoString = "hello world".into();
let sub = s.slice(6, 11);
assert_eq!(sub.as_str(), "world");
assert!(Arc::ptr_eq(&s.buf, &sub.buf)); }
#[test]
fn at_byte() {
let s: GoString = "hello".into();
assert_eq!(s.at(0), b'h');
assert_eq!(s.at(4), b'o');
}
#[test]
#[should_panic]
fn at_out_of_range_panics() {
let s: GoString = "abc".into();
let _ = s.at(5);
}
#[test]
fn cat_binary() {
let a: GoString = "hello ".into();
let b: GoString = "world".into();
assert_eq!(a.cat(&b), "hello world");
}
#[test]
fn concat_variadic() {
let a: GoString = "a".into();
let b: GoString = "b".into();
let c: GoString = "c".into();
let r = GoString::concat(&[a.as_str(), b.as_str(), c.as_str()]);
assert_eq!(r, "abc");
}
#[test]
fn compare_with_str_literal() {
let s: GoString = "x".into();
assert!(s == "x");
assert!("x" == s);
assert!(s != "y");
}
#[test]
fn add_assign_str_literal() {
let mut p: GoString = "foo".into();
p += "/";
p += "bar";
assert_eq!(p, "foo/bar");
}
#[test]
fn index_byte() {
let p: GoString = "hello".into();
assert_eq!(p[0], b'h');
assert_eq!(p[4], b'o');
let n = p.len();
let mut sum: u64 = 0;
for i in 0..n { sum += p[i] as u64; }
assert_eq!(sum, (b'h' as u64) + (b'e' as u64) + (b'l' as u64) * 2 + (b'o' as u64));
}
#[test]
#[should_panic]
fn index_out_of_range_panics() {
let p: GoString = "ab".into();
let _ = p[5];
}
#[test]
#[should_panic]
fn index_negative_panics() {
let p: GoString = "ab".into();
let _ = p[-1];
}
#[test]
fn string_macro_indexable() {
let chars = crate::string!("abcdef");
let i: i64 = 2;
assert_eq!(chars[i], b'c');
assert_eq!(chars.len(), 6);
let get = || crate::string!("cached");
let a = get();
let b = get();
assert!(std::ptr::eq(a as *const _, b as *const _));
}
#[test]
fn from_vec_u8_roundtrip() {
let bytes: Vec<u8> = b"hello".to_vec();
let s: GoString = bytes.into();
assert_eq!(s, "hello");
let back = s.to_bytes();
assert_eq!(back, vec![b'h', b'e', b'l', b'l', b'o']);
}
#[test]
fn from_byte_slice() {
let s: GoString = (&b"world"[..]).into();
assert_eq!(s, "world");
}
#[test]
fn from_slice_u8() {
let v: crate::_slice::slice<u8> = crate::_slice::slice(b"abc".to_vec());
let s: GoString = v.into();
assert_eq!(s, "abc");
}
#[test]
fn from_invalid_utf8_is_lossy() {
let bytes: Vec<u8> = vec![0xff, 0xfe, b'x'];
let s: GoString = bytes.into();
assert!(s.as_str().contains('x'));
}
#[test]
fn str_plus_gostring() {
let p: GoString = "foo".into();
let r: GoString = "/" + p.clone();
assert_eq!(r, "/foo");
let r: GoString = "/" + &p;
assert_eq!(r, "/foo");
}
#[test]
fn add_assign_gostring() {
let mut p: GoString = "a".into();
let b: GoString = "b".into();
p += &b;
p += b;
assert_eq!(p, "abb");
}
#[test]
fn canonical_url_path() {
fn Canonical(p: GoString) -> GoString {
if p.is_empty() || p.at(0) != b'/' {
return GoString::from("/").cat(&p);
}
if &*p != "/" && p.has_suffix("/") {
return p.slice(0, p.len() - 1);
}
p
}
assert_eq!(Canonical("".into()), "/");
assert_eq!(Canonical("/a".into()), "/a");
assert_eq!(Canonical("a".into()), "/a");
assert_eq!(Canonical("/a/".into()),"/a");
assert_eq!(Canonical("/".into()), "/");
assert_eq!(Canonical("//".into()), "/");
}
}