Compare commits

...

3 Commits

Author SHA1 Message Date
5b0e51a127
ArcStr can now store purely static strings.
Added several tests to `ArcStr`.
2025-10-06 19:51:56 +01:00
5176d20ab7
ArcStr now implements From<&str>. 2025-10-06 18:50:08 +01:00
5a2c251b9b
Fixed panic when single-line comment didn't have a newline afterwards.
Added some tests for the ast.
2025-10-06 18:41:25 +01:00
3 changed files with 206 additions and 51 deletions

View File

@ -21,6 +21,7 @@
"tempdir",
"thiserror",
"yeet",
"yokeable",
"Zbuild",
"zutil"
],

View File

@ -14,7 +14,7 @@ use {
};
/// Zbuild ast
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Ast {
/// Aliases
pub aliases: Vec<AliasStmt>,
@ -61,7 +61,7 @@ impl Parsable for Ast {
}
/// Alias statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct AliasStmt {
/// Alias name
pub name: Ident,
@ -83,7 +83,7 @@ impl Parsable for AliasStmt {
}
/// Pattern statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct PatStmt {
/// Pattern name
pub name: Ident,
@ -107,7 +107,7 @@ impl Parsable for PatStmt {
}
/// Default statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DefaultStmt {
/// Default
pub default: Expr,
@ -124,7 +124,7 @@ impl Parsable for DefaultStmt {
}
/// Rule statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct RuleStmt {
/// Rule name
pub name: Ident,
@ -190,7 +190,7 @@ impl Parsable for RuleStmt {
}
/// Dependency statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DepStmt {
/// File
File(Expr),
@ -214,7 +214,7 @@ impl Parsable for DepStmt {
}
/// Command
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Command {
/// Working directory
pub cwd: Option<Expr>,
@ -274,7 +274,7 @@ impl Parsable for Command {
}
/// Include statement
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct IncludeStmt {
/// Path
pub path: StringLiteral,
@ -291,7 +291,7 @@ impl Parsable for IncludeStmt {
}
/// Array
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Array<T>(pub Vec<T>);
impl<T> Parsable for Array<T>
@ -334,7 +334,7 @@ where
}
/// Expression
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Expr {
/// Dependencies file
pub is_deps_file: bool,
@ -378,7 +378,7 @@ impl Parsable for Expr {
}
/// Expression component
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ExprCmpt {
/// Identifier
Ident { ident: Ident, ops: Vec<ExprOp> },
@ -442,14 +442,14 @@ impl ExprCmpt {
}
/// Expression operators
#[derive(Clone, Copy, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ExprOp {
/// Directory name, `.dir_name`.
DirName,
}
/// Identifier
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Ident(pub ArcStr);
impl Parsable for Ident {
@ -466,7 +466,7 @@ impl Parsable for Ident {
}
/// String literal
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct StringLiteral(ArcStr);
impl Parsable for StringLiteral {
@ -543,7 +543,7 @@ pub trait Parsable: Sized {
}
/// Parser
#[derive(Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Parser {
/// Input
input: ArcStr,
@ -656,8 +656,10 @@ impl Parser {
let end_idx = rest.find("###").context("Expected `###` after `###`")?;
Ok(&rest[end_idx + 3..])
} else if let Some(rest) = remaining.strip_prefix('#') {
let end_idx = rest.find('\n').unwrap_or(rest.len());
Ok(&rest[end_idx + 1..])
match rest.find('\n') {
Some(end_idx) => Ok(&rest[end_idx + 1..]),
None => Ok(&rest[rest.len()..]),
}
} else {
Ok::<_, AppError>(remaining)
}
@ -788,3 +790,32 @@ pub fn parse(path: &Path) -> Result<Ast, AppError> {
Ok(ast)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_ast() {
const EMPTY_AST: Ast = Ast {
aliases: vec![],
pats: vec![],
defaults: vec![],
rules: vec![],
includes: vec![],
};
let cases = [
("", EMPTY_AST),
("#Comment\n", EMPTY_AST),
("#Comment", EMPTY_AST),
("###Comment###", EMPTY_AST),
];
for (input, expected_ast) in cases {
let mut parser = Parser::new(input.into());
let ast = Ast::parse_from(&mut parser)
.unwrap_or_else(|err| panic!("Unable to parse input {input:?}: {}", err.pretty()));
assert_eq!(ast, expected_ast, "Ast differed for {input:?}");
}
}
}

View File

@ -27,16 +27,28 @@ use {
pub struct ArcStr {
/// Inner
// Note: We need an `Arc<String>` for efficient conversion to/from `String`
inner: Yoke<&'static str, Arc<String>>,
inner: Yoke<&'static str, Option<Arc<String>>>,
}
impl ArcStr {
/// Creates a new arc string from a static string
pub const fn from_static(s: &'static str) -> Self {
Self {
inner: Yoke::new_owned(s),
}
}
/// Returns the range of this string compared to the base
fn base_range(&self) -> Range<usize> {
self.inner
.backing_cart()
.substr_range(self)
.expect("String pointer should be within allocation")
match self.inner.backing_cart() {
// If we're backed by anything, check in relation to it
Some(inner) => inner
.substr_range(self)
.expect("String pointer should be within allocation"),
// Otherwise, we're all of ourselves
None => 0..self.len(),
}
}
/// Updates this string as a `&mut String`.
@ -50,29 +62,37 @@ impl ArcStr {
let range = self.base_range();
// Get the inner string
let mut inner = mem::take(self).inner.into_backing_cart();
let s = match Arc::get_mut(&mut inner) {
// If we're unique, slice the parts we don't care about and return
Some(s) => {
s.truncate(range.end);
let _ = s.drain(..range.start);
s
let inner = mem::take(self).inner;
let output;
*self = match inner.try_into_yokeable() {
Ok(s) => {
let mut s = s.to_owned();
output = f(&mut s);
Self::from(s)
},
Err(inner) => {
// TODO: Get rid of this panic here?
let mut inner = inner.into_backing_cart().expect("Should have a cart");
let s = match Arc::get_mut(&mut inner) {
// If we're unique, slice the parts we don't care about and return
Some(s) => {
s.truncate(range.end);
let _ = s.drain(..range.start);
// Otherwise copy
None => {
inner = Arc::new(inner[range].to_owned());
Arc::get_mut(&mut inner).expect("Should be unique")
s
},
// Otherwise copy
None => {
inner = Arc::new(inner[range].to_owned());
Arc::get_mut(&mut inner).expect("Should be unique")
},
};
output = f(s);
Self::from(inner)
},
};
// Then mutate
let output = f(s);
// And finally, reconstruct ourselves
*self = Self::from(inner);
output
}
@ -163,6 +183,12 @@ impl Borrow<str> for ArcStr {
}
}
impl From<&'_ str> for ArcStr {
fn from(s: &str) -> Self {
Self::from(s.to_owned())
}
}
impl From<String> for ArcStr {
fn from(s: String) -> Self {
Self::from(Arc::new(s))
@ -172,7 +198,7 @@ impl From<String> for ArcStr {
impl From<Arc<String>> for ArcStr {
fn from(s: Arc<String>) -> Self {
Self {
inner: Yoke::attach_to_cart(s, |s| &**s),
inner: Yoke::attach_to_cart(s, |s| &**s).wrap_cart_in_option(),
}
}
}
@ -182,18 +208,115 @@ impl From<ArcStr> for String {
// Get the range of our specific string
let range = s.base_range();
let inner = s.inner.into_backing_cart();
match Arc::try_unwrap(inner) {
// If we're unique, slice the parts we don't care about and return
Ok(mut inner) => {
inner.truncate(range.end);
let _ = inner.drain(..range.start);
match s.inner.try_into_yokeable() {
// If we're not backed by anything, just extend the string
Ok(s) => s.to_owned(),
inner
// Otherwise, wrap it
Err(inner) => match Arc::try_unwrap(inner.into_backing_cart().expect("Should have a cart")) {
// If we're unique, slice the parts we don't care about and return
Ok(mut inner) => {
inner.truncate(range.end);
let _ = inner.drain(..range.start);
inner
},
// Otherwise copy
Err(inner) => inner[range].to_owned(),
},
// Otherwise copy
Err(inner) => inner[range].to_owned(),
}
}
}
#[cfg(test)]
mod tests {
use {super::*, std::collections::HashSet};
#[test]
fn from_static() {
let mut s = ArcStr::from_static("abc");
assert_eq!(&*s, "abc");
assert_eq!(&*s.slice(1..2), "b");
assert_eq!(&*s.slice_from_str(&s[1..2]), "b");
s.with_mut(|s| s.push('d'));
assert_eq!(&*s, "abcd");
}
#[test]
fn mutate() {
let mut s = ArcStr::from("abc");
s.with_mut(|s| s.push_str("def"));
assert_eq!(&*s, "abcdef");
let s2 = s.clone();
s.with_mut(|s| s.push_str("ghi"));
assert_eq!(&*s, "abcdefghi");
assert_eq!(&*s2, "abcdef");
let output = s.with_mut(|_s| 123);
assert_eq!(output, 123);
let output = ArcStr::from_static("abc").with_mut(|_s| 456);
assert_eq!(output, 456);
}
#[test]
fn strip() {
let s = ArcStr::from("abc");
assert_eq!(s.strip_prefix("ab").as_deref(), Some("c"));
assert_eq!(s.strip_prefix("ac").as_deref(), None);
assert_eq!(s.strip_suffix("bc").as_deref(), Some("a"));
assert_eq!(s.strip_suffix("ac").as_deref(), None);
}
#[test]
fn eq_cmp() {
let a = ArcStr::from("a");
let b = ArcStr::from("b");
assert_eq!(a, ArcStr::from("a"));
assert_ne!(a, b);
assert!(b > a);
}
#[test]
fn hash() {
let s = ArcStr::from("abc");
let mut h = HashSet::new();
assert!(h.insert(s.clone()));
assert!(!h.insert(s));
assert!(!h.insert(ArcStr::from("abc")));
}
#[test]
fn to_string() {
assert_eq!(String::from(ArcStr::from_static("abc")), "abc");
let mut s = ArcStr::from("abc");
assert_eq!(String::from(s.clone()), "abc");
s.with_mut(|s| s.push('d'));
assert_eq!(String::from(s), "abcd");
}
#[test]
fn borrow() {
let s = ArcStr::from("abc");
assert_eq!(Borrow::<str>::borrow(&s), "abc");
}
#[test]
fn fmt() {
let s = ArcStr::from("abc");
let mut output = String::new();
fmt::write(&mut output, format_args!("{s}")).expect("Unable to write");
assert_eq!(output, "abc");
output.clear();
fmt::write(&mut output, format_args!("{s:?}")).expect("Unable to write");
assert_eq!(output, r#""abc""#);
}
}