mirror of
https://github.com/Zenithsiz/zbuild.git
synced 2026-02-04 14:45:54 +00:00
299 lines
7.7 KiB
Rust
299 lines
7.7 KiB
Rust
//! Expander
|
|
|
|
// Imports
|
|
use {
|
|
crate::{
|
|
error::AppError,
|
|
rules::{AliasOp, Command, DepItem, Exec, Expr, ExprCmpt, OutItem, Rule, Target},
|
|
util::CowStr,
|
|
},
|
|
itertools::Itertools,
|
|
std::path::PathBuf,
|
|
};
|
|
|
|
/// Expander
|
|
#[derive(Debug)]
|
|
pub struct Expander;
|
|
|
|
#[expect(clippy::unused_self)] // Currently expander doesn't do anything
|
|
impl Expander {
|
|
/// Creates a new expander
|
|
pub const fn new() -> Self {
|
|
Self
|
|
}
|
|
|
|
/// Expands an expression to it's components
|
|
pub fn expand_expr<'s>(&self, expr: &Expr<'s>, visitor: &mut impl Visitor<'s>) -> Result<Expr<'s>, AppError> {
|
|
// Go through all components
|
|
let cmpts = expr
|
|
.cmpts
|
|
.iter()
|
|
.try_fold::<_, _, Result<_, _>>(vec![], |mut cmpts, cmpt| {
|
|
match cmpt {
|
|
// If it's a string, we keep it
|
|
ExprCmpt::String(_) => cmpts.push(cmpt.clone()),
|
|
|
|
// 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) {
|
|
// If expanded, just replace it with a string
|
|
FlowControl::ExpandTo(value) => cmpts.push(ExprCmpt::String(value)),
|
|
|
|
// Else keep on Keep and error on Error
|
|
FlowControl::Keep => cmpts.push(cmpt.clone()),
|
|
FlowControl::Error =>
|
|
return Err(AppError::UnknownPattern {
|
|
pattern_name: pat.name.to_owned(),
|
|
}),
|
|
},
|
|
|
|
// If it's an alias, we visit and then expand it
|
|
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
|
|
true => cmpts.extend(self.expand_expr(&alias_expr, visitor)?.cmpts),
|
|
|
|
// Else expand it to a string, then apply all operations
|
|
// Note: We expand to string even if we don't *need* to to ensure the user doesn't
|
|
// add a dependency at some point, which needs a string output and suddenly
|
|
// we can't resolve the operations.
|
|
false => {
|
|
// Expand
|
|
let value = self.expand_expr_string(&alias_expr, visitor)?;
|
|
|
|
// Then apply all
|
|
#[expect(clippy::shadow_unrelated)] // They are the same value
|
|
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))
|
|
})?;
|
|
|
|
cmpts.push(ExprCmpt::String(value));
|
|
},
|
|
},
|
|
|
|
// Else keep on Keep and error on Error
|
|
FlowControl::Keep => cmpts.push(cmpt.clone()),
|
|
FlowControl::Error =>
|
|
return Err(AppError::UnknownAlias {
|
|
alias_name: alias.name.to_owned(),
|
|
}),
|
|
},
|
|
};
|
|
|
|
Ok(cmpts)
|
|
})?;
|
|
|
|
// Then merge neighboring strings
|
|
// TODO: Do this in the above pass
|
|
let cmpts = cmpts
|
|
.into_iter()
|
|
.coalesce(|prev, next| match (prev, next) {
|
|
// Merge strings
|
|
(ExprCmpt::String(prev), ExprCmpt::String(next)) => Ok(ExprCmpt::String(prev + next)),
|
|
|
|
// Everything else leave
|
|
(prev, next) => Err((prev, next)),
|
|
})
|
|
.collect();
|
|
|
|
Ok(Expr { cmpts })
|
|
}
|
|
|
|
/// Expands an expression into a string
|
|
pub fn expand_expr_string<'s>(
|
|
&self,
|
|
expr: &Expr<'s>,
|
|
visitor: &mut impl Visitor<'s>,
|
|
) -> Result<CowStr<'s>, AppError> {
|
|
let expr_cmpts = self.expand_expr(expr, visitor)?.cmpts.into_boxed_slice();
|
|
let res = match Box::<[_; 0]>::try_from(expr_cmpts) {
|
|
Ok(box []) => Ok("".into()),
|
|
Err(cmpts) => match Box::<[_; 1]>::try_from(cmpts) {
|
|
Ok(box [ExprCmpt::String(s)]) => Ok(s),
|
|
Ok(box [cmpt]) => Err(vec![cmpt]),
|
|
Err(cmpts) => Err(cmpts.into_vec()),
|
|
},
|
|
};
|
|
|
|
res.map_err(|cmpts| AppError::UnresolvedAliasOrPats {
|
|
expr_fmt: expr.to_string(),
|
|
expr_cmpts_fmt: cmpts.into_iter().map(|cmpt| cmpt.to_string()).collect(),
|
|
})
|
|
}
|
|
|
|
/// 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 {
|
|
AliasOp::DirName => {
|
|
// Get the path and try to pop the last segment
|
|
let mut path = PathBuf::from(value);
|
|
if !path.pop() {
|
|
return Err(AppError::PathParent { path });
|
|
}
|
|
|
|
// Then convert it back to a string
|
|
// Note: This should technically never fail, since the path was originally
|
|
// utf-8
|
|
path.into_os_string()
|
|
.into_string()
|
|
.expect("utf-8 path was no longer utf-8 after getting dir-name")
|
|
},
|
|
};
|
|
|
|
Ok(value)
|
|
}
|
|
|
|
/// Expands a rule of all it's aliases and patterns
|
|
pub fn expand_rule<'s>(
|
|
&self,
|
|
rule: &Rule<'s, Expr<'s>>,
|
|
visitor: &mut impl Visitor<'s>,
|
|
) -> Result<Rule<'s, CowStr<'s>>, AppError> {
|
|
let aliases = rule
|
|
.aliases
|
|
.iter()
|
|
.map(|(&name, expr)| Ok((name, self.expand_expr_string(expr, visitor)?)))
|
|
.collect::<Result<_, AppError>>()?;
|
|
|
|
let output = rule
|
|
.output
|
|
.iter()
|
|
.map(|item: &OutItem<Expr>| match item {
|
|
OutItem::File { file } => Ok::<_, AppError>(OutItem::File {
|
|
file: self.expand_expr_string(file, visitor)?,
|
|
}),
|
|
OutItem::DepsFile { file } => Ok::<_, AppError>(OutItem::DepsFile {
|
|
file: self.expand_expr_string(file, visitor)?,
|
|
}),
|
|
})
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
let deps = rule
|
|
.deps
|
|
.iter()
|
|
.map(|item: &DepItem<Expr>| match *item {
|
|
DepItem::File {
|
|
ref file,
|
|
is_optional,
|
|
is_static,
|
|
} => Ok::<_, AppError>(DepItem::File {
|
|
file: self.expand_expr_string(file, visitor)?,
|
|
is_optional,
|
|
is_static,
|
|
}),
|
|
DepItem::DepsFile {
|
|
ref file,
|
|
is_optional,
|
|
is_static,
|
|
} => Ok::<_, AppError>(DepItem::DepsFile {
|
|
file: self.expand_expr_string(file, visitor)?,
|
|
is_optional,
|
|
is_static,
|
|
}),
|
|
DepItem::Rule { ref name, ref pats } => Ok::<_, AppError>(DepItem::Rule {
|
|
name: self.expand_expr_string(name, visitor)?,
|
|
pats: pats
|
|
.iter()
|
|
.map(|(pat, expr)| {
|
|
Ok((
|
|
self.expand_expr_string(pat, visitor)?,
|
|
self.expand_expr_string(expr, visitor)?,
|
|
))
|
|
})
|
|
.collect::<Result<_, AppError>>()?,
|
|
}),
|
|
})
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
let exec = Exec {
|
|
cwd: rule
|
|
.exec
|
|
.cwd
|
|
.as_ref()
|
|
.map(|cwd| self.expand_expr_string(cwd, visitor))
|
|
.transpose()?,
|
|
cmds: rule
|
|
.exec
|
|
.cmds
|
|
.iter()
|
|
.map(|cmd| {
|
|
Ok(Command {
|
|
args: cmd
|
|
.args
|
|
.iter()
|
|
.map(|arg| self.expand_expr_string(arg, visitor))
|
|
.collect::<Result<_, _>>()?,
|
|
})
|
|
})
|
|
.collect::<Result<_, _>>()?,
|
|
};
|
|
|
|
Ok(Rule {
|
|
name: rule.name,
|
|
aliases,
|
|
output,
|
|
deps,
|
|
exec,
|
|
})
|
|
}
|
|
|
|
/// Expands a target expression
|
|
pub fn expand_target<'s>(
|
|
&self,
|
|
target: &Target<'s, Expr<'s>>,
|
|
visitor: &mut impl Visitor<'s>,
|
|
) -> Result<Target<'s, CowStr<'s>>, AppError> {
|
|
let target = match *target {
|
|
Target::File { ref file, is_static } => Target::File {
|
|
file: self
|
|
.expand_expr_string(file, visitor)
|
|
.map_err(AppError::expand_expr(file))?,
|
|
is_static,
|
|
},
|
|
|
|
Target::Rule { ref rule, ref pats } => Target::Rule {
|
|
rule: self
|
|
.expand_expr_string(rule, visitor)
|
|
.map_err(AppError::expand_expr(rule))?,
|
|
pats: pats
|
|
.iter()
|
|
.map(|(pat, expr)| {
|
|
Ok((
|
|
pat.clone(),
|
|
self.expand_expr_string(expr, visitor)
|
|
.map_err(AppError::expand_expr(expr))?,
|
|
))
|
|
})
|
|
.collect::<Result<_, AppError>>()?,
|
|
},
|
|
};
|
|
|
|
Ok(target)
|
|
}
|
|
}
|
|
|
|
/// Flow control for [`expand_expr_string`]
|
|
pub enum FlowControl<T> {
|
|
/// Expand to
|
|
ExpandTo(T),
|
|
|
|
/// Keep
|
|
Keep,
|
|
|
|
/// Error
|
|
Error,
|
|
}
|
|
|
|
/// Visitor for [`Expander`]
|
|
pub trait Visitor<'s> {
|
|
/// Visits an alias
|
|
fn visit_alias(&mut self, alias_name: &str) -> FlowControl<Expr<'s>>;
|
|
|
|
/// Visits a pattern
|
|
fn visit_pat(&mut self, pat_name: &str) -> FlowControl<CowStr<'s>>;
|
|
}
|