summaryrefslogtreecommitdiff
path: root/src/eval/value.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-06-14 15:07:13 +0200
committerLaurenz <laurmaedje@gmail.com>2022-06-14 17:17:54 +0200
commit7a6c2cce7747f7632f0be012f49b548db3e62a2d (patch)
tree30103d743fcf22c6838e5ce3b6c632abe15e78b9 /src/eval/value.rs
parent6832ca2a26df5a9407bd2b0266cc0bab328ebeba (diff)
Make radius configuration unconfusing
Diffstat (limited to 'src/eval/value.rs')
-rw-r--r--src/eval/value.rs212
1 files changed, 125 insertions, 87 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs
index a7da99c9..294aac94 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -8,7 +8,8 @@ use std::sync::Arc;
use super::{ops, Args, Array, Dict, Func, RawLength, Regex};
use crate::diag::{with_alternative, StrResult};
use crate::geom::{
- Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
+ Angle, Color, Corners, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor,
+ Sides,
};
use crate::library::text::RawNode;
use crate::model::{Content, Group, Layout, LayoutNode, Pattern};
@@ -516,6 +517,71 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
}
}
+dynamic! {
+ Dir: "direction",
+}
+
+dynamic! {
+ Regex: "regular expression",
+}
+
+dynamic! {
+ Group: "group",
+}
+
+castable! {
+ usize,
+ Expected: "non-negative integer",
+ Value::Int(int) => int.try_into().map_err(|_| {
+ if int < 0 {
+ "must be at least zero"
+ } else {
+ "number too large"
+ }
+ })?,
+}
+
+castable! {
+ NonZeroUsize,
+ Expected: "positive integer",
+ Value::Int(int) => int
+ .try_into()
+ .and_then(|int: usize| int.try_into())
+ .map_err(|_| if int <= 0 {
+ "must be positive"
+ } else {
+ "number too large"
+ })?,
+}
+
+castable! {
+ Paint,
+ Expected: "color",
+ Value::Color(color) => Paint::Solid(color),
+}
+
+castable! {
+ String,
+ Expected: "string",
+ Value::Str(string) => string.into(),
+}
+
+castable! {
+ LayoutNode,
+ Expected: "content",
+ Value::None => Self::default(),
+ Value::Str(text) => Content::Text(text).pack(),
+ Value::Content(content) => content.pack(),
+}
+
+castable! {
+ Pattern,
+ Expected: "function, string or regular expression",
+ Value::Func(func) => Self::Node(func.node()?),
+ Value::Str(text) => Self::text(&text),
+ @regex: Regex => Self::Regex(regex.clone()),
+}
+
impl<T: Cast> Cast for Option<T> {
fn is(value: &Value) -> bool {
matches!(value, Value::None) || T::is(value)
@@ -609,112 +675,84 @@ impl<T: Cast> Cast for Smart<T> {
impl<T> Cast for Sides<T>
where
- T: Cast + Default + Clone,
+ T: Cast + Default + Copy,
{
fn is(value: &Value) -> bool {
matches!(value, Value::Dict(_)) || T::is(value)
}
- fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::Dict(dict) => {
- for (key, _) in &dict {
- if !matches!(
- key.as_str(),
- "left" | "top" | "right" | "bottom" | "x" | "y" | "rest"
- ) {
- return Err(format!("unexpected key {key:?}"));
- }
- }
+ fn cast(mut value: Value) -> StrResult<Self> {
+ if let Value::Dict(dict) = &mut value {
+ let mut take = |key| dict.take(key).map(T::cast).transpose();
- let sides = Sides {
- left: dict.get("left").or(dict.get("x")),
- top: dict.get("top").or(dict.get("y")),
- right: dict.get("right").or(dict.get("x")),
- bottom: dict.get("bottom").or(dict.get("y")),
- };
+ let rest = take("rest")?;
+ let x = take("x")?.or(rest);
+ let y = take("y")?.or(rest);
+ let sides = Sides {
+ left: take("left")?.or(x),
+ top: take("top")?.or(y),
+ right: take("right")?.or(x),
+ bottom: take("bottom")?.or(y),
+ };
- Ok(sides.map(|side| {
- side.or(dict.get("rest"))
- .cloned()
- .and_then(T::cast)
- .unwrap_or_default()
- }))
+ if let Some((key, _)) = dict.iter().next() {
+ return Err(format!("unexpected key {key:?}"));
}
- v => T::cast(v).map(Sides::splat).map_err(|msg| {
+
+ Ok(sides.map(Option::unwrap_or_default))
+ } else {
+ T::cast(value).map(Self::splat).map_err(|msg| {
with_alternative(
msg,
- "dictionary with any of `left`, `top`, `right`, `bottom`, \
+ "dictionary with any of \
+ `left`, `top`, `right`, `bottom`, \
`x`, `y`, or `rest` as keys",
)
- }),
+ })
}
}
}
-dynamic! {
- Dir: "direction",
-}
-
-dynamic! {
- Regex: "regular expression",
-}
+impl<T> Cast for Corners<T>
+where
+ T: Cast + Default + Copy,
+{
+ fn is(value: &Value) -> bool {
+ matches!(value, Value::Dict(_)) || T::is(value)
+ }
-dynamic! {
- Group: "group",
-}
+ fn cast(mut value: Value) -> StrResult<Self> {
+ if let Value::Dict(dict) = &mut value {
+ let mut take = |key| dict.take(key).map(T::cast).transpose();
+
+ let rest = take("rest")?;
+ let left = take("left")?.or(rest);
+ let top = take("top")?.or(rest);
+ let right = take("right")?.or(rest);
+ let bottom = take("bottom")?.or(rest);
+ let corners = Corners {
+ top_left: take("top-left")?.or(top).or(left),
+ top_right: take("top-right")?.or(top).or(right),
+ bottom_right: take("bottom-right")?.or(bottom).or(right),
+ bottom_left: take("bottom-left")?.or(bottom).or(left),
+ };
+
+ if let Some((key, _)) = dict.iter().next() {
+ return Err(format!("unexpected key {key:?}"));
+ }
-castable! {
- usize,
- Expected: "non-negative integer",
- Value::Int(int) => int.try_into().map_err(|_| {
- if int < 0 {
- "must be at least zero"
+ Ok(corners.map(Option::unwrap_or_default))
} else {
- "number too large"
+ T::cast(value).map(Self::splat).map_err(|msg| {
+ with_alternative(
+ msg,
+ "dictionary with any of \
+ `top-left`, `top-right`, `bottom-right`, `bottom-left`, \
+ `left`, `top`, `right`, `bottom`, or `rest` as keys",
+ )
+ })
}
- })?,
-}
-
-castable! {
- NonZeroUsize,
- Expected: "positive integer",
- Value::Int(int) => int
- .try_into()
- .and_then(|int: usize| int.try_into())
- .map_err(|_| if int <= 0 {
- "must be positive"
- } else {
- "number too large"
- })?,
-}
-
-castable! {
- Paint,
- Expected: "color",
- Value::Color(color) => Paint::Solid(color),
-}
-
-castable! {
- String,
- Expected: "string",
- Value::Str(string) => string.into(),
-}
-
-castable! {
- LayoutNode,
- Expected: "content",
- Value::None => Self::default(),
- Value::Str(text) => Content::Text(text).pack(),
- Value::Content(content) => content.pack(),
-}
-
-castable! {
- Pattern,
- Expected: "function, string or regular expression",
- Value::Func(func) => Self::Node(func.node()?),
- Value::Str(text) => Self::text(&text),
- @regex: Regex => Self::Regex(regex.clone()),
+ }
}
#[cfg(test)]