diff options
Diffstat (limited to 'src/eval/value.rs')
| -rw-r--r-- | src/eval/value.rs | 481 |
1 files changed, 300 insertions, 181 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs index a009e891..d1dcdcfa 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -1,25 +1,21 @@ //! Computational values. +use std::any::Any; +use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use std::ops::Deref; use std::rc::Rc; -use fontdock::{FontStretch, FontStyle, FontWeight}; - -use super::{Args, Dict, Eval, EvalContext, SpannedEntry}; +use super::{Args, Eval, EvalContext}; use crate::color::Color; -use crate::diag::Diag; -use crate::geom::{Dir, Length, Linear, Relative}; -use crate::paper::Paper; -use crate::syntax::{Ident, Spanned, SynTree, WithSpan}; +use crate::geom::{Length, Linear, Relative}; +use crate::syntax::{Spanned, SynTree, WithSpan}; /// A computational value. #[derive(Clone, PartialEq)] pub enum Value { /// The value that indicates the absence of a meaningful value. None, - /// An identifier: `ident`. - Ident(Ident), /// A boolean: `true, false`. Bool(bool), /// An integer: `120`. @@ -36,34 +32,46 @@ pub enum Value { Color(Color), /// A string: `"string"`. Str(String), - /// A dictionary value: `(false, 12cm, greeting: "hi")`. + /// An array value: `(1, "hi", 12cm)`. + Array(ValueArray), + /// A dictionary value: `(color: #f79143, pattern: dashed)`. Dict(ValueDict), /// A content value: `{*Hi* there}`. - Content(SynTree), + Content(ValueContent), /// An executable function. Func(ValueFunc), + /// Any object. + Any(ValueAny), /// The result of invalid operations. Error, } impl Value { - /// The natural-language name of this value's type for use in error - /// messages. - pub fn ty(&self) -> &'static str { + /// Try to cast the value into a specific type. + pub fn cast<T>(self) -> CastResult<T, Self> + where + T: Cast<Value>, + { + T::cast(self) + } + + /// The name of the stored value's type. + pub fn type_name(&self) -> &'static str { match self { Self::None => "none", - Self::Ident(_) => "identifier", - Self::Bool(_) => "bool", - Self::Int(_) => "integer", - Self::Float(_) => "float", - Self::Relative(_) => "relative", - Self::Length(_) => "length", - Self::Linear(_) => "linear", - Self::Color(_) => "color", - Self::Str(_) => "string", - Self::Dict(_) => "dict", - Self::Content(_) => "content", - Self::Func(_) => "function", + Self::Bool(_) => bool::TYPE_NAME, + Self::Int(_) => i64::TYPE_NAME, + Self::Float(_) => f64::TYPE_NAME, + Self::Relative(_) => Relative::TYPE_NAME, + Self::Length(_) => Length::TYPE_NAME, + Self::Linear(_) => Linear::TYPE_NAME, + Self::Color(_) => Color::TYPE_NAME, + Self::Str(_) => String::TYPE_NAME, + Self::Array(_) => ValueArray::TYPE_NAME, + Self::Dict(_) => ValueDict::TYPE_NAME, + Self::Content(_) => ValueContent::TYPE_NAME, + Self::Func(_) => ValueFunc::TYPE_NAME, + Self::Any(v) => v.type_name(), Self::Error => "error", } } @@ -81,13 +89,6 @@ impl Eval for &Value { // Pass through. Value::Content(tree) => tree.eval(ctx), - // Forward to each dictionary entry. - Value::Dict(dict) => { - for entry in dict.values() { - entry.value.v.eval(ctx); - } - } - // Format with debug. val => ctx.push(ctx.make_text_node(format!("{:?}", val))), } @@ -104,7 +105,6 @@ impl Debug for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::None => f.pad("none"), - Self::Ident(v) => v.fmt(f), Self::Bool(v) => v.fmt(f), Self::Int(v) => v.fmt(f), Self::Float(v) => v.fmt(f), @@ -113,45 +113,36 @@ impl Debug for Value { Self::Linear(v) => v.fmt(f), Self::Color(v) => v.fmt(f), Self::Str(v) => v.fmt(f), + Self::Array(v) => v.fmt(f), Self::Dict(v) => v.fmt(f), Self::Content(v) => v.fmt(f), Self::Func(v) => v.fmt(f), + Self::Any(v) => v.fmt(f), Self::Error => f.pad("<error>"), } } } -/// A dictionary of values. -/// -/// # Example -/// ```typst -/// (false, 12cm, greeting: "hi") -/// ``` -pub type ValueDict = Dict<SpannedEntry<Value>>; +/// An array value: `(1, "hi", 12cm)`. +pub type ValueArray = Vec<Value>; -/// An wrapper around a reference-counted function trait object. -/// -/// The dynamic function object is wrapped in an `Rc` to keep [`Value`] -/// cloneable. -/// -/// _Note_: This is needed because the compiler can't `derive(PartialEq)` for -/// [`Value`] when directly putting the `Rc` in there, see the [Rust -/// Issue]. -/// -/// [Rust Issue]: https://github.com/rust-lang/rust/issues/31740 -#[derive(Clone)] -pub struct ValueFunc(pub Rc<Func>); +/// A dictionary value: `(color: #f79143, pattern: dashed)`. +pub type ValueDict = HashMap<String, Value>; + +/// A content value: `{*Hi* there}`. +pub type ValueContent = SynTree; -/// The signature of executable functions. -type Func = dyn Fn(Args, &mut EvalContext) -> Value; +/// A wrapper around a reference-counted executable function. +#[derive(Clone)] +pub struct ValueFunc(Rc<dyn Fn(&mut EvalContext, &mut Args) -> Value>); impl ValueFunc { /// Create a new function value from a rust function or closure. - pub fn new<F>(f: F) -> Self + pub fn new<F>(func: F) -> Self where - F: Fn(Args, &mut EvalContext) -> Value + 'static, + F: Fn(&mut EvalContext, &mut Args) -> Value + 'static, { - Self(Rc::new(f)) + Self(Rc::new(func)) } } @@ -162,7 +153,7 @@ impl PartialEq for ValueFunc { } impl Deref for ValueFunc { - type Target = Func; + type Target = dyn Fn(&mut EvalContext, &mut Args) -> Value; fn deref(&self) -> &Self::Target { self.0.as_ref() @@ -175,160 +166,288 @@ impl Debug for ValueFunc { } } -/// Try to convert a value into a more specific type. -pub trait TryFromValue: Sized { - /// Try to convert the value into yourself. - fn try_from_value(value: Spanned<Value>) -> Conv<Self>; +/// A wrapper around a dynamic value. +pub struct ValueAny(Box<dyn Bounds>); + +impl ValueAny { + /// Create a new instance from any value that satisifies the required bounds. + pub fn new<T>(any: T) -> Self + where + T: Type + Debug + Clone + PartialEq + 'static, + { + Self(Box::new(any)) + } + + /// Whether the wrapped type is `T`. + pub fn is<T: 'static>(&self) -> bool { + self.0.as_any().is::<T>() + } + + /// Try to downcast to a specific type. + pub fn downcast<T: 'static>(self) -> Result<T, Self> { + if self.is::<T>() { + Ok(*self.0.into_any().downcast().unwrap()) + } else { + Err(self) + } + } + + /// Try to downcast to a reference to a specific type. + pub fn downcast_ref<T: 'static>(&self) -> Option<&T> { + self.0.as_any().downcast_ref() + } + + /// The name of the stored object's type. + pub fn type_name(&self) -> &'static str { + self.0.dyn_type_name() + } } -/// The result of a conversion. -#[derive(Debug, Clone, PartialEq)] -pub enum Conv<T> { - /// Success conversion. - Ok(T), - /// Sucessful conversion with a warning. - Warn(T, Diag), - /// Unsucessful conversion, gives back the value alongside the error. - Err(Value, Diag), +impl Clone for ValueAny { + fn clone(&self) -> Self { + Self(self.0.dyn_clone()) + } } -impl<T> Conv<T> { - /// Map the conversion result. - pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Conv<U> { - match self { - Conv::Ok(t) => Conv::Ok(f(t)), - Conv::Warn(t, warn) => Conv::Warn(f(t), warn), - Conv::Err(v, err) => Conv::Err(v, err), - } +impl PartialEq for ValueAny { + fn eq(&self, other: &Self) -> bool { + self.0.dyn_eq(other) } } -impl<T: TryFromValue> TryFromValue for Spanned<T> { - fn try_from_value(value: Spanned<Value>) -> Conv<Self> { - let span = value.span; - T::try_from_value(value).map(|v| v.with_span(span)) +impl Debug for ValueAny { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) } } -/// A value type that matches [identifier](Value::Ident) and [string](Value::Str) values. -pub struct StringLike(pub String); +trait Bounds: Debug + 'static { + fn as_any(&self) -> &dyn Any; + fn into_any(self: Box<Self>) -> Box<dyn Any>; + fn dyn_eq(&self, other: &ValueAny) -> bool; + fn dyn_clone(&self) -> Box<dyn Bounds>; + fn dyn_type_name(&self) -> &'static str; +} -impl From<StringLike> for String { - fn from(like: StringLike) -> String { - like.0 +impl<T> Bounds for T +where + T: Type + Debug + Clone + PartialEq + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn into_any(self: Box<Self>) -> Box<dyn Any> { + self + } + + fn dyn_eq(&self, other: &ValueAny) -> bool { + if let Some(other) = other.downcast_ref::<Self>() { + self == other + } else { + false + } + } + + fn dyn_clone(&self) -> Box<dyn Bounds> { + Box::new(self.clone()) + } + + fn dyn_type_name(&self) -> &'static str { + T::TYPE_NAME } } -impl Deref for StringLike { - type Target = str; +/// Types that can be stored in values. +pub trait Type { + /// The name of the type. + const TYPE_NAME: &'static str; +} - fn deref(&self) -> &str { - self.0.as_str() +impl<T> Type for Spanned<T> +where + T: Type, +{ + const TYPE_NAME: &'static str = T::TYPE_NAME; +} + +/// Cast from a value to a specific type. +pub trait Cast<V>: Type + Sized { + /// Try to cast the value into an instance of `Self`. + fn cast(value: V) -> CastResult<Self, V>; +} + +/// The result of casting a value to a specific type. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum CastResult<T, V> { + /// The value was cast successfully. + Ok(T), + /// The value was cast successfully, but with a warning message. + Warn(T, String), + /// The value could not be cast into the specified type. + Err(V), +} + +impl<T, V> CastResult<T, V> { + /// Access the conversion resulting, discarding a possibly existing warning. + pub fn ok(self) -> Option<T> { + match self { + CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t), + CastResult::Err(_) => None, + } } } -/// Implement [`TryFromValue`] through a match. -macro_rules! try_from_match { - ($type:ty[$name:literal] $(@ $span:ident)?: $($pattern:pat => $output:expr),* $(,)?) => { - impl $crate::eval::TryFromValue for $type { - fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> { - use $crate::eval::Conv; - #[allow(unused)] - $(let $span = value.span;)? - #[allow(unreachable_patterns)] - match value.v { - $($pattern => Conv::Ok($output)),*, - v => { - let e = error!("expected {}, found {}", $name, v.ty()); - Conv::Err(v, e) - } - } - } +impl<T> Cast<Spanned<Value>> for T +where + T: Cast<Value>, +{ + fn cast(value: Spanned<Value>) -> CastResult<Self, Spanned<Value>> { + let span = value.span; + match T::cast(value.v) { + CastResult::Ok(t) => CastResult::Ok(t), + CastResult::Warn(t, m) => CastResult::Warn(t, m), + CastResult::Err(v) => CastResult::Err(v.with_span(span)), } - }; + } +} + +impl<T> Cast<Spanned<Value>> for Spanned<T> +where + T: Cast<Value>, +{ + fn cast(value: Spanned<Value>) -> CastResult<Self, Spanned<Value>> { + let span = value.span; + match T::cast(value.v) { + CastResult::Ok(t) => CastResult::Ok(t.with_span(span)), + CastResult::Warn(t, m) => CastResult::Warn(t.with_span(span), m), + CastResult::Err(v) => CastResult::Err(v.with_span(span)), + } + } } -/// Implement [`TryFromValue`] through a function parsing an identifier. -macro_rules! try_from_id { - ($type:ty[$name:literal]: $from_str:expr) => { - impl $crate::eval::TryFromValue for $type { - fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> { - use $crate::eval::Conv; - let v = value.v; - if let Value::Ident(id) = v { - if let Some(v) = $from_str(&id) { - Conv::Ok(v) - } else { - Conv::Err(Value::Ident(id), error!("invalid {}", $name)) - } - } else { - let e = error!("expected identifier, found {}", v.ty()); - Conv::Err(v, e) +macro_rules! impl_primitive { + ($type:ty: + $type_name:literal, + $variant:path + $(, $pattern:pat => $out:expr)* $(,)? + ) => { + impl Type for $type { + const TYPE_NAME: &'static str = $type_name; + } + + impl From<$type> for Value { + fn from(v: $type) -> Self { + $variant(v) + } + } + + impl Cast<Value> for $type { + fn cast(value: Value) -> CastResult<Self, Value> { + match value { + $variant(v) => CastResult::Ok(v), + $($pattern => CastResult::Ok($out),)* + v => CastResult::Err(v), } } } }; } -try_from_match!(Value["value"]: v => v); -try_from_match!(Ident["identifier"]: Value::Ident(v) => v); -try_from_match!(bool["bool"]: Value::Bool(v) => v); -try_from_match!(i64["integer"]: Value::Int(v) => v); -try_from_match!(f64["float"]: +impl_primitive! { bool: "boolean", Value::Bool } +impl_primitive! { i64: "integer", Value::Int } +impl_primitive! { Length: "length", Value::Length } +impl_primitive! { Relative: "relative", Value::Relative } +impl_primitive! { Color: "color", Value::Color } +impl_primitive! { String: "string", Value::Str } +impl_primitive! { ValueArray: "array", Value::Array } +impl_primitive! { ValueDict: "dictionary", Value::Dict } +impl_primitive! { ValueContent: "content", Value::Content } +impl_primitive! { ValueFunc: "function", Value::Func } + +impl_primitive! { + f64: "float", + Value::Float, Value::Int(v) => v as f64, - Value::Float(v) => v, -); -try_from_match!(Length["length"]: Value::Length(v) => v); -try_from_match!(Relative["relative"]: Value::Relative(v) => v); -try_from_match!(Linear["linear"]: - Value::Linear(v) => v, +} + +impl_primitive! { + Linear: "linear", + Value::Linear, Value::Length(v) => v.into(), Value::Relative(v) => v.into(), -); -try_from_match!(Color["color"]: Value::Color(v) => v); -try_from_match!(String["string"]: Value::Str(v) => v); -try_from_match!(SynTree["tree"]: Value::Content(v) => v); -try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v); -try_from_match!(ValueFunc["function"]: Value::Func(v) => v); -try_from_match!(StringLike["identifier or string"]: - Value::Ident(Ident(v)) => Self(v), - Value::Str(v) => Self(v), -); -try_from_id!(Dir["direction"]: |v| match v { - "ltr" | "left-to-right" => Some(Self::LTR), - "rtl" | "right-to-left" => Some(Self::RTL), - "ttb" | "top-to-bottom" => Some(Self::TTB), - "btt" | "bottom-to-top" => Some(Self::BTT), - _ => None, -}); -try_from_id!(FontStyle["font style"]: Self::from_str); -try_from_id!(FontStretch["font stretch"]: Self::from_str); -try_from_id!(Paper["paper"]: Self::from_name); - -impl TryFromValue for FontWeight { - fn try_from_value(value: Spanned<Value>) -> Conv<Self> { - match value.v { - Value::Int(number) => { - let [min, max] = [Self::THIN, Self::BLACK]; - if number < i64::from(min.to_number()) { - Conv::Warn(min, warning!("the minimum font weight is {:#?}", min)) - } else if number > i64::from(max.to_number()) { - Conv::Warn(max, warning!("the maximum font weight is {:#?}", max)) - } else { - Conv::Ok(Self::from_number(number as u16)) - } +} + +impl From<&str> for Value { + fn from(v: &str) -> Self { + Self::Str(v.to_string()) + } +} + +impl<F> From<F> for Value +where + F: Fn(&mut EvalContext, &mut Args) -> Value + 'static, +{ + fn from(func: F) -> Self { + Self::Func(ValueFunc::new(func)) + } +} + +impl From<ValueAny> for Value { + fn from(v: ValueAny) -> Self { + Self::Any(v) + } +} + +/// Make a type usable with [`ValueAny`]. +/// +/// Given a type `T`, this implements the following traits: +/// - [`Type`] for `T`, +/// - [`From<T>`](From) for [`Value`], +/// - [`Cast<Value>`](Cast) for `T`. +#[macro_export] +macro_rules! impl_type { + ($type:ty: + $type_name:literal + $(, $pattern:pat => $out:expr)* + $(, #($anyvar:ident: $anytype:ty) => $anyout:expr)* + $(,)? + ) => { + impl $crate::eval::Type for $type { + const TYPE_NAME: &'static str = $type_name; + } + + impl From<$type> for $crate::eval::Value { + fn from(any: $type) -> Self { + $crate::eval::Value::Any($crate::eval::ValueAny::new(any)) } - Value::Ident(id) => { - if let Some(weight) = Self::from_str(&id) { - Conv::Ok(weight) - } else { - Conv::Err(Value::Ident(id), error!("invalid font weight")) + } + + impl $crate::eval::Cast<$crate::eval::Value> for $type { + fn cast( + value: $crate::eval::Value, + ) -> $crate::eval::CastResult<Self, $crate::eval::Value> { + use $crate::eval::*; + + #[allow(unreachable_code)] + match value { + $($pattern => CastResult::Ok($out),)* + Value::Any(mut any) => { + any = match any.downcast::<Self>() { + Ok(t) => return CastResult::Ok(t), + Err(any) => any, + }; + + $(any = match any.downcast::<$anytype>() { + Ok($anyvar) => return CastResult::Ok($anyout), + Err(any) => any, + };)* + + CastResult::Err(Value::Any(any)) + }, + v => CastResult::Err(v), } } - v => { - let e = error!("expected font weight, found {}", v.ty()); - Conv::Err(v, e) - } } - } + }; } |
