Expressions are now always a single string.

Expression operators are now alias and pattern operators.
Added pattern operator `NonEmpty`.
This commit is contained in:
Filipe Rodrigues 2022-08-29 01:46:08 +01:00
parent f97f238393
commit e4fe3071a7
8 changed files with 280 additions and 222 deletions

View File

@ -44,25 +44,9 @@ pub enum Target {
/// Expression
#[derive(Clone, Debug)]
pub enum Expr {
/// Operation
Op {
/// Operation
op: ExprOp,
/// Expr
expr: Box<Self>,
},
/// String
String(Vec<ExprCmpt>),
}
/// Expression operator
#[derive(Clone, Debug)]
pub enum ExprOp {
/// Dir name
DirName,
pub struct Expr {
/// Components
pub cmpts: Vec<ExprCmpt>,
}
/// Expression component
@ -72,10 +56,24 @@ pub enum ExprCmpt {
String(String),
/// Pattern
Pattern(String),
Pattern { name: String, ops: Vec<PatternOp> },
/// Alias
Alias(String),
Alias { name: String, ops: Vec<AliasOp> },
}
/// Pattern operator
#[derive(Clone, Debug)]
pub enum PatternOp {
/// Non-empty
NonEmpty,
}
/// Alias operator
#[derive(Clone, Debug)]
pub enum AliasOp {
/// Directory name
DirName,
}
impl<'de> serde::Deserialize<'de> for Expr {
@ -83,98 +81,94 @@ impl<'de> serde::Deserialize<'de> for Expr {
where
D: serde::Deserializer<'de>,
{
struct Visitor;
// Parse the string
// TODO: Allow arrays and concat them?
// TODO: Deserialize a `Cow<str>`?
let inner = String::deserialize(deserializer)?;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Expr;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "Map with single key for operation or string")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
// Get the entry
let (op, expr) = map
.next_entry::<String, Expr>()?
.ok_or_else(|| A::Error::custom("Unexpected empty map"))?;
// Make sure there isn't another one
if let Some((key, _)) = map.next_entry::<String, Expr>()? {
return Err(A::Error::custom(format!("Unexpected second map entry: {key:?}")));
}
// Then parse the operation
let op = match op.as_str() {
"dir_name" => ExprOp::DirName,
_ => return Err(A::Error::custom(format!("Unknown expression operation: {op:?}"))),
};
Ok(Expr::Op {
op,
expr: Box::new(expr),
})
}
fn visit_str<E>(self, mut s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let mut components = vec![];
loop {
// Try to find the next pattern / alias
match s.find(['$', '^']) {
// If we found it
Some(idx) => {
// Add the string until it, if it isn't empty
if !s[..idx].is_empty() {
components.push(ExprCmpt::String(s[..idx].to_owned()));
}
// Then check which one we got
let mut chars = s[idx..].chars();
let cmpt_fn = match chars.next() {
Some('$') => ExprCmpt::Alias,
Some('^') => ExprCmpt::Pattern,
_ => unreachable!(),
};
// Make sure it's valid
match chars.next() {
Some('(') => (),
Some(ch) => return Err(E::custom(format!("Expected `(` after `$`, found {ch:?}"))),
None => return Err(E::custom("Expected `(` after `$`")),
};
// Then read until `)`
s = chars.as_str();
let alias;
(alias, s) = s
.split_once(')')
.ok_or_else(|| E::custom("Alias `$(` has no closing brace"))?;
components.push(cmpt_fn(alias.to_owned()));
},
// If we didn't find any, the rest of the expression if a string
None => {
// Add the rest only if it isn't empty
if !s.is_empty() {
components.push(ExprCmpt::String(s.to_owned()))
}
break;
},
// Then parse all components
let mut cmpts = vec![];
let mut rest = inner.as_str();
loop {
// Try to find the next pattern / alias
match rest.find(['$', '^']) {
// If we found it
Some(idx) => {
// Add the string until the pattern / alias, if it isn't empty
if !rest[..idx].is_empty() {
cmpts.push(ExprCmpt::String(rest[..idx].to_owned()));
}
}
Ok(Expr::String(components))
// Then check if it was an alias or pattern
enum Kind {
Alias,
Pattern,
}
let mut chars = rest[idx..].chars();
let kind = match chars.next() {
Some('$') => Kind::Alias,
Some('^') => Kind::Pattern,
_ => unreachable!(),
};
// Ensure it starts with `(`
match chars.next() {
Some('(') => (),
Some(ch) => return Err(D::Error::custom(format!("Expected `(` after `$`, found {ch:?}"))),
None => return Err(D::Error::custom("Expected `(` after `$`")),
};
// Then read until `)`
rest = chars.as_str();
let inner;
(inner, rest) = rest
.split_once(')')
.ok_or_else(|| D::Error::custom("Alias `$(` has no closing brace"))?;
// And split all operations
let (name, ops) = match inner.split_once("::") {
Some((name, ops)) => (name, ops.split("::").collect()),
None => (inner, vec![]),
};
let cmpt = match kind {
Kind::Alias => ExprCmpt::Alias {
name: name.to_owned(),
ops: ops
.into_iter()
.map(|op| match op.trim() {
"dir_name" => Ok(AliasOp::DirName),
op => Err(D::Error::custom(format!("Unknown alias operator {op:?}"))),
})
.collect::<Result<_, _>>()?,
},
Kind::Pattern => ExprCmpt::Pattern {
name: name.to_owned(),
ops: ops
.into_iter()
.map(|op| match op.trim() {
"non_empty" => Ok(PatternOp::NonEmpty),
op => Err(D::Error::custom(format!("Unknown pattern operator {op:?}"))),
})
.collect::<Result<_, _>>()?,
},
};
cmpts.push(cmpt);
},
// If we didn't find any, the rest of the expression if a string
None => {
// Add the rest only if it isn't empty
if !rest.is_empty() {
cmpts.push(ExprCmpt::String(rest.to_owned()))
}
break;
},
}
}
// TODO: Not use `_any`?
deserializer.deserialize_any(Visitor)
Ok(Expr { cmpts })
}
}

View File

@ -229,7 +229,6 @@ impl Builder {
.max_by_key(|res| res.build_time);
let output_last_build_time = self::rule_last_build_time(&rule).ok().flatten();
tracing::trace!(?target, ?output_last_build_time, ?deps_res, "Check rebuild");
let needs_rebuilt = match (deps_res, output_last_build_time) {
// If not built, rebuild
(_, None) => true,

View File

@ -2,43 +2,63 @@
// Imports
use {
crate::rules::{Alias, Expr, ExprCmpt, ExprOp, Pattern},
crate::rules::{AliasOp, Expr, ExprCmpt},
anyhow::Context,
std::{borrow::Cow, collections::HashMap, path::PathBuf},
};
/// Expands an expression to it's components
pub fn expand_expr(expr: &Expr, visitor: &mut impl Visitor) -> Result<Vec<ExprCmpt>, anyhow::Error> {
let cmpts = match expr {
Expr::Op { op, expr } => {
let s = self::expand_expr_string(expr, visitor)
.with_context(|| format!("Unable to expand expression to string for operator {expr:?}"))?;
let s = self::expand_op(op, s)?;
vec![ExprCmpt::String(s)]
},
// If we got a string, simply expand it
Expr::String(cmpts) => cmpts.iter().try_fold::<_, _, Result<_, _>>(vec![], |mut cmpts, cmpt| {
// Go through all components
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()),
ExprCmpt::Pattern(pat) => match visitor.visit_pat(pat) {
FlowControl::ExpandTo(s) => cmpts.push(ExprCmpt::String(s)),
// 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 => anyhow::bail!("Unknown pattern {pat:?}"),
},
ExprCmpt::Alias(alias) => match visitor.visit_alias(alias) {
FlowControl::ExpandTo(expr) => cmpts.extend(self::expand_expr(&expr, visitor)?),
// 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(expr) => match alias.ops.is_empty() {
// If not, just recursively expand it
true => cmpts.extend(self::expand_expr(&expr, visitor)?),
// Else expand it to a string, then apply all operations
// TODO: Apply operations on the expression components without expanding to a string?
false => {
// Expand
let value = self::expand_expr_string(&expr, visitor)?;
// Then apply all
let value = alias.ops.iter().try_fold(value, |value, &op| {
self::expand_alias_op(op, value)
.with_context(|| format!("Unable to apply alias operator {op:?}"))
})?;
cmpts.push(ExprCmpt::String(value))
},
},
// Else keep on Keep and error on Error
FlowControl::Keep => cmpts.push(cmpt.clone()),
FlowControl::Error => anyhow::bail!("Unknown alias {alias:?}"),
},
};
Ok(cmpts)
})?,
};
Ok(cmpts)
})
}
/// Expands an expression into a string
@ -46,49 +66,55 @@ pub fn expand_expr(expr: &Expr, visitor: &mut impl Visitor) -> Result<Vec<ExprCm
/// Panics if `visitor` returns `Keep`.
// TODO: Merge neighboring strings in output.
pub fn expand_expr_string(expr: &Expr, visitor: &mut impl Visitor) -> Result<String, anyhow::Error> {
let s = match expr {
Expr::Op { op, expr } => {
let expr = self::expand_expr_string(expr, visitor)?;
self::expand_op(op, expr)?
},
expr.cmpts
.iter()
.try_fold::<_, _, Result<_, _>>(String::new(), |mut string, cmpt| {
let s = match cmpt {
ExprCmpt::String(s) => Cow::Borrowed(s),
ExprCmpt::Pattern(pat) => match visitor.visit_pat(&pat.name) {
FlowControl::ExpandTo(s) => Cow::Owned(s),
FlowControl::Keep => panic!("Cannot keep unknown pattern when expanding to string"),
FlowControl::Error => anyhow::bail!("Unknown pattern {pat:?}"),
},
ExprCmpt::Alias(alias) => {
match visitor.visit_alias(&alias.name) {
FlowControl::ExpandTo(expr) => match alias.ops.is_empty() {
// If not, just recursively expand it
true => self::expand_expr_string(&expr, visitor).map(Cow::Owned)?,
// If we got a string, simply expand it
Expr::String(cmpts) => cmpts
.iter()
.try_fold::<_, _, Result<_, _>>(String::new(), |mut string, cmpt| {
let s = match cmpt {
ExprCmpt::String(s) => Cow::Borrowed(s),
ExprCmpt::Pattern(pat) => match visitor.visit_pat(pat) {
FlowControl::ExpandTo(s) => Cow::Owned(s),
// Else expand it to a string, then apply all operations
// TODO: Apply operations on the expression components without expanding to a string?
false => {
// Expand
let value = self::expand_expr_string(&expr, visitor)?;
// Then apply all
let value = alias.ops.iter().try_fold(value, |value, &op| {
self::expand_alias_op(op, value)
.with_context(|| format!("Unable to apply alias operator {op:?}"))
})?;
Cow::Owned(value)
},
},
FlowControl::Keep => panic!("Cannot keep unknown pattern when expanding to string"),
FlowControl::Error => anyhow::bail!("Unknown pattern {pat:?}"),
},
ExprCmpt::Alias(alias) => {
let expr = match visitor.visit_alias(alias) {
FlowControl::ExpandTo(expr) => expr,
FlowControl::Keep => panic!("Cannot keep unknown pattern when expanding to string"),
FlowControl::Error => anyhow::bail!("Unknown alias {alias:?}"),
};
let s = self::expand_expr_string(&expr, visitor)?;
Cow::Owned(s)
},
};
FlowControl::Error => anyhow::bail!("Unknown alias {alias:?}"),
}
},
};
string.push_str(&s);
Ok(string)
})?,
};
Ok(s)
string.push_str(&s);
Ok(string)
})
}
/// Expands an operation on an expression
fn expand_op(op: &ExprOp, expr: String) -> Result<String, anyhow::Error> {
let expr = match op {
/// Expands an alias operation on the value of that alias
fn expand_alias_op(op: AliasOp, value: String) -> Result<String, anyhow::Error> {
let value = match op {
// TODO: Not add `/` here.
ExprOp::DirName => {
AliasOp::DirName => {
// Get the path and try to pop the last segment
let mut path = PathBuf::from(expr);
let mut path = PathBuf::from(value);
anyhow::ensure!(path.pop(), "Path {path:?} had no directory name");
// Then convert it back to a string
@ -103,7 +129,7 @@ fn expand_op(op: &ExprOp, expr: String) -> Result<String, anyhow::Error> {
},
};
Ok(expr)
Ok(value)
}
/// Flow control for [`expand_expr_string`]
@ -121,10 +147,10 @@ pub enum FlowControl<T> {
/// Visitor for [`expand_expr_string`]
pub trait Visitor {
/// Visits an alias
fn visit_alias(&mut self, alias: &Alias) -> FlowControl<Expr>;
fn visit_alias(&mut self, alias_name: &str) -> FlowControl<Expr>;
/// Visits a pattern
fn visit_pat(&mut self, pat: &Pattern) -> FlowControl<String>;
fn visit_pat(&mut self, pat_name: &str) -> FlowControl<String>;
}
/// Visitor for global aliases.
@ -145,14 +171,14 @@ impl<'global> GlobalVisitor<'global> {
}
impl<'global> Visitor for GlobalVisitor<'global> {
fn visit_alias(&mut self, alias: &Alias) -> FlowControl<Expr> {
match self.aliases.get(&alias.name).cloned() {
fn visit_alias(&mut self, alias_name: &str) -> FlowControl<Expr> {
match self.aliases.get(alias_name).cloned() {
Some(expr) => FlowControl::ExpandTo(expr),
None => FlowControl::Error,
}
}
fn visit_pat(&mut self, _pat: &Pattern) -> FlowControl<String> {
fn visit_pat(&mut self, _pat: &str) -> FlowControl<String> {
FlowControl::Error
}
}
@ -181,14 +207,14 @@ impl<'global, 'rule> RuleOutputVisitor<'global, 'rule> {
}
impl<'global, 'rule> Visitor for RuleOutputVisitor<'global, 'rule> {
fn visit_alias(&mut self, alias: &Alias) -> FlowControl<Expr> {
match self.rule_aliases.get(&alias.name).cloned() {
fn visit_alias(&mut self, alias_name: &str) -> FlowControl<Expr> {
match self.rule_aliases.get(alias_name).cloned() {
Some(expr) => FlowControl::ExpandTo(expr),
None => self.global_visitor.visit_alias(alias),
None => self.global_visitor.visit_alias(alias_name),
}
}
fn visit_pat(&mut self, _pat: &Pattern) -> FlowControl<String> {
fn visit_pat(&mut self, _pat: &str) -> FlowControl<String> {
FlowControl::Keep
}
}
@ -217,12 +243,12 @@ impl<'global, 'rule, 'pats> RuleVisitor<'global, 'rule, 'pats> {
}
impl<'global, 'rule, 'pats> Visitor for RuleVisitor<'global, 'rule, 'pats> {
fn visit_alias(&mut self, alias: &Alias) -> FlowControl<Expr> {
self.rule_output_visitor.visit_alias(alias)
fn visit_alias(&mut self, alias_name: &str) -> FlowControl<Expr> {
self.rule_output_visitor.visit_alias(alias_name)
}
fn visit_pat(&mut self, pat: &Pattern) -> FlowControl<String> {
match self.pats.get(&pat.name).cloned() {
fn visit_pat(&mut self, pat_name: &str) -> FlowControl<String> {
match self.pats.get(pat_name).cloned() {
Some(value) => FlowControl::ExpandTo(value),
None => FlowControl::Error,
}

View File

@ -1,7 +1,10 @@
//! Expression matching
// Imports
use {crate::rules::ExprCmpt, std::collections::HashMap};
use {
crate::rules::{ExprCmpt, PatternOp},
std::collections::HashMap,
};
/// Returns if `value` matches all `cmpts` and returns all patterns resolved
///
@ -30,8 +33,26 @@ pub fn match_expr(mut cmpts: &[ExprCmpt], mut value: &str) -> Result<Option<Hash
None => return Ok(None),
},
// If we're a single pattern, we fully match anything on the right
// If we're a single pattern, check for operators
[ExprCmpt::Pattern(pat)] => {
let mut ops = pat.ops.as_slice();
loop {
match ops {
// If we're empty, match everything
[] => break,
// On non-empty check if the rest of the value is empty
[PatternOp::NonEmpty, rest @ ..] => match value.is_empty() {
// If so, we don't match anything
true => return Ok(None),
// Else continue checking the rest of the operators
false => ops = rest,
},
}
}
// If we get here, match everything
patterns.insert(pat.name.clone(), value.to_owned());
cmpts = &[];
value = "";

View File

@ -10,10 +10,10 @@ mod target;
// Exports
pub use {
alias::Alias,
expr::{Expr, ExprCmpt, ExprOp},
alias::{Alias, AliasOp},
expr::{Expr, ExprCmpt},
item::Item,
pattern::Pattern,
pattern::{Pattern, PatternOp},
rule::{Command, Rule},
target::Target,
};

View File

@ -5,4 +5,14 @@
pub struct Alias {
/// Alias name
pub name: String,
/// Operators
pub ops: Vec<AliasOp>,
}
/// Alias operator
#[derive(Clone, Copy, Debug)]
pub enum AliasOp {
/// Directory name
DirName,
}

View File

@ -2,31 +2,18 @@
// Imports
use {
super::{alias::Alias, pattern::Pattern},
super::{
alias::{Alias, AliasOp},
pattern::{Pattern, PatternOp},
},
crate::ast,
};
/// Expression
#[derive(Clone, Debug)]
pub enum Expr {
/// Operation
Op {
/// Operation
op: ExprOp,
/// Expression
expr: Box<Self>,
},
/// String
String(Vec<ExprCmpt>),
}
/// Expression operator
#[derive(Clone, Debug)]
pub enum ExprOp {
/// Dir name
DirName,
pub struct Expr {
/// Components
pub cmpts: Vec<ExprCmpt>,
}
/// Expression component
@ -45,28 +32,39 @@ pub enum ExprCmpt {
impl Expr {
/// Creates a new expression from it's ast
pub fn new(expr: ast::Expr) -> Self {
match expr {
ast::Expr::Op { op, expr } => Self::Op {
op: match op {
ast::ExprOp::DirName => ExprOp::DirName,
},
expr: Box::new(Self::new(*expr)),
},
ast::Expr::String(cmpts) => Self::String(
cmpts
.into_iter()
.map(|cmpt| match cmpt {
ast::ExprCmpt::String(s) => ExprCmpt::String(s),
ast::ExprCmpt::Pattern(name) => ExprCmpt::Pattern(Pattern { name }),
ast::ExprCmpt::Alias(name) => ExprCmpt::Alias(Alias { name }),
})
.collect(),
),
}
let cmpts = expr
.cmpts
.into_iter()
.map(|cmpt| match cmpt {
ast::ExprCmpt::String(s) => ExprCmpt::String(s),
ast::ExprCmpt::Pattern { name, ops } => ExprCmpt::Pattern(Pattern {
name,
ops: ops
.into_iter()
.map(|op| match op {
ast::PatternOp::NonEmpty => PatternOp::NonEmpty,
})
.collect(),
}),
ast::ExprCmpt::Alias { name, ops } => ExprCmpt::Alias(Alias {
name,
ops: ops
.into_iter()
.map(|op| match op {
ast::AliasOp::DirName => AliasOp::DirName,
})
.collect(),
}),
})
.collect();
Self { cmpts }
}
/// Returns an expression that's just a string
pub fn string(value: String) -> Self {
Self::String(vec![ExprCmpt::String(value)])
Self {
cmpts: vec![ExprCmpt::String(value)],
}
}
}

View File

@ -5,4 +5,14 @@
pub struct Pattern {
/// Pattern name
pub name: String,
/// Operators
pub ops: Vec<PatternOp>,
}
/// Pattern operator
#[derive(Clone, Copy, Debug)]
pub enum PatternOp {
/// Non-empty
NonEmpty,
}