From ed50661378f356e02c6ec943bc4840091d33cfbd Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 22 Nov 2021 14:30:43 +0100 Subject: Castable optional and smart values --- src/eval/value.rs | 144 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 107 insertions(+), 37 deletions(-) (limited to 'src/eval/value.rs') 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: Sized { fn cast(value: V) -> StrResult; } -impl Cast for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult { - Ok(value) - } -} - -impl Cast> for T -where - T: Cast, -{ - fn is(value: &Spanned) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - T::cast(value.v) - } -} - -impl Cast> for Spanned -where - T: Cast, -{ - 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)) - } -} - /// 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 for Value { + fn is(_: &Value) -> bool { + true + } + + fn cast(value: Value) -> StrResult { + Ok(value) + } +} + +impl Cast> for T +where + T: Cast, +{ + fn is(value: &Spanned) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + T::cast(value.v) + } +} + +impl Cast> for Spanned +where + T: Cast, +{ + 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)) + } +} + +/// 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 forced, specific value. + Custom(T), +} + +impl Smart { + /// 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 Default for Smart { + fn default() -> Self { + Self::Auto + } +} + +impl Cast for Option +where + T: Cast, +{ + 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")), + } + } +} + +impl Cast for Smart +where + T: Cast, +{ + 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")), + } + } +} + +/// 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::*; -- cgit v1.2.3