summaryrefslogtreecommitdiff
path: root/src/eval/value.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
commit1c40dc42e7bc7b799b77f06d25414aca59a044ba (patch)
treeea8bdedaebf59f5bc601346b0108236c7264a29d /src/eval/value.rs
parent8cad78481cd52680317032c3bb84cacda5666489 (diff)
Dynamic values, Types, Arrays, and Dictionaries 🚀
- Identifiers are now evaluated as variables instead of being plain values - Constants like `left` or `bold` are stored as dynamic values containing the respective rust types - We now distinguish between arrays and dictionaries to make things more intuitive (at the cost of a bit more complex parsing) - Spans were removed from collections (arrays, dictionaries), function arguments still have spans for the top-level values to enable good diagnostics
Diffstat (limited to 'src/eval/value.rs')
-rw-r--r--src/eval/value.rs481
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)
- }
}
- }
+ };
}