Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/astr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ rust-version.workspace = true

[dependencies]
diesel.workspace = true
stable_deref_trait = "1.2.1"
triomphe.workspace = true

[lints]
Expand Down
84 changes: 73 additions & 11 deletions crates/astr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
use std::{
borrow::{Borrow, Cow},
fmt,
hash::Hash,
ops::Deref,
path::Path,
};

use stable_deref_trait::StableDeref;
use triomphe::{Arc, HeaderWithLength};

mod diesel;

/// String 'atom'.
///
/// Cloning doesn't allocate. As of the time of writing, uses reference
/// counting. Implementation may change.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AStr(triomphe::Arc<str>);
#[derive(Clone)]
pub struct AStr(triomphe::ThinArc<(), u8>);

impl AStr {
#[inline]
pub fn as_str(&self) -> &str {
&self.0
// SAFETY: We only ever store UTF-8,
// would use ThinArc<(), str> if possible
unsafe { str::from_utf8_unchecked(&self.0.slice) }
}
}

Expand All @@ -29,33 +36,39 @@ impl Default for AStr {
impl Deref for AStr {
type Target = str;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
self.as_str()
}
}

unsafe impl StableDeref for AStr {}

impl Borrow<str> for AStr {
#[inline]
fn borrow(&self) -> &str {
&self.0
self.as_str()
}
}

impl fmt::Debug for AStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
self.as_str().fmt(f)
}
}

impl fmt::Display for AStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
self.as_str().fmt(f)
}
}

impl From<&str> for AStr {
#[inline]
fn from(value: &str) -> Self {
Self(value.into())
Self(Arc::into_thin(Arc::from_header_and_slice(
HeaderWithLength::new((), value.len()),
value.as_bytes(),
)))
}
}

Expand All @@ -69,7 +82,7 @@ impl From<&AStr> for AStr {
impl From<String> for AStr {
#[inline]
fn from(value: String) -> Self {
Self(value.into())
Self::from(value.as_str())
}
}

Expand All @@ -88,13 +101,62 @@ impl<'a> From<&'a AStr> for Cow<'a, str> {
}

impl AsRef<str> for AStr {
#[inline]
fn as_ref(&self) -> &str {
&self.0
self.as_str()
}
}

impl AsRef<Path> for AStr {
#[inline]
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}

impl PartialEq for AStr {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}

impl Eq for AStr {}

impl PartialOrd for AStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for AStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}

impl Hash for AStr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}

#[cfg(test)]
mod tests {
use super::AStr;

#[test]
fn basic_use() {
let empty = AStr::from("");
let empty2 = empty.clone();
assert_eq!(format!("{empty}{empty2}{empty}"), "");

let x = AStr::from("x");
assert_eq!(format!("{x}x{x}"), "xxx");
}

#[test]
fn long_string() {
let foo = AStr::from("/foo/bar/helloworld");
assert_eq!(foo.as_str(), "/foo/bar/helloworld");
}
}
2 changes: 2 additions & 0 deletions crates/vfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition.workspace = true

[dependencies]
astr.workspace = true
derive_more.workspace = true
elsa = "1.11.2"
indextree.workspace = true
snafu.workspace = true

Expand Down
59 changes: 56 additions & 3 deletions crates/vfs/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::ops::Deref;

use astr::AStr;
use derive_more::Debug;

pub fn join(a: &str, b: impl AsRef<str> + Into<AStr>) -> AStr {
let b_ = b.as_ref();
Expand All @@ -11,16 +14,66 @@ pub fn join(a: &str, b: impl AsRef<str> + Into<AStr>) -> AStr {
}
}

pub fn file_name(path: &str) -> Option<&str> {
path.trim_end_matches('/').rsplit('/').next()
#[derive(Clone, Debug)]
#[debug("{path:?}")]
pub struct VfsPath {
path: AStr,
file_name_start_idx: u32,
parent_end_idx: u32,
}

impl VfsPath {
pub fn new(path: AStr) -> Self {
assert!(path.starts_with('/'));
if path.len() > 1 {
assert!(!path.ends_with('/'));
}

let file_name_start_idx = (path.rfind('/').unwrap() + 1).try_into().unwrap();
let parent_end_idx = if file_name_start_idx == 1 {
1
} else {
file_name_start_idx - 1
};
Self {
path,
file_name_start_idx,
parent_end_idx,
}
}

pub fn astr(&self) -> AStr {
self.path.clone()
}

pub fn file_name(&self) -> &str {
&self.path[self.file_name_start_idx as usize..]
}

pub fn parent(&self) -> Option<&str> {
(self.path.len() > 1).then(|| &self.path[..self.parent_end_idx as usize])
}
}

impl Deref for VfsPath {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.path
}
}

/*pub fn file_name(path: &str) -> Option<&str> {
let (_, file_name) = path.trim_end_matches('/').rsplit_once('/')?;
Some(file_name)
}

pub fn parent(path: &str) -> Option<&str> {
path.trim_end_matches('/').rsplit_once('/').map(|(parent, _)| {
// We had to have split on a direct descendent of `/`
if parent.is_empty() { "/" } else { parent }
})
}
}*/

pub fn components(path: &str) -> impl Iterator<Item = &str> {
path.starts_with('/')
Expand Down
Loading
Loading