diff --git a/src/eval/cast.rs b/src/eval/cast.rs new file mode 100644 index 000000000..063d378a4 --- /dev/null +++ b/src/eval/cast.rs @@ -0,0 +1,358 @@ +use std::num::NonZeroUsize; + +use super::{Regex, Value}; +use crate::diag::{with_alternative, StrResult}; +use crate::geom::{Corners, Dir, Paint, Sides}; +use crate::model::{Content, Group, Layout, LayoutNode, Pattern}; +use crate::syntax::Spanned; + +/// Cast from a value to a specific type. +pub trait Cast: Sized { + /// Check whether the value is castable to `Self`. + fn is(value: &V) -> bool; + + /// Try to cast the value into an instance of `Self`. + fn cast(value: V) -> StrResult; +} + +/// Implement traits for dynamic types. +macro_rules! dynamic { + ($type:ty: $name:literal, $($tts:tt)*) => { + impl $crate::eval::Type for $type { + const TYPE_NAME: &'static str = $name; + } + + castable! { + $type, + Expected: ::TYPE_NAME, + $($tts)* + @this: Self => this.clone(), + } + + impl From<$type> for $crate::eval::Value { + fn from(v: $type) -> Self { + $crate::eval::Value::Dyn($crate::eval::Dynamic::new(v)) + } + } + }; +} + +/// Make a type castable from a value. +macro_rules! castable { + ($type:ty: $inner:ty) => { + impl $crate::eval::Cast<$crate::eval::Value> for $type { + fn is(value: &$crate::eval::Value) -> bool { + <$inner>::is(value) + } + + fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult { + <$inner>::cast(value).map(Self) + } + } + }; + + ( + $type:ty, + Expected: $expected:expr, + $($pattern:pat => $out:expr,)* + $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* + ) => { + #[allow(unreachable_patterns)] + impl $crate::eval::Cast<$crate::eval::Value> for $type { + fn is(value: &$crate::eval::Value) -> bool { + #[allow(unused_variables)] + match value { + $($pattern => true,)* + $crate::eval::Value::Dyn(dynamic) => { + false $(|| dynamic.is::<$dyn_type>())* + } + _ => false, + } + } + + fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult { + let found = match value { + $($pattern => return Ok($out),)* + $crate::eval::Value::Dyn(dynamic) => { + $(if let Some($dyn_in) = dynamic.downcast::<$dyn_type>() { + return Ok($dyn_out); + })* + dynamic.type_name() + } + v => v.type_name(), + }; + + Err(format!("expected {}, found {}", $expected, found)) + } + } + }; +} + +impl Cast for Value { + fn is(_: &Value) -> bool { + true + } + + fn cast(value: Value) -> StrResult { + Ok(value) + } +} + +impl Cast> for T { + fn is(value: &Spanned) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + T::cast(value.v) + } +} + +impl Cast> for Spanned { + fn is(value: &Spanned) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + let span = value.span; + T::cast(value.v).map(|t| Spanned::new(t, span)) + } +} + +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.into()).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 Cast for Option { + fn is(value: &Value) -> bool { + matches!(value, Value::None) || T::is(value) + } + + fn cast(value: Value) -> StrResult { + match value { + Value::None => Ok(None), + v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")), + } + } +} + +/// A value that can be automatically determined. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Smart { + /// The value should be determined smartly based on the circumstances. + Auto, + /// A specific value. + Custom(T), +} + +impl Smart { + /// Map the contained custom value with `f`. + pub fn map(self, f: F) -> Smart + where + F: FnOnce(T) -> U, + { + match self { + Self::Auto => Smart::Auto, + Self::Custom(x) => Smart::Custom(f(x)), + } + } + + /// Keeps `self` if it contains a custom value, otherwise returns `other`. + pub fn or(self, other: Smart) -> Self { + match self { + Self::Custom(x) => Self::Custom(x), + Self::Auto => other, + } + } + + /// Returns the contained custom value or a provided default value. + pub fn unwrap_or(self, default: T) -> T { + match self { + Self::Auto => default, + Self::Custom(x) => x, + } + } + + /// Returns the contained custom value or computes a default value. + pub fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T, + { + match self { + Self::Auto => f(), + Self::Custom(x) => x, + } + } + + /// Returns the contained custom value or the default value. + pub fn unwrap_or_default(self) -> T + where + T: Default, + { + self.unwrap_or_else(T::default) + } +} + +impl Default for Smart { + fn default() -> Self { + Self::Auto + } +} + +impl Cast for Smart { + fn is(value: &Value) -> bool { + matches!(value, Value::Auto) || T::is(value) + } + + fn cast(value: Value) -> StrResult { + match value { + Value::Auto => Ok(Self::Auto), + v => T::cast(v) + .map(Self::Custom) + .map_err(|msg| with_alternative(msg, "auto")), + } + } +} + +impl Cast for Sides +where + T: Cast + Default + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult { + if let Value::Dict(dict) = &mut value { + let mut take = |key| dict.take(key).map(T::cast).transpose(); + + 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), + }; + + if let Some((key, _)) = dict.iter().next() { + return Err(format!("unexpected key {key:?}")); + } + + 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`, \ + `x`, `y`, or `rest` as keys", + ) + }) + } + } +} + +impl Cast for Corners +where + T: Cast + Default + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult { + 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:?}")); + } + + Ok(corners.map(Option::unwrap_or_default)) + } else { + 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", + ) + }) + } + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e2090cd22..ab92c8f37 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,5 +1,7 @@ //! Evaluation of markup into modules. +#[macro_use] +mod cast; #[macro_use] mod array; #[macro_use] @@ -21,6 +23,7 @@ pub use self::str::*; pub use args::*; pub use array::*; pub use capture::*; +pub use cast::*; pub use dict::*; pub use func::*; pub use machine::*; diff --git a/src/eval/value.rs b/src/eval/value.rs index 294aac946..cdd403a77 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -2,18 +2,13 @@ use std::any::Any; use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; use std::sync::Arc; -use super::{ops, Args, Array, Dict, Func, RawLength, Regex}; -use crate::diag::{with_alternative, StrResult}; -use crate::geom::{ - Angle, Color, Corners, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, - Sides, -}; +use super::{ops, Args, Array, Cast, Dict, Func, RawLength}; +use crate::diag::StrResult; +use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor}; use crate::library::text::RawNode; -use crate::model::{Content, Group, Layout, LayoutNode, Pattern}; -use crate::syntax::Spanned; +use crate::model::{Content, Layout}; use crate::util::EcoString; /// A computational value. @@ -340,15 +335,6 @@ pub trait Type { const TYPE_NAME: &'static str; } -/// Cast from a value to a specific type. -pub trait Cast: Sized { - /// Check whether the value is castable to `Self`. - fn is(value: &V) -> bool; - - /// Try to cast the value into an instance of `Self`. - fn cast(value: V) -> StrResult; -} - /// Implement traits for primitives. macro_rules! primitive { ( @@ -389,79 +375,6 @@ macro_rules! primitive { (@$other:ident) => { Value::$other }; } -/// Implement traits for dynamic types. -macro_rules! dynamic { - ($type:ty: $name:literal, $($tts:tt)*) => { - impl $crate::eval::Type for $type { - const TYPE_NAME: &'static str = $name; - } - - castable! { - $type, - Expected: ::TYPE_NAME, - $($tts)* - @this: Self => this.clone(), - } - - impl From<$type> for $crate::eval::Value { - fn from(v: $type) -> Self { - $crate::eval::Value::Dyn($crate::eval::Dynamic::new(v)) - } - } - }; -} - -/// Make a type castable from a value. -macro_rules! castable { - ($type:ty: $inner:ty) => { - impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &$crate::eval::Value) -> bool { - <$inner>::is(value) - } - - fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult { - <$inner>::cast(value).map(Self) - } - } - }; - - ( - $type:ty, - Expected: $expected:expr, - $($pattern:pat => $out:expr,)* - $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* - ) => { - #[allow(unreachable_patterns)] - impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &$crate::eval::Value) -> bool { - #[allow(unused_variables)] - match value { - $($pattern => true,)* - $crate::eval::Value::Dyn(dynamic) => { - false $(|| dynamic.is::<$dyn_type>())* - } - _ => false, - } - } - - fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult { - let found = match value { - $($pattern => return Ok($out),)* - $crate::eval::Value::Dyn(dynamic) => { - $(if let Some($dyn_in) = dynamic.downcast::<$dyn_type>() { - return Ok($dyn_out); - })* - dynamic.type_name() - } - v => v.type_name(), - }; - - Err(format!("expected {}, found {}", $expected, found)) - } - } - }; -} - primitive! { bool: "boolean", Bool } primitive! { i64: "integer", Int } primitive! { f64: "float", Float, Int(v) => v as f64 } @@ -486,275 +399,6 @@ primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } primitive! { Args: "arguments", Args } -impl Cast for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult { - Ok(value) - } -} - -impl Cast> for T { - fn is(value: &Spanned) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - T::cast(value.v) - } -} - -impl Cast> for Spanned { - fn is(value: &Spanned) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) - } -} - -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 Cast for Option { - fn is(value: &Value) -> bool { - matches!(value, Value::None) || T::is(value) - } - - fn cast(value: Value) -> StrResult { - match value { - Value::None => Ok(None), - v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")), - } - } -} - -/// A value that can be automatically determined. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Smart { - /// The value should be determined smartly based on the circumstances. - Auto, - /// A specific value. - Custom(T), -} - -impl Smart { - /// Map the contained custom value with `f`. - pub fn map(self, f: F) -> Smart - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => Smart::Auto, - Self::Custom(x) => Smart::Custom(f(x)), - } - } - - /// Keeps `self` if it contains a custom value, otherwise returns `other`. - pub fn or(self, other: Smart) -> Self { - match self { - Self::Custom(x) => Self::Custom(x), - Self::Auto => other, - } - } - - /// Returns the contained custom value or a provided default value. - pub fn unwrap_or(self, default: T) -> T { - match self { - Self::Auto => default, - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or computes a default value. - pub fn unwrap_or_else(self, f: F) -> T - where - F: FnOnce() -> T, - { - match self { - Self::Auto => f(), - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or the default value. - pub fn unwrap_or_default(self) -> T - where - T: Default, - { - self.unwrap_or_else(T::default) - } -} - -impl Default for Smart { - fn default() -> Self { - Self::Auto - } -} - -impl Cast for Smart { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) || T::is(value) - } - - fn cast(value: Value) -> StrResult { - match value { - Value::Auto => Ok(Self::Auto), - v => T::cast(v) - .map(Self::Custom) - .map_err(|msg| with_alternative(msg, "auto")), - } - } -} - -impl Cast for Sides -where - T: Cast + Default + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).map(T::cast).transpose(); - - 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), - }; - - if let Some((key, _)) = dict.iter().next() { - return Err(format!("unexpected key {key:?}")); - } - - 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`, \ - `x`, `y`, or `rest` as keys", - ) - }) - } - } -} - -impl Cast for Corners -where - T: Cast + Default + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult { - 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:?}")); - } - - Ok(corners.map(Option::unwrap_or_default)) - } else { - 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", - ) - }) - } - } -} - #[cfg(test)] mod tests { use super::*;