Replaced CowStr with ArcStr.

This is a reference-counted string with efficient slicing, cloning and conversion to/from `String`.
This commit is contained in:
Filipe Rodrigues 2024-08-25 17:21:00 +01:00
parent 296136f0fd
commit 00ad444194
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
15 changed files with 409 additions and 161 deletions

View File

@ -31,10 +31,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[lints]
# This project doesn't require unsafe code
# Note: Although we don't use unsafe code, if we do in the future, `unsafe_op_in_unsafe_fn`
# should be denied, so we deny it already, despite it never triggering due to forbidding `rust.unsafe_code`
rust.unsafe_code = "forbid"
# This project doesn't require unsafe code (outside of some modules)
rust.unsafe_code = "deny"
rust.unsafe_op_in_unsafe_fn = "deny"
# Group lints

View File

@ -17,7 +17,7 @@ use {
error::ResultMultiple,
expand,
rules::{Command, CommandArg, DepItem, Expr, OutItem, Rule, Target},
util::{self, CowStr},
util::{self, ArcStr},
AppError,
Expander,
Rules,
@ -36,10 +36,10 @@ pub enum Event {
/// Target dependency built
TargetDepBuilt {
/// Target that was being built
target: Target<CowStr>,
target: Target<ArcStr>,
/// Dependency that was built
dep: Target<CowStr>,
dep: Target<ArcStr>,
},
}
@ -47,10 +47,10 @@ pub enum Event {
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub struct TargetRule {
/// Name
name: &'static str,
name: ArcStr,
/// Patterns
pats: Arc<BTreeMap<CowStr, CowStr>>,
pats: Arc<BTreeMap<ArcStr, ArcStr>>,
}
/// Builder
@ -95,7 +95,7 @@ impl Builder {
}
/// Returns all build results
pub fn into_build_results(self) -> IndexMap<&'static str, Option<Result<BuildResult, ()>>> {
pub fn into_build_results(self) -> IndexMap<ArcStr, Option<Result<BuildResult, ()>>> {
self.rules_lock
.into_iter()
.map(|(rule, lock)| (rule.name, lock.into_res()))
@ -124,15 +124,18 @@ impl Builder {
/// Finds a target's rule
fn target_rule(
&self,
target: &Target<CowStr>,
target: &Target<ArcStr>,
rules: &Rules,
) -> Result<Option<(Rule<CowStr>, TargetRule)>, AppError> {
) -> Result<Option<(Rule<ArcStr>, TargetRule)>, AppError> {
let target_rule = match *target {
// If we got a file, check which rule can make it
Target::File { ref file, .. } => match self.find_rule_for_file(file, rules)? {
Some((rule, pats)) => {
tracing::trace!(%target, %rule.name, "Found target rule");
let target_rule = TargetRule { name: rule.name, pats };
let target_rule = TargetRule {
name: rule.name.clone(),
pats,
};
(rule, target_rule)
},
@ -149,9 +152,9 @@ impl Builder {
let rule = self
.expander
.expand_rule(rule, &expand_visitor)
.map_err(AppError::expand_rule(rule.name))?;
.map_err(AppError::expand_rule(&*rule.name))?;
let target_rule = TargetRule {
name: rule.name,
name: rule.name.clone(),
pats: Arc::clone(pats),
};
(rule, target_rule)
@ -162,7 +165,7 @@ impl Builder {
}
/// Resets a build
pub async fn reset_build(&self, target: &Target<CowStr>, rules: &Rules) -> Result<(), AppError> {
pub async fn reset_build(&self, target: &Target<ArcStr>, rules: &Rules) -> Result<(), AppError> {
// Get the rule for the target
let Some((_, target_rule)) = self.target_rule(target, rules)? else {
return Ok(());
@ -204,7 +207,7 @@ impl Builder {
/// Builds a target
pub async fn build(
&self,
target: &Target<CowStr>,
target: &Target<ArcStr>,
rules: &Rules,
ignore_missing: bool,
reason: BuildReason<'_>,
@ -214,7 +217,7 @@ impl Builder {
// Normalize file paths
let target = match *target {
Target::File { ref file, is_static } => Target::File {
file: CowStr::Owned(util::normalize_path(file)),
file: util::normalize_path(file).into(),
is_static,
},
ref target @ Target::Rule { .. } => target.clone(),
@ -330,8 +333,8 @@ impl Builder {
#[async_recursion::async_recursion]
async fn build_unchecked<'reason>(
&self,
target: &Target<CowStr>,
rule: &Rule<CowStr>,
target: &Target<ArcStr>,
rule: &Rule<ArcStr>,
rules: &Rules,
ignore_missing: bool,
reason: BuildReason<'reason>,
@ -344,7 +347,7 @@ impl Builder {
enum Dep {
/// File
File {
file: CowStr,
file: ArcStr,
is_static: bool,
is_deps_file: bool,
is_output: bool,
@ -354,8 +357,8 @@ impl Builder {
/// Rule
Rule {
name: CowStr,
pats: Arc<BTreeMap<CowStr, CowStr>>,
name: ArcStr,
pats: Arc<BTreeMap<ArcStr, ArcStr>>,
},
}
@ -554,7 +557,9 @@ impl Builder {
// Then rebuild, if needed
if needs_rebuilt {
tracing::trace!(%target, ?rule.name, ?deps_last_build_time, ?rule_last_build_time, "Rebuilding target rule");
self.rebuild_rule(rule).await.map_err(AppError::build_rule(rule.name))?;
self.rebuild_rule(rule)
.await
.map_err(AppError::build_rule(&*rule.name))?;
}
// Then get the build time
@ -573,13 +578,13 @@ impl Builder {
/// Returns the latest modification date of the dependencies
async fn build_deps_file(
&self,
parent_target: &Target<CowStr>,
parent_target: &Target<ArcStr>,
deps_file: &str,
rule: &Rule<CowStr>,
rule: &Rule<ArcStr>,
rules: &Rules,
ignore_missing: bool,
reason: BuildReason<'_>,
) -> Result<Vec<(Target<CowStr>, BuildResult, Option<BuildLockDepGuard>)>, AppError> {
) -> Result<Vec<(Target<ArcStr>, BuildResult, Option<BuildLockDepGuard>)>, AppError> {
tracing::trace!(target=?parent_target, ?rule.name, ?deps_file, "Building dependencies of target rule dependency-file");
let (output, deps) = self::parse_deps_file(deps_file).await?;
@ -587,10 +592,10 @@ impl Builder {
// If there were no outputs, make sure it matches the rule name
// TODO: Seems kinda weird for it to match the rule name, but not sure how else to check this here
true =>
if output != rule.name {
if output != *rule.name {
return Err(AppError::DepFileMissingRuleName {
deps_file_path: deps_file.into(),
rule_name: rule.name.to_owned(),
rule_name: rule.name.to_string(),
dep_output: output,
});
},
@ -598,7 +603,7 @@ impl Builder {
// If there were any output, make sure the dependency file applies to one of them
false => {
let any_matches = rule.output.iter().any(|out| match out {
OutItem::File { file, .. } => file == &output,
OutItem::File { file, .. } => **file == output,
});
if !any_matches {
return Err(AppError::DepFileMissingOutputs {
@ -615,7 +620,7 @@ impl Builder {
let deps_res = deps
.into_iter()
.map(|dep| {
let dep = CowStr::Owned(util::normalize_path(&dep));
let dep = ArcStr::from(util::normalize_path(&dep));
tracing::trace!(?rule.name, ?dep, "Found rule dependency");
let dep_target = Target::File {
file: dep,
@ -646,7 +651,7 @@ impl Builder {
}
/// Rebuilds a rule
pub async fn rebuild_rule(&self, rule: &Rule<CowStr>) -> Result<(), AppError> {
pub async fn rebuild_rule(&self, rule: &Rule<ArcStr>) -> Result<(), AppError> {
// Lock the semaphore
// Note: If we locked it per-command, we could exit earlier
// when closed, but that would break some executions.
@ -660,7 +665,7 @@ impl Builder {
};
for cmd in &rule.exec.cmds {
self.exec_cmd(rule.name, cmd).await?;
self.exec_cmd(&rule.name, cmd).await?;
}
Ok(())
@ -670,7 +675,7 @@ impl Builder {
#[expect(unused_results, reason = "Due to the builder pattern of `Command`")]
#[expect(clippy::unused_self, reason = "It might be used in the future")]
#[async_recursion::async_recursion]
async fn exec_cmd(&self, rule_name: &str, cmd: &Command<CowStr>) -> Result<(), AppError> {
async fn exec_cmd(&self, rule_name: &str, cmd: &Command<ArcStr>) -> Result<(), AppError> {
// Process all arguments
let args = cmd
.args
@ -729,9 +734,9 @@ impl Builder {
#[expect(clippy::type_complexity, reason = "TODO: Add some type aliases / struct")]
pub fn find_rule_for_file(
&self,
file: &str,
file: &ArcStr,
rules: &Rules,
) -> Result<Option<(Rule<CowStr>, Arc<BTreeMap<CowStr, CowStr>>)>, AppError> {
) -> Result<Option<(Rule<ArcStr>, Arc<BTreeMap<ArcStr, ArcStr>>)>, AppError> {
for rule in rules.rules.values() {
for output in &rule.output {
// Expand all expressions in the output file
@ -744,14 +749,14 @@ impl Builder {
let output_file = self.expander.expand_expr(output_file, &expand_visitor)?;
// Then try to match the output file to the file we need to create
if let Some(rule_pats) = self::match_expr(&output_file, &output_file.cmpts, file)? {
if let Some(rule_pats) = self::match_expr(&output_file, &output_file.cmpts, file.clone())? {
let rule_pats = Arc::new(rule_pats);
let expand_visitor = expand::Visitor::new([&rule.aliases, &rules.aliases], [&rule_pats]);
let rule = self
.expander
.expand_rule(rule, &expand_visitor)
.map_err(AppError::expand_rule(rule.name.to_owned()))?;
.map_err(AppError::expand_rule(&*rule.name))?;
return Ok(Some((rule, rule_pats)));
}
}
@ -782,7 +787,7 @@ async fn parse_deps_file(file: &str) -> Result<(String, Vec<String>), AppError>
///
/// Returns `Err` if any files didn't exist,
/// Returns `Ok(None)` if rule has no outputs
async fn rule_last_build_time(rule: &Rule<CowStr>) -> Result<Option<SystemTime>, AppError> {
async fn rule_last_build_time(rule: &Rule<ArcStr>) -> Result<Option<SystemTime>, AppError> {
// Note: We get the time of the oldest file in order to ensure all
// files are at-least that old
let built_time = rule
@ -813,7 +818,7 @@ async fn rule_last_build_time(rule: &Rule<CowStr>) -> Result<Option<SystemTime>,
#[derive(Clone, Copy, Debug)]
pub struct BuildReasonInner<'a> {
/// Target
target: &'a Target<CowStr>,
target: &'a Target<ArcStr>,
/// Previous reason
prev: &'a BuildReason<'a>,
@ -830,14 +835,14 @@ impl BuildReason<'_> {
}
/// Adds a target to this build reason
pub const fn with_target<'a>(&'a self, target: &'a Target<CowStr>) -> BuildReason<'a> {
pub const fn with_target<'a>(&'a self, target: &'a Target<ArcStr>) -> BuildReason<'a> {
BuildReason(Some(BuildReasonInner { target, prev: self }))
}
/// Iterates over all reasons
pub fn for_each<F, R>(&self, mut f: F) -> R
where
F: FnMut(&Target<CowStr>) -> R,
F: FnMut(&Target<ArcStr>) -> R,
R: Try<Output = ()>,
{
let mut reason = &self.0;
@ -850,7 +855,7 @@ impl BuildReason<'_> {
}
/// Collects all reasons
pub fn collect_all(&self) -> Vec<&Target<CowStr>> {
pub fn collect_all(&self) -> Vec<&Target<ArcStr>> {
let mut targets = vec![];
let mut reason = &self.0;

View File

@ -4,7 +4,7 @@
use {
crate::{
rules::{Expr, ExprCmpt, PatternOp},
util::CowStr,
util::ArcStr,
AppError,
},
std::{assert_matches::assert_matches, collections::BTreeMap},
@ -18,8 +18,8 @@ use {
pub fn match_expr(
expr: &Expr,
cmpts: &[ExprCmpt],
mut value: &str,
) -> Result<Option<BTreeMap<CowStr, CowStr>>, AppError> {
mut value: ArcStr,
) -> Result<Option<BTreeMap<ArcStr, ArcStr>>, AppError> {
let mut patterns = BTreeMap::new();
// Until `rhs` has anything to match left
@ -65,10 +65,10 @@ pub fn match_expr(
// If we get here, match everything
// TODO: Borrow some cases?
let prev_value = patterns.insert(CowStr::Borrowed(pat.name), CowStr::Owned(value.to_owned()));
let prev_value = patterns.insert(pat.name.clone(), value);
assert_matches!(prev_value, None, "Found repeated pattern");
cur_cmpts = &[];
value = "";
value = ArcStr::default();
},
// If we have patterns on both sides, reject
@ -82,7 +82,7 @@ pub fn match_expr(
unreachable!("Cannot match unexpanded alias: {alias:?}"),
// If we're empty, we match an empty string
[] => match value {
[] => match &*value {
"" => return Ok(Some(patterns)),
_ => return Ok(None),
},

View File

@ -5,11 +5,11 @@ use {
crate::{
error::{AppError, ResultMultiple},
rules::{AliasOp, Command, CommandArg, DepItem, Exec, Expr, ExprCmpt, OutItem, Rule, Target},
util::CowStr,
util::ArcStr,
},
indexmap::IndexMap,
smallvec::SmallVec,
std::{collections::BTreeMap, marker::PhantomData, path::PathBuf, sync::Arc},
std::{collections::BTreeMap, marker::PhantomData, mem, path::PathBuf, sync::Arc},
};
/// Expander
@ -38,7 +38,7 @@ impl Expander {
// If it's a pattern, we visit it
// Note: We don't care about the operations on patterns, those are for matching
ExprCmpt::Pattern(pat) => match visitor.visit_pat(pat.name) {
ExprCmpt::Pattern(pat) => match visitor.visit_pat(&pat.name) {
// If expanded, just replace it with a string
FlowControl::ExpandTo(value) => expr.push_str(value),
@ -46,12 +46,12 @@ impl Expander {
FlowControl::Keep => expr.push(cmpt),
FlowControl::Error =>
return Err(AppError::UnknownPattern {
pattern_name: pat.name.to_owned(),
pattern_name: pat.name.to_string(),
}),
},
// If it's an alias, we visit and then expand it
ExprCmpt::Alias(alias) => match visitor.visit_alias(alias.name) {
ExprCmpt::Alias(alias) => match visitor.visit_alias(&alias.name) {
// If expanded, check if we need to apply any operations
FlowControl::ExpandTo(alias_expr) => match alias.ops.is_empty() {
// If not, just recursively expand it
@ -66,11 +66,12 @@ impl Expander {
let value = self.expand_expr_string(alias_expr, visitor)?;
// Then apply all
let value = alias.ops.iter().try_fold(value, |value, &op| {
let s = CowStr::into_owned(value);
self.expand_alias_op(op, s)
.map(CowStr::from)
.map_err(AppError::alias_op(op))
let value = alias.ops.iter().try_fold(value, |mut value, &op| {
value
.with_mut(|s| self.expand_alias_op(op, s))
.map_err(AppError::alias_op(op))?;
Ok(value)
})?;
expr.push_str(&value);
@ -81,7 +82,7 @@ impl Expander {
FlowControl::Keep => expr.push(cmpt),
FlowControl::Error =>
return Err(AppError::UnknownAlias {
alias_name: alias.name.to_owned(),
alias_name: alias.name.to_string(),
}),
},
};
@ -91,7 +92,7 @@ impl Expander {
}
/// Expands an expression into a string
pub fn expand_expr_string(&self, expr: &Expr, visitor: &Visitor) -> Result<CowStr, AppError> {
pub fn expand_expr_string(&self, expr: &Expr, visitor: &Visitor) -> Result<ArcStr, AppError> {
self.expand_expr(expr, visitor)?
.try_into_string()
.map_err(|expr| AppError::UnresolvedAliasOrPats {
@ -101,11 +102,11 @@ impl Expander {
}
/// Expands an alias operation on the value of that alias
fn expand_alias_op(&self, op: AliasOp, value: String) -> Result<String, AppError> {
let value = match op {
fn expand_alias_op(&self, op: AliasOp, value: &mut String) -> Result<(), AppError> {
match op {
AliasOp::DirName => {
// Get the path and try to pop the last segment
let mut path = PathBuf::from(value);
let mut path = PathBuf::from(mem::take(value));
if !path.pop() {
return Err(AppError::PathParent { path });
}
@ -113,21 +114,22 @@ impl Expander {
// Then convert it back to a string
// Note: This should technically never fail, since the path was originally
// utf-8
path.into_os_string()
*value = path
.into_os_string()
.into_string()
.expect("utf-8 path was no longer utf-8 after getting dir-name")
.expect("utf-8 path was no longer utf-8 after getting dir-name");
},
};
Ok(value)
Ok(())
}
/// Expands a rule of all it's aliases and patterns
pub fn expand_rule(&self, rule: &Rule<Expr>, visitor: &Visitor) -> Result<Rule<CowStr>, AppError> {
pub fn expand_rule(&self, rule: &Rule<Expr>, visitor: &Visitor) -> Result<Rule<ArcStr>, AppError> {
let aliases = rule
.aliases
.iter()
.map(|(&name, expr)| Ok((name, self.expand_expr_string(expr, visitor)?)))
.map(|(name, expr)| Ok((name.clone(), self.expand_expr_string(expr, visitor)?)))
.collect::<ResultMultiple<_>>()?;
let output = rule
@ -184,7 +186,7 @@ impl Expander {
};
Ok(Rule {
name: rule.name,
name: rule.name.clone(),
aliases: Arc::new(aliases),
output,
deps,
@ -193,7 +195,7 @@ impl Expander {
}
/// Expands a command
pub fn expand_cmd(&self, cmd: &Command<Expr>, visitor: &Visitor) -> Result<Command<CowStr>, AppError> {
pub fn expand_cmd(&self, cmd: &Command<Expr>, visitor: &Visitor) -> Result<Command<ArcStr>, AppError> {
Ok(Command {
cwd: cmd
.cwd
@ -214,7 +216,7 @@ impl Expander {
}
/// Expands a target expression
pub fn expand_target(&self, target: &Target<Expr>, visitor: &Visitor) -> Result<Target<CowStr>, AppError> {
pub fn expand_target(&self, target: &Target<Expr>, visitor: &Visitor) -> Result<Target<ArcStr>, AppError> {
let target = match *target {
Target::File { ref file, is_static } => Target::File {
file: self
@ -275,24 +277,24 @@ impl<T> FlowControl<T> {
#[derive(Clone, Debug)]
pub struct Visitor {
/// All aliases, in order to check
aliases: SmallVec<[Arc<IndexMap<&'static str, Expr>>; 2]>,
aliases: SmallVec<[Arc<IndexMap<ArcStr, Expr>>; 2]>,
/// All patterns, in order to check
pats: SmallVec<[Arc<BTreeMap<CowStr, CowStr>>; 1]>,
pats: SmallVec<[Arc<BTreeMap<ArcStr, ArcStr>>; 1]>,
/// Default alias action
default_alias: FlowControl<Expr>,
/// Default pattern action
default_pat: FlowControl<CowStr>,
default_pat: FlowControl<ArcStr>,
}
impl Visitor {
/// Creates a new visitor with aliases and patterns
pub fn new<'a, A, P>(aliases: A, pats: P) -> Self
where
A: IntoIterator<Item = &'a Arc<IndexMap<&'static str, Expr>>>,
P: IntoIterator<Item = &'a Arc<BTreeMap<CowStr, CowStr>>>,
A: IntoIterator<Item = &'a Arc<IndexMap<ArcStr, Expr>>>,
P: IntoIterator<Item = &'a Arc<BTreeMap<ArcStr, ArcStr>>>,
{
Self {
aliases: aliases.into_iter().map(Arc::clone).collect(),
@ -305,13 +307,13 @@ impl Visitor {
/// Creates a visitor from aliases
pub fn from_aliases<'a, A>(aliases: A) -> Self
where
A: IntoIterator<Item = &'a Arc<IndexMap<&'static str, Expr>>>,
A: IntoIterator<Item = &'a Arc<IndexMap<ArcStr, Expr>>>,
{
Self::new(aliases, [])
}
/// Sets the default pattern
pub fn with_default_pat(self, default_pat: FlowControl<CowStr>) -> Self {
pub fn with_default_pat(self, default_pat: FlowControl<ArcStr>) -> Self {
Self { default_pat, ..self }
}
@ -327,7 +329,7 @@ impl Visitor {
}
/// Visits a pattern
fn visit_pat(&self, pat_name: &str) -> FlowControl<&CowStr> {
fn visit_pat(&self, pat_name: &str) -> FlowControl<&ArcStr> {
for pats in &self.pats {
if let Some(pat) = pats.get(pat_name) {
return FlowControl::ExpandTo(pat);

View File

@ -12,7 +12,11 @@
strict_provenance,
assert_matches,
try_trait_v2,
if_let_guard
if_let_guard,
pattern,
unsigned_signed_diff,
vec_into_raw_parts,
ptr_metadata
)]
// Lints
#![allow(
@ -55,7 +59,7 @@ use {
thread,
time::{Duration, SystemTime},
},
util::CowStr,
util::ArcStr,
watcher::Watcher,
};
@ -84,13 +88,13 @@ async fn main() -> ExitResult {
// Parse the ast
let zbuild_file = fs::read_to_string(zbuild_path).map_err(AppError::read_file(&zbuild_path))?;
let zbuild_file = zbuild_file.leak();
let zbuild_file = ArcStr::from(zbuild_file);
tracing::trace!(?zbuild_file, "Read zbuild.yaml");
let ast = serde_yaml::from_str::<Ast<'_>>(zbuild_file).map_err(AppError::parse_yaml(&zbuild_path))?;
let ast = serde_yaml::from_str::<Ast<'_>>(&zbuild_file).map_err(AppError::parse_yaml(&zbuild_path))?;
tracing::trace!(?ast, "Parsed ast");
// Build the rules
let rules = Rules::new(ast);
let rules = Rules::new(&zbuild_file, ast);
tracing::trace!(?rules, "Built rules");
// Get the max number of jobs we can execute at once
@ -130,7 +134,7 @@ async fn main() -> ExitResult {
// If there was a rule, use it without any patterns
// TODO: If it requires patterns maybe error out here?
|rule| rules::Target::Rule {
rule: rules::Expr::string(rule.name.to_owned()),
rule: rules::Expr::string(rule.name.clone()),
pats: Arc::new(BTreeMap::new()),
},
)
@ -288,7 +292,7 @@ impl BuildableTargetInner for rules::Expr {
}
}
impl BuildableTargetInner for CowStr {
impl BuildableTargetInner for ArcStr {
async fn build(
target: &rules::Target<Self>,
builder: &Builder,

View File

@ -19,7 +19,11 @@ pub use {
};
// Imports
use {crate::Ast, indexmap::IndexMap, std::sync::Arc};
use {
crate::{util::ArcStr, Ast},
indexmap::IndexMap,
std::sync::Arc,
};
/// Rules.
///
@ -31,29 +35,36 @@ pub struct Rules {
///
/// These are available for the whole program to
/// use.
pub aliases: Arc<IndexMap<&'static str, Expr>>,
pub aliases: Arc<IndexMap<ArcStr, Expr>>,
/// Default targets to build
pub default: Vec<Target<Expr>>,
/// Rules
pub rules: IndexMap<&'static str, Rule<Expr>>,
pub rules: IndexMap<ArcStr, Rule<Expr>>,
}
impl Rules {
/// Creates all rules from the ast
#[must_use]
pub fn new(ast: Ast<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, ast: Ast<'_>) -> Self {
let aliases = ast
.aliases
.into_iter()
.map(|(alias, value)| (alias, Expr::new(value)))
.map(|(alias, value)| (zbuild_file.slice_from_str(alias), Expr::new(zbuild_file, value)))
.collect();
let default = ast
.default
.into_iter()
.map(|target| Target::new(zbuild_file, target))
.collect();
let default = ast.default.into_iter().map(Target::new).collect();
let rules = ast
.rules
.into_iter()
.map(|(name, rule)| (name, Rule::new(name, rule)))
.map(|(name, rule)| {
let name = zbuild_file.slice_from_str(name);
(name.clone(), Rule::new(zbuild_file, name, rule))
})
.collect();
Self {

View File

@ -1,13 +1,13 @@
//! Pattern
// Imports
use std::fmt;
use {crate::util::ArcStr, std::fmt};
/// Alias
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
pub struct Alias {
/// Alias name
pub name: &'static str,
pub name: ArcStr,
/// Operators
pub ops: Vec<AliasOp>,

View File

@ -6,7 +6,7 @@ use {
alias::{Alias, AliasOp},
pattern::{Pattern, PatternOp},
},
crate::{ast, util::CowStr},
crate::{ast, util::ArcStr},
std::fmt,
};
@ -14,7 +14,7 @@ use {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
pub enum ExprCmpt {
/// String
String(CowStr),
String(ArcStr),
/// Pattern
Pattern(Pattern),
@ -25,7 +25,7 @@ pub enum ExprCmpt {
impl ExprCmpt {
/// Converts this expression into a string, if it's a string.
pub fn try_into_string(self) -> Result<CowStr, Self> {
pub fn try_into_string(self) -> Result<ArcStr, Self> {
#[expect(clippy::wildcard_enum_match_arm, reason = "We only care about a specific variant")]
match self {
Self::String(v) => Ok(v),
@ -49,24 +49,20 @@ impl Expr {
}
/// Pushes a string into this expression
pub fn push_str(&mut self, s: &CowStr) {
pub fn push_str(&mut self, s: &ArcStr) {
match self.cmpts.last_mut() {
Some(ExprCmpt::String(last)) => {
last.to_mut().push_str(s);
},
Some(ExprCmpt::String(last)) => last.with_mut(|last| last.push_str(s)),
_ => self.cmpts.push(ExprCmpt::String(s.clone())),
}
}
/// Pushes a component into this expression
pub fn push(&mut self, cmpt: &ExprCmpt) {
#[expect(clippy::wildcard_enum_match_arm, reason = "The wildcard matches all variants")]
match cmpt {
// If it's a string, try to use `push_str` for merging strings.
ExprCmpt::String(s) if let Some(ExprCmpt::String(last)) = self.cmpts.last_mut() =>
last.to_mut().push_str(s),
ExprCmpt::String(s) => self.push_str(s),
cmpt => self.cmpts.push(cmpt.clone()),
cmpt @ (ExprCmpt::Alias(_) | ExprCmpt::Pattern(_)) => self.cmpts.push(cmpt.clone()),
}
}
@ -84,7 +80,7 @@ impl Expr {
}
/// Converts this expression into a string, if it's compromised of only string components
pub fn try_into_string(self) -> Result<CowStr, Self> {
pub fn try_into_string(self) -> Result<ArcStr, Self> {
// If all components aren't strings, return Err
if !self.cmpts.iter().all(|cmpt| matches!(cmpt, ExprCmpt::String(_))) {
return Err(self);
@ -101,7 +97,7 @@ impl Expr {
for cmpt in cmpts {
let cmpt = cmpt.try_into_string().expect("Component wasn't a string");
if !cmpt.is_empty() {
output.to_mut().push_str(&cmpt);
output.with_mut(|output| output.push_str(&cmpt));
}
}
@ -109,15 +105,15 @@ impl Expr {
}
/// Creates a new expression from it's ast
pub fn new(expr: ast::Expr<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, expr: ast::Expr<'_>) -> Self {
let cmpts = expr
.cmpts
.into_iter()
.map(|cmpt| match cmpt {
ast::ExprCmpt::String(s) => ExprCmpt::String(CowStr::Borrowed(s)),
ast::ExprCmpt::String(s) => ExprCmpt::String(zbuild_file.slice_from_str(s)),
ast::ExprCmpt::Pattern(ast::Pattern { name, ops }) => ExprCmpt::Pattern(Pattern {
name,
ops: ops
name: zbuild_file.slice_from_str(name),
ops: ops
.into_iter()
.map(|op| match op {
ast::PatternOp::NonEmpty => PatternOp::NonEmpty,
@ -125,8 +121,8 @@ impl Expr {
.collect(),
}),
ast::ExprCmpt::Alias(ast::Alias { name, ops }) => ExprCmpt::Alias(Alias {
name,
ops: ops
name: zbuild_file.slice_from_str(name),
ops: ops
.into_iter()
.map(|op| match op {
ast::AliasOp::DirName => AliasOp::DirName,
@ -140,7 +136,7 @@ impl Expr {
}
/// Returns an expression that's just a string
pub fn string(value: impl Into<CowStr>) -> Self {
pub fn string(value: impl Into<ArcStr>) -> Self {
Self {
cmpts: vec![ExprCmpt::String(value.into())],
}

View File

@ -3,7 +3,7 @@
// Imports
use {
super::Expr,
crate::ast,
crate::{ast, util::ArcStr},
std::{collections::BTreeMap, fmt, sync::Arc},
};
@ -23,14 +23,14 @@ pub enum OutItem<T> {
impl OutItem<Expr> {
/// Creates a new item from it's `ast`.
pub fn new(item: ast::OutItem<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, item: ast::OutItem<'_>) -> Self {
match item {
ast::OutItem::File(file) => Self::File {
file: Expr::new(file),
file: Expr::new(zbuild_file, file),
is_deps_file: false,
},
ast::OutItem::DepsFile { deps_file } => Self::File {
file: Expr::new(deps_file),
file: Expr::new(zbuild_file, deps_file),
is_deps_file: true,
},
}
@ -83,10 +83,10 @@ pub enum DepItem<T> {
impl DepItem<Expr> {
/// Creates a new item from it's `ast`.
pub fn new(item: ast::DepItem<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, item: ast::DepItem<'_>) -> Self {
match item {
ast::DepItem::File(file) => Self::File {
file: Expr::new(file),
file: Expr::new(zbuild_file, file),
is_optional: false,
is_static: false,
is_deps_file: false,
@ -94,28 +94,28 @@ impl DepItem<Expr> {
ast::DepItem::Rule { rule, pats } => {
let pats = pats
.into_iter()
.map(|(pat, value)| (Expr::new(pat), Expr::new(value)))
.map(|(pat, value)| (Expr::new(zbuild_file, pat), Expr::new(zbuild_file, value)))
.collect();
Self::Rule {
name: Expr::new(rule),
name: Expr::new(zbuild_file, rule),
pats: Arc::new(pats),
}
},
ast::DepItem::DepsFile { deps_file } => Self::File {
file: Expr::new(deps_file),
file: Expr::new(zbuild_file, deps_file),
is_optional: false,
is_static: false,
is_deps_file: true,
},
ast::DepItem::Static { item: static_item } => match static_item {
ast::StaticDepItem::File(file) => Self::File {
file: Expr::new(file),
file: Expr::new(zbuild_file, file),
is_optional: false,
is_static: true,
is_deps_file: false,
},
ast::StaticDepItem::DepsFile { deps_file } => Self::File {
file: Expr::new(deps_file),
file: Expr::new(zbuild_file, deps_file),
is_optional: false,
is_static: true,
is_deps_file: true,
@ -123,13 +123,13 @@ impl DepItem<Expr> {
},
ast::DepItem::Opt { item: opt_item } => match opt_item {
ast::OptDepItem::File(file) => Self::File {
file: Expr::new(file),
file: Expr::new(zbuild_file, file),
is_optional: true,
is_static: true,
is_deps_file: false,
},
ast::OptDepItem::DepsFile { deps_file } => Self::File {
file: Expr::new(deps_file),
file: Expr::new(zbuild_file, deps_file),
is_optional: true,
is_static: true,
is_deps_file: true,

View File

@ -1,13 +1,13 @@
//! Pattern
// Imports
use std::fmt;
use {crate::util::ArcStr, std::fmt};
/// Pattern
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
pub struct Pattern {
/// Pattern name
pub name: &'static str,
pub name: ArcStr,
/// Operators
pub ops: Vec<PatternOp>,

View File

@ -3,7 +3,7 @@
// Imports
use {
super::{DepItem, Expr, OutItem},
crate::ast,
crate::{ast, util::ArcStr},
indexmap::IndexMap,
std::sync::Arc,
};
@ -12,10 +12,10 @@ use {
#[derive(Clone, Debug)]
pub struct Rule<T> {
/// Name
pub name: &'static str,
pub name: ArcStr,
/// Aliases
pub aliases: Arc<IndexMap<&'static str, T>>,
pub aliases: Arc<IndexMap<ArcStr, T>>,
/// Output items
pub output: Vec<OutItem<T>>,
@ -29,15 +29,19 @@ pub struct Rule<T> {
impl Rule<Expr> {
/// Creates a new rule from it's ast
pub fn new(name: &'static str, rule: ast::Rule<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, name: ArcStr, rule: ast::Rule<'_>) -> Self {
let aliases = rule
.aliases
.into_iter()
.map(|(alias, expr)| (alias, Expr::new(expr)))
.map(|(alias, expr)| (zbuild_file.slice_from_str(alias), Expr::new(zbuild_file, expr)))
.collect();
let output = rule.out.into_iter().map(OutItem::new).collect();
let deps = rule.deps.into_iter().map(DepItem::new).collect();
let exec = Exec::new(rule.exec);
let output = rule.out.into_iter().map(|out| OutItem::new(zbuild_file, out)).collect();
let deps = rule
.deps
.into_iter()
.map(|dep| DepItem::new(zbuild_file, dep))
.collect();
let exec = Exec::new(zbuild_file, rule.exec);
Self {
name,
@ -59,9 +63,13 @@ pub struct Exec<T> {
impl Exec<Expr> {
/// Creates a new exec from it's ast
pub fn new(exec: ast::Exec<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, exec: ast::Exec<'_>) -> Self {
Self {
cmds: exec.cmds.into_iter().map(Command::new).collect(),
cmds: exec
.cmds
.into_iter()
.map(|cmd| Command::new(zbuild_file, cmd))
.collect(),
}
}
}
@ -79,15 +87,15 @@ pub struct Command<T> {
impl Command<Expr> {
/// Creates a new command from it's ast
pub fn new(cmd: ast::Command<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, cmd: ast::Command<'_>) -> Self {
match cmd {
ast::Command::OnlyArgs(args) => Self {
cwd: None,
args: args.into_iter().map(CommandArg::new).collect(),
args: args.into_iter().map(|arg| CommandArg::new(zbuild_file, arg)).collect(),
},
ast::Command::Full { cwd, args } => Self {
cwd: cwd.map(Expr::new),
args: args.into_iter().map(CommandArg::new).collect(),
cwd: cwd.map(|cwd| Expr::new(zbuild_file, cwd)),
args: args.into_iter().map(|arg| CommandArg::new(zbuild_file, arg)).collect(),
},
}
}
@ -102,9 +110,9 @@ pub enum CommandArg<T> {
impl CommandArg<Expr> {
/// Creates a new command argument from it's ast
pub fn new(arg: ast::CommandArg<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, arg: ast::CommandArg<'_>) -> Self {
match arg {
ast::CommandArg::Expr(expr) => Self::Expr(Expr::new(expr)),
ast::CommandArg::Expr(expr) => Self::Expr(Expr::new(zbuild_file, expr)),
}
}
}

View File

@ -3,7 +3,7 @@
// Imports
use {
super::Expr,
crate::{ast, util::CowStr},
crate::{ast, util::ArcStr},
std::{
collections::BTreeMap,
fmt,
@ -31,7 +31,7 @@ pub enum Target<T> {
rule: T,
/// Patterns
pats: Arc<BTreeMap<CowStr, T>>,
pats: Arc<BTreeMap<ArcStr, T>>,
},
}
@ -47,14 +47,14 @@ impl<T> Target<T> {
impl Target<Expr> {
/// Creates a new target from it's ast
pub fn new(ast: ast::Target<'static>) -> Self {
pub fn new(zbuild_file: &ArcStr, ast: ast::Target<'_>) -> Self {
match ast {
ast::Target::File(file) => Self::File {
file: Expr::new(file),
file: Expr::new(zbuild_file, file),
is_static: false,
},
ast::Target::Rule { rule } => Self::Rule {
rule: Expr::new(rule),
rule: Expr::new(zbuild_file, rule),
pats: Arc::new(BTreeMap::new()),
},
}

View File

@ -1,11 +1,16 @@
//! Utilities
// Modules
pub mod arc_str;
// Exports
pub use self::arc_str::ArcStr;
// Imports
use {
futures::Future,
pin_project::pin_project,
std::{
borrow::Cow,
io,
path::{self, Path, PathBuf},
pin::Pin,
@ -15,11 +20,6 @@ use {
tokio::fs,
};
/// Alias for `Cow<'static, str>`
// TODO: Replace this with some type like `(Arc<str>, Range<usize>)`
// to allow for cheap `clone`ing, which we perform a lot?
pub type CowStr = Cow<'static, str>;
/// Chains together any number of `IntoIterator`s
pub macro chain {
($lhs:expr, $rhs:expr $(,)?) => {

224
src/util/arc_str.rs Normal file
View File

@ -0,0 +1,224 @@
//! Arc string
// Lints
#![expect(unsafe_code, reason = "We need unsafe to implement our string 'cached' pointer")]
// Imports
use std::{
borrow::Borrow,
cmp,
fmt,
hash::{Hash, Hasher},
ops::Deref,
ptr::NonNull,
str::pattern::{Pattern, ReverseSearcher},
sync::Arc,
};
/// Arc string.
///
/// Stores a string as a (theoretical) `(Arc<str>, Range<usize>)`,
/// to allow for fast indexing and cloning.
///
/// The actual implementation stores a `(&str, Arc<String>)` for fast
/// access to the string, while retaining the ability to be cheaply
/// accessible as a `String`.
#[derive(Clone)]
pub struct ArcStr {
/// This string's pointer
///
/// The string must *never* be mutated through this pointer,
/// due to it being possibly derived from a `&str`.
ptr: NonNull<str>,
/// Inner
#[expect(clippy::rc_buffer, reason = "We need it for efficient conversion to/from `String`")]
inner: Arc<String>,
}
impl ArcStr {
/// Returns the offset of this string compared to the base
fn base_offset(&self) -> usize {
// SAFETY: `self.ptr` was derived from `inner.base_ptr`
let start = unsafe { self.ptr.as_ptr().byte_offset_from(self.inner.as_ptr()) };
usize::try_from(start).expect("String pointer was before base pointer")
}
/// Updates this string as a `&mut String`.
///
/// Copies the string unless no other copies exist
pub fn with_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut String) -> R,
{
// Get the offset and length of our specific string
let start = self.base_offset();
let len = self.len();
// Get the inner string
let s = match Arc::get_mut(&mut self.inner) {
// If we're unique, slice the parts we don't care about and return
Some(s) => {
s.truncate(start + len);
let _ = s.drain(..start);
s
},
// Otherwise copy
None => {
self.inner = Arc::new(self.to_string());
Arc::get_mut(&mut self.inner).expect("Should be unique")
},
};
// Since we're invalidating `self.inner`, replace `ptr`
// with a dummy value in case of panics.
self.ptr = NonNull::from("");
// Then mutate
let output = f(s);
// And finally, reconstruct ourselves
self.ptr = NonNull::from(s.as_str());
output
}
/// Creates a sub-slice of `self` containing `s`.
///
/// # Panics
/// `s` must be derived from this string, else this method panics.
pub fn slice_from_str(&self, s: &str) -> Self {
// Get pointer ranges
let self_range = self.as_bytes().as_ptr_range();
let s_range = s.as_bytes().as_ptr_range();
assert!(
self_range.contains(&s_range.start) || s_range.start == self_range.end,
"String start was before this string"
);
assert!(
self_range.contains(&s_range.end) || s_range.end == self_range.end,
"String end was past this string"
);
Self {
ptr: NonNull::from(s),
inner: Arc::clone(&self.inner),
}
}
/// Wrapper for [`str::strip_prefix`]
pub fn strip_prefix<P: Pattern>(&self, prefix: P) -> Option<Self> {
(**self).strip_prefix(prefix).map(|s| self.slice_from_str(s))
}
/// Wrapper for [`str::strip_suffix`]
pub fn strip_suffix<P: Pattern>(&self, suffix: P) -> Option<Self>
where
for<'a> P::Searcher<'a>: ReverseSearcher<'a>,
{
(**self).strip_suffix(suffix).map(|s| self.slice_from_str(s))
}
}
// SAFETY: We're a self-referential `(&str, Arc<String>)`,
// which is comprised of `Send + Sync` types.
unsafe impl Send for ArcStr {}
// SAFETY: See above in [`Send`] impl
unsafe impl Sync for ArcStr {}
impl PartialEq for ArcStr {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for ArcStr {}
impl PartialOrd for ArcStr {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ArcStr {
fn cmp(&self, other: &Self) -> cmp::Ordering {
(**self).cmp(&**other)
}
}
impl Hash for ArcStr {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
impl Default for ArcStr {
fn default() -> Self {
String::new().into()
}
}
impl fmt::Display for ArcStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl fmt::Debug for ArcStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl Deref for ArcStr {
type Target = str;
fn deref(&self) -> &Self::Target {
// SAFETY: `self.ptr` always contains a valid `str`.
unsafe { self.ptr.as_ref() }
}
}
impl Borrow<str> for ArcStr {
fn borrow(&self) -> &str {
self
}
}
impl From<String> for ArcStr {
fn from(s: String) -> Self {
Self {
ptr: NonNull::from(s.as_str()),
inner: Arc::new(s),
}
}
}
impl From<ArcStr> for String {
fn from(s: ArcStr) -> Self {
// Get the offset and length of our specific string
let start = s.base_offset();
let len = s.len();
match Arc::try_unwrap(s.inner) {
// If we're unique, slice the parts we don't care about and return
Ok(mut inner) => {
inner.truncate(start + len);
let _ = inner.drain(..start);
inner
},
// Otherwise copy
Err(inner) => ArcStr { inner, ..s }.to_string(),
}
}
}
impl From<&str> for ArcStr {
fn from(s: &str) -> Self {
s.to_owned().into()
}
}

View File

@ -8,7 +8,7 @@
// Imports
use {
crate::{build, rules::Target, util::CowStr, AppError, Builder, Rules},
crate::{build, rules::Target, util::ArcStr, AppError, Builder, Rules},
anyhow::Context,
dashmap::{DashMap, DashSet},
futures::{stream::FuturesUnordered, StreamExt},
@ -28,10 +28,10 @@ use {
#[derive(Clone, Debug)]
struct RevDep {
/// Target of the dependency
target: Target<CowStr>,
target: Target<ArcStr>,
/// All parent targets
parents: Arc<DashSet<Target<CowStr>>>,
parents: Arc<DashSet<Target<ArcStr>>>,
}
/// Target watcher