summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-22 14:30:43 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-29 16:47:35 +0100
commited50661378f356e02c6ec943bc4840091d33cfbd (patch)
tree7ed51339ea1a4b7ccc4308c902b36e86f9c07e26 /src/eval
parentcef46e6c40fed0089a20e44ff2f251c06878891c (diff)
Castable optional and smart values
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/value.rs144
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::*;