diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-11-22 14:30:43 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-11-29 16:47:35 +0100 |
| commit | ed50661378f356e02c6ec943bc4840091d33cfbd (patch) | |
| tree | 7ed51339ea1a4b7ccc4308c902b36e86f9c07e26 /src/eval | |
| parent | cef46e6c40fed0089a20e44ff2f251c06878891c (diff) | |
Castable optional and smart values
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/value.rs | 144 |
1 files changed, 107 insertions, 37 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs index e224438a..dec5c6c0 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -252,43 +252,6 @@ pub trait Cast<V>: Sized { fn cast(value: V) -> StrResult<Self>; } -impl Cast<Value> for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult<Self> { - Ok(value) - } -} - -impl<T> Cast<Spanned<Value>> for T -where - T: Cast<Value>, -{ - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - T::cast(value.v) - } -} - -impl<T> Cast<Spanned<Value>> for Spanned<T> -where - T: Cast<Value>, -{ - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) - } -} - /// Implement traits for primitives. macro_rules! primitive { ( @@ -400,6 +363,113 @@ primitive! { Dict: "dictionary", Dict } primitive! { Template: "template", Template } primitive! { Function: "function", Func } +impl Cast<Value> for Value { + fn is(_: &Value) -> bool { + true + } + + fn cast(value: Value) -> StrResult<Self> { + Ok(value) + } +} + +impl<T> Cast<Spanned<Value>> for T +where + T: Cast<Value>, +{ + fn is(value: &Spanned<Value>) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + T::cast(value.v) + } +} + +impl<T> Cast<Spanned<Value>> for Spanned<T> +where + T: Cast<Value>, +{ + fn is(value: &Spanned<Value>) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + let span = value.span; + T::cast(value.v).map(|t| Spanned::new(t, span)) + } +} + +/// A value that can be automatically determined. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Smart<T> { + /// The value should be determined smartly based on the + /// circumstances. + Auto, + /// A forced, specific value. + Custom(T), +} + +impl<T> Smart<T> { + /// 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, + } + } +} + +impl<T> Default for Smart<T> { + fn default() -> Self { + Self::Auto + } +} + +impl<T> Cast<Value> for Option<T> +where + T: Cast<Value>, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::None) || T::is(value) + } + + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::None => Ok(None), + v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")), + } + } +} + +impl<T> Cast<Value> for Smart<T> +where + T: Cast<Value>, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Auto) || T::is(value) + } + + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::Auto => Ok(Self::Auto), + v => T::cast(v) + .map(Self::Custom) + .map_err(|msg| with_alternative(msg, "auto")), + } + } +} + +/// Transform `expected X, found Y` into `expected X or A, found Y`. +fn with_alternative(msg: String, alt: &str) -> String { + let mut parts = msg.split(", found "); + if let (Some(a), Some(b)) = (parts.next(), parts.next()) { + format!("{} or {}, found {}", a, alt, b) + } else { + msg + } +} + #[cfg(test)] mod tests { use super::*; |
