use super::{IntoExpr, List, Path};
use std::marker::PhantomData;
use toasty_core::stmt;
pub trait Assign<T> {
fn into_assignment(self) -> Assignment<T>;
fn assign(self, assignments: &mut stmt::Assignments, projection: stmt::Projection)
where
Self: Sized,
{
self.into_assignment().kind.apply(assignments, projection);
}
}
pub struct Assignment<T> {
kind: AssignmentKind,
_p: PhantomData<T>,
}
enum AssignmentKind {
Set(stmt::Expr),
Insert(stmt::Expr),
Remove(stmt::Expr),
Patch {
path_projection: stmt::Projection,
inner: Box<AssignmentKind>,
},
Apply(Vec<AssignmentKind>),
}
impl AssignmentKind {
fn apply(self, assignments: &mut stmt::Assignments, projection: stmt::Projection) {
match self {
AssignmentKind::Set(expr) => assignments.set(projection, expr),
AssignmentKind::Insert(expr) => assignments.insert(projection, expr),
AssignmentKind::Remove(expr) => assignments.remove(projection, expr),
AssignmentKind::Patch {
path_projection,
inner,
} => {
let mut projection = projection;
for &step in path_projection.as_slice() {
projection.push(step);
}
inner.apply(assignments, projection);
}
AssignmentKind::Apply(ops) => {
for op in ops {
op.apply(assignments, projection.clone());
}
}
}
}
}
impl<T> Assign<T> for Assignment<T> {
fn into_assignment(self) -> Assignment<T> {
self
}
}
macro_rules! impl_assign_via_expr {
($source:ty => $target:ty) => {
impl super::assignment::Assign<$target> for $source {
fn into_assignment(self) -> super::assignment::Assignment<$target> {
$crate::stmt::set(
super::IntoExpr::<$target>::into_expr(self),
)
}
}
};
({ $($gen:tt)* } $source:ty => $target:ty) => {
impl<$($gen)*> super::assignment::Assign<$target> for $source {
fn into_assignment(self) -> super::assignment::Assignment<$target> {
$crate::stmt::set(
super::IntoExpr::<$target>::into_expr(self),
)
}
}
};
}
pub(super) use impl_assign_via_expr;
pub fn insert<T>(expr: impl IntoExpr<T>) -> Assignment<List<T>> {
Assignment {
kind: AssignmentKind::Insert(expr.into_expr().untyped),
_p: PhantomData,
}
}
pub fn remove<T>(expr: impl IntoExpr<T>) -> Assignment<List<T>> {
Assignment {
kind: AssignmentKind::Remove(expr.into_expr().untyped),
_p: PhantomData,
}
}
pub fn set<T>(expr: impl IntoExpr<T>) -> Assignment<T> {
Assignment {
kind: AssignmentKind::Set(expr.into_expr().untyped),
_p: PhantomData,
}
}
pub fn patch<T, U>(path: Path<T, U>, value: impl Assign<U>) -> Assignment<T> {
let inner = value.into_assignment();
Assignment {
kind: AssignmentKind::Patch {
path_projection: path.untyped.projection,
inner: Box::new(inner.kind),
},
_p: PhantomData,
}
}
pub fn apply<T>(ops: impl IntoIterator<Item = Assignment<T>>) -> Assignment<T> {
let ops: Vec<AssignmentKind> = ops.into_iter().map(|a| a.kind).collect();
Assignment {
kind: AssignmentKind::Apply(ops),
_p: PhantomData,
}
}