summaryrefslogtreecommitdiff
path: root/src/eval
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
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')
-rw-r--r--src/eval/args.rs246
-rw-r--r--src/eval/dict.rs522
-rw-r--r--src/eval/mod.rs95
-rw-r--r--src/eval/scope.rs18
-rw-r--r--src/eval/value.rs481
5 files changed, 473 insertions, 889 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
index 43c30daf..ea248ec4 100644
--- a/src/eval/args.rs
+++ b/src/eval/args.rs
@@ -1,184 +1,138 @@
-//! Simplifies argument parsing.
+use super::*;
+
+/// Evaluated arguments to a function.
+#[derive(Debug)]
+pub struct Args {
+ span: Span,
+ pos: SpanVec<Value>,
+ named: Vec<(Spanned<String>, Spanned<Value>)>,
+}
-use std::mem;
+impl Eval for Spanned<&Arguments> {
+ type Output = Args;
-use super::{Conv, EvalContext, RefKey, TryFromValue, Value, ValueDict};
-use crate::diag::Diag;
-use crate::syntax::{Span, SpanVec, Spanned, WithSpan};
+ fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ let mut pos = vec![];
+ let mut named = vec![];
-/// A wrapper around a dictionary value that simplifies argument parsing in
-/// functions.
-pub struct Args(pub Spanned<ValueDict>);
+ for arg in self.v {
+ match arg {
+ Argument::Pos(expr) => {
+ pos.push(expr.as_ref().eval(ctx).with_span(expr.span));
+ }
+ Argument::Named(Named { name, expr }) => {
+ named.push((
+ name.as_ref().map(|id| id.0.clone()),
+ expr.as_ref().eval(ctx).with_span(expr.span),
+ ));
+ }
+ }
+ }
-impl Args {
- /// Retrieve and remove the argument associated with the given key if there
- /// is any.
- ///
- /// Generates an error if the key exists, but the value can't be converted
- /// into the type `T`.
- pub fn get<'a, K, T>(&mut self, ctx: &mut EvalContext, key: K) -> Option<T>
- where
- K: Into<RefKey<'a>>,
- T: TryFromValue,
- {
- self.0.v.remove(key).and_then(|entry| {
- let span = entry.value.span;
- conv_diag(
- T::try_from_value(entry.value),
- &mut ctx.feedback.diags,
- span,
- )
- })
+ Args { span: self.span, pos, named }
}
+}
- /// This is the same as [`get`](Self::get), except that it generates an error about a
- /// missing argument with the given `name` if the key does not exist.
- pub fn need<'a, K, T>(
- &mut self,
- ctx: &mut EvalContext,
- key: K,
- name: &str,
- ) -> Option<T>
+impl Args {
+ /// Find the first convertible positional argument.
+ pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
where
- K: Into<RefKey<'a>>,
- T: TryFromValue,
+ T: Cast<Spanned<Value>>,
{
- if let Some(entry) = self.0.v.remove(key) {
- let span = entry.value.span;
- conv_diag(
- T::try_from_value(entry.value),
- &mut ctx.feedback.diags,
- span,
- )
- } else {
- ctx.diag(error!(self.0.span, "missing argument: {}", name));
- None
- }
+ self.pos.iter_mut().find_map(move |slot| try_cast(ctx, slot))
}
- /// Retrieve and remove the first matching positional argument.
- pub fn find<T>(&mut self) -> Option<T>
+ /// Find the first convertible positional argument, producing an error if
+ /// no match was found.
+ pub fn require<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T>
where
- T: TryFromValue,
+ T: Cast<Spanned<Value>>,
{
- for (&key, entry) in self.0.v.nums_mut() {
- let span = entry.value.span;
- let slot = &mut entry.value;
- let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
- if let Some(t) = conv {
- self.0.v.remove(key);
- return Some(t);
- }
+ let found = self.find(ctx);
+ if found.is_none() {
+ ctx.diag(error!(self.span, "missing argument: {}", what));
}
- None
+ found
}
- /// Retrieve and remove all matching positional arguments.
- pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_
+ /// Filter out all convertible positional arguments.
+ pub fn filter<'a, T>(
+ &'a mut self,
+ ctx: &'a mut EvalContext,
+ ) -> impl Iterator<Item = T> + 'a
where
- T: TryFromValue,
+ T: Cast<Spanned<Value>>,
{
- let mut skip = 0;
- std::iter::from_fn(move || {
- for (&key, entry) in self.0.v.nums_mut().skip(skip) {
- let span = entry.value.span;
- let slot = &mut entry.value;
- let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
- if let Some(t) = conv {
- self.0.v.remove(key);
- return Some(t);
- }
- skip += 1;
- }
- None
- })
+ self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot))
}
- /// Retrieve and remove all matching named arguments.
- pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
+ /// Convert the value for the given named argument.
+ ///
+ /// Generates an error if the conversion fails.
+ pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
where
- T: TryFromValue,
+ T: Cast<Spanned<Value>>,
{
- let mut skip = 0;
- std::iter::from_fn(move || {
- for (key, entry) in self.0.v.strs_mut().skip(skip) {
- let span = entry.value.span;
- let slot = &mut entry.value;
- let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
- if let Some(t) = conv {
- let key = key.clone();
- self.0.v.remove(&key);
- return Some((key, t));
- }
- skip += 1;
- }
-
- None
- })
+ let index = self.named.iter().position(|(k, _)| k.v.as_str() == name)?;
+ let value = self.named.remove(index).1;
+ cast(ctx, value)
}
- /// Generated _unexpected argument_ errors for all remaining entries.
- pub fn done(&self, ctx: &mut EvalContext) {
- for entry in self.0.v.values() {
- let span = entry.key_span.join(entry.value.span);
- ctx.diag(error!(span, "unexpected argument"));
+ /// Generate "unexpected argument" errors for all remaining arguments.
+ pub fn finish(self, ctx: &mut EvalContext) {
+ let a = self.pos.iter().map(|v| v.as_ref());
+ let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span)));
+ for value in a.chain(b) {
+ if value.v != &Value::Error {
+ ctx.diag(error!(value.span, "unexpected argument"));
+ }
}
}
}
-fn conv_diag<T>(conv: Conv<T>, diags: &mut SpanVec<Diag>, span: Span) -> Option<T> {
- match conv {
- Conv::Ok(t) => Some(t),
- Conv::Warn(t, warn) => {
- diags.push(warn.with_span(span));
+/// Cast the value into `T`, generating an error if the conversion fails.
+fn cast<T>(ctx: &mut EvalContext, value: Spanned<Value>) -> Option<T>
+where
+ T: Cast<Spanned<Value>>,
+{
+ let span = value.span;
+ match T::cast(value) {
+ CastResult::Ok(t) => Some(t),
+ CastResult::Warn(t, m) => {
+ ctx.diag(warning!(span, "{}", m));
Some(t)
}
- Conv::Err(_, err) => {
- diags.push(err.with_span(span));
+ CastResult::Err(value) => {
+ ctx.diag(error!(
+ span,
+ "expected {}, found {}",
+ T::TYPE_NAME,
+ value.v.type_name()
+ ));
None
}
}
}
-fn conv_put_back<T>(conv: Conv<T>, slot: &mut Spanned<Value>, span: Span) -> Option<T> {
- match conv {
- Conv::Ok(t) => Some(t),
- Conv::Warn(t, _) => Some(t),
- Conv::Err(v, _) => {
- *slot = v.with_span(span);
+/// Try to cast the value in the slot into `T`, putting it back if the
+/// conversion fails.
+fn try_cast<T>(ctx: &mut EvalContext, slot: &mut Spanned<Value>) -> Option<T>
+where
+ T: Cast<Spanned<Value>>,
+{
+ // Replace with error placeholder when conversion works since error values
+ // are ignored when generating "unexpected argument" errors.
+ let value = std::mem::replace(slot, Spanned::zero(Value::Error));
+ let span = value.span;
+ match T::cast(value) {
+ CastResult::Ok(t) => Some(t),
+ CastResult::Warn(t, m) => {
+ ctx.diag(warning!(span, "{}", m));
+ Some(t)
+ }
+ CastResult::Err(value) => {
+ *slot = value;
None
}
}
}
-
-#[cfg(test)]
-mod tests {
- use super::super::{Dict, SpannedEntry, Value};
- use super::*;
-
- fn entry(value: Value) -> SpannedEntry<Value> {
- SpannedEntry::value(Spanned::zero(value))
- }
-
- #[test]
- fn test_args_find() {
- let mut args = Args(Spanned::zero(Dict::new()));
- args.0.v.insert(1, entry(Value::Bool(false)));
- args.0.v.insert(2, entry(Value::Str("hi".to_string())));
- assert_eq!(args.find::<String>(), Some("hi".to_string()));
- assert_eq!(args.0.v.len(), 1);
- assert_eq!(args.find::<bool>(), Some(false));
- assert!(args.0.v.is_empty());
- }
-
- #[test]
- fn test_args_find_all() {
- let mut args = Args(Spanned::zero(Dict::new()));
- args.0.v.insert(1, entry(Value::Bool(false)));
- args.0.v.insert(3, entry(Value::Float(0.0)));
- args.0.v.insert(7, entry(Value::Bool(true)));
- assert_eq!(args.find_all::<bool>().collect::<Vec<_>>(), [false, true]);
- assert_eq!(args.0.v.len(), 1);
- assert_eq!(args.0.v[3].value.v, Value::Float(0.0));
- }
-}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
deleted file mode 100644
index efa5cb37..00000000
--- a/src/eval/dict.rs
+++ /dev/null
@@ -1,522 +0,0 @@
-//! A key-value map that can also model array-like structures.
-
-use std::collections::BTreeMap;
-use std::fmt::{self, Debug, Display, Formatter};
-use std::iter::{Extend, FromIterator};
-use std::ops::Index;
-
-use crate::syntax::{Span, Spanned};
-
-/// A dictionary data structure, which maps from integers and strings to a
-/// generic value type.
-///
-/// The dictionary can be used to model arrays by assigning values to successive
-/// indices from `0..n`. The `push` method offers special support for this
-/// pattern.
-#[derive(Clone)]
-pub struct Dict<V> {
- nums: BTreeMap<u64, V>,
- strs: BTreeMap<String, V>,
- lowest_free: u64,
-}
-
-impl<V> Dict<V> {
- /// Create a new empty dictionary.
- pub fn new() -> Self {
- Self {
- nums: BTreeMap::new(),
- strs: BTreeMap::new(),
- lowest_free: 0,
- }
- }
-
- /// The total number of entries in the dictionary.
- pub fn len(&self) -> usize {
- self.nums.len() + self.strs.len()
- }
-
- /// Whether the dictionary contains no entries.
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- /// The first number key-value pair (with lowest number).
- pub fn first(&self) -> Option<(u64, &V)> {
- self.nums.iter().next().map(|(&k, v)| (k, v))
- }
-
- /// The last number key-value pair (with highest number).
- pub fn last(&self) -> Option<(u64, &V)> {
- self.nums.iter().next_back().map(|(&k, v)| (k, v))
- }
-
- /// Get a reference to the value with the given key.
- pub fn get<'a, K>(&self, key: K) -> Option<&V>
- where
- K: Into<RefKey<'a>>,
- {
- match key.into() {
- RefKey::Num(num) => self.nums.get(&num),
- RefKey::Str(string) => self.strs.get(string),
- }
- }
-
- /// Borrow the value with the given key mutably.
- pub fn get_mut<'a, K>(&mut self, key: K) -> Option<&mut V>
- where
- K: Into<RefKey<'a>>,
- {
- match key.into() {
- RefKey::Num(num) => self.nums.get_mut(&num),
- RefKey::Str(string) => self.strs.get_mut(string),
- }
- }
-
- /// Insert a value into the dictionary.
- pub fn insert<K>(&mut self, key: K, value: V)
- where
- K: Into<DictKey>,
- {
- match key.into() {
- DictKey::Num(num) => {
- self.nums.insert(num, value);
- if self.lowest_free == num {
- self.lowest_free += 1;
- }
- }
- DictKey::Str(string) => {
- self.strs.insert(string, value);
- }
- }
- }
-
- /// Remove the value with the given key from the dictionary.
- pub fn remove<'a, K>(&mut self, key: K) -> Option<V>
- where
- K: Into<RefKey<'a>>,
- {
- match key.into() {
- RefKey::Num(num) => {
- self.lowest_free = self.lowest_free.min(num);
- self.nums.remove(&num)
- }
- RefKey::Str(string) => self.strs.remove(string),
- }
- }
-
- /// Append a value to the dictionary.
- ///
- /// This will associate the `value` with the lowest free number key (zero if
- /// there is no number key so far).
- pub fn push(&mut self, value: V) {
- while self.nums.contains_key(&self.lowest_free) {
- self.lowest_free += 1;
- }
- self.nums.insert(self.lowest_free, value);
- self.lowest_free += 1;
- }
-}
-
-impl<'a, K, V> Index<K> for Dict<V>
-where
- K: Into<RefKey<'a>>,
-{
- type Output = V;
-
- fn index(&self, index: K) -> &Self::Output {
- self.get(index).expect("key not in dict")
- }
-}
-
-impl<V: Eq> Eq for Dict<V> {}
-
-impl<V: PartialEq> PartialEq for Dict<V> {
- fn eq(&self, other: &Self) -> bool {
- self.iter().eq(other.iter())
- }
-}
-
-impl<V> Default for Dict<V> {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl<V: Debug> Debug for Dict<V> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if self.is_empty() {
- return f.write_str("()");
- }
-
- let mut builder = f.debug_tuple("");
-
- struct Entry<'a>(bool, &'a dyn Display, &'a dyn Debug);
- impl<'a> Debug for Entry<'a> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if self.0 {
- f.write_str("\"")?;
- }
- self.1.fmt(f)?;
- if self.0 {
- f.write_str("\"")?;
- }
-
- f.write_str(": ")?;
-
- self.2.fmt(f)
- }
- }
-
- for (key, value) in self.nums() {
- builder.field(&Entry(false, &key, &value));
- }
-
- for (key, value) in self.strs() {
- builder.field(&Entry(key.contains(' '), &key, &value));
- }
-
- builder.finish()
- }
-}
-
-/// Iteration.
-impl<V> Dict<V> {
- /// Iterator over all borrowed keys and values.
- pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
- self.into_iter()
- }
-
- /// Iterator over all borrowed keys and values.
- pub fn iter_mut(&mut self) -> impl Iterator<Item = (RefKey, &mut V)> {
- self.into_iter()
- }
-
- /// Iterate over all values in the dictionary.
- pub fn values(&self) -> impl Iterator<Item = &V> {
- self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
- }
-
- /// Iterate over all values in the dictionary.
- pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
- self.nums
- .iter_mut()
- .map(|(_, v)| v)
- .chain(self.strs.iter_mut().map(|(_, v)| v))
- }
-
- /// Move into an owned iterator over all values in the dictionary.
- pub fn into_values(self) -> impl Iterator<Item = V> {
- self.nums
- .into_iter()
- .map(|(_, v)| v)
- .chain(self.strs.into_iter().map(|(_, v)| v))
- }
-
- /// Iterate over the number key-value pairs.
- pub fn nums(&self) -> std::collections::btree_map::Iter<u64, V> {
- self.nums.iter()
- }
-
- /// Iterate mutably over the number key-value pairs.
- pub fn nums_mut(&mut self) -> std::collections::btree_map::IterMut<u64, V> {
- self.nums.iter_mut()
- }
-
- /// Iterate over the number key-value pairs.
- pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
- self.nums.into_iter()
- }
-
- /// Iterate over the string key-value pairs.
- pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
- self.strs.iter()
- }
-
- /// Iterate mutably over the string key-value pairs.
- pub fn strs_mut(&mut self) -> std::collections::btree_map::IterMut<String, V> {
- self.strs.iter_mut()
- }
-
- /// Iterate over the string key-value pairs.
- pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
- self.strs.into_iter()
- }
-}
-
-impl<V> Extend<(DictKey, V)> for Dict<V> {
- fn extend<T: IntoIterator<Item = (DictKey, V)>>(&mut self, iter: T) {
- for (key, value) in iter.into_iter() {
- self.insert(key, value);
- }
- }
-}
-
-impl<V> FromIterator<(DictKey, V)> for Dict<V> {
- fn from_iter<T: IntoIterator<Item = (DictKey, V)>>(iter: T) -> Self {
- let mut v = Self::new();
- v.extend(iter);
- v
- }
-}
-
-impl<V> IntoIterator for Dict<V> {
- type Item = (DictKey, V);
- type IntoIter = std::iter::Chain<
- std::iter::Map<
- std::collections::btree_map::IntoIter<u64, V>,
- fn((u64, V)) -> (DictKey, V),
- >,
- std::iter::Map<
- std::collections::btree_map::IntoIter<String, V>,
- fn((String, V)) -> (DictKey, V),
- >,
- >;
-
- fn into_iter(self) -> Self::IntoIter {
- let nums = self.nums.into_iter().map((|(k, v)| (DictKey::Num(k), v)) as _);
- let strs = self.strs.into_iter().map((|(k, v)| (DictKey::Str(k), v)) as _);
- nums.chain(strs)
- }
-}
-
-impl<'a, V> IntoIterator for &'a Dict<V> {
- type Item = (RefKey<'a>, &'a V);
- type IntoIter = std::iter::Chain<
- std::iter::Map<
- std::collections::btree_map::Iter<'a, u64, V>,
- fn((&'a u64, &'a V)) -> (RefKey<'a>, &'a V),
- >,
- std::iter::Map<
- std::collections::btree_map::Iter<'a, String, V>,
- fn((&'a String, &'a V)) -> (RefKey<'a>, &'a V),
- >,
- >;
-
- fn into_iter(self) -> Self::IntoIter {
- let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
- let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
- nums.chain(strs)
- }
-}
-
-impl<'a, V> IntoIterator for &'a mut Dict<V> {
- type Item = (RefKey<'a>, &'a mut V);
- type IntoIter = std::iter::Chain<
- std::iter::Map<
- std::collections::btree_map::IterMut<'a, u64, V>,
- fn((&'a u64, &'a mut V)) -> (RefKey<'a>, &'a mut V),
- >,
- std::iter::Map<
- std::collections::btree_map::IterMut<'a, String, V>,
- fn((&'a String, &'a mut V)) -> (RefKey<'a>, &'a mut V),
- >,
- >;
-
- fn into_iter(self) -> Self::IntoIter {
- let nums = self
- .nums
- .iter_mut()
- .map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
- let strs = self
- .strs
- .iter_mut()
- .map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
- nums.chain(strs)
- }
-}
-
-/// The owned variant of a dictionary key.
-#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum DictKey {
- Num(u64),
- Str(String),
-}
-
-impl From<&Self> for DictKey {
- fn from(key: &Self) -> Self {
- key.clone()
- }
-}
-
-impl From<RefKey<'_>> for DictKey {
- fn from(key: RefKey<'_>) -> Self {
- match key {
- RefKey::Num(num) => Self::Num(num),
- RefKey::Str(string) => Self::Str(string.to_string()),
- }
- }
-}
-
-impl From<u64> for DictKey {
- fn from(num: u64) -> Self {
- Self::Num(num)
- }
-}
-
-impl From<String> for DictKey {
- fn from(string: String) -> Self {
- Self::Str(string)
- }
-}
-
-impl From<&'static str> for DictKey {
- fn from(string: &'static str) -> Self {
- Self::Str(string.to_string())
- }
-}
-
-/// The borrowed variant of a dictionary key.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum RefKey<'a> {
- Num(u64),
- Str(&'a str),
-}
-
-impl From<u64> for RefKey<'static> {
- fn from(num: u64) -> Self {
- Self::Num(num)
- }
-}
-
-impl<'a> From<&'a String> for RefKey<'a> {
- fn from(string: &'a String) -> Self {
- Self::Str(&string)
- }
-}
-
-impl<'a> From<&'a str> for RefKey<'a> {
- fn from(string: &'a str) -> Self {
- Self::Str(string)
- }
-}
-
-/// A dictionary entry which combines key span and value.
-///
-/// This exists because a key in a directory can't track its span by itself.
-#[derive(Clone, PartialEq)]
-pub struct SpannedEntry<V> {
- pub key_span: Span,
- pub value: Spanned<V>,
-}
-
-impl<V> SpannedEntry<V> {
- /// Create a new entry.
- pub fn new(key: Span, val: Spanned<V>) -> Self {
- Self { key_span: key, value: val }
- }
-
- /// Create an entry with the same span for key and value.
- pub fn value(val: Spanned<V>) -> Self {
- Self { key_span: val.span, value: val }
- }
-
- /// Convert from `&SpannedEntry<T>` to `SpannedEntry<&T>`
- pub fn as_ref(&self) -> SpannedEntry<&V> {
- SpannedEntry {
- key_span: self.key_span,
- value: self.value.as_ref(),
- }
- }
-
- /// Map the entry to a different value type.
- pub fn map<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
- SpannedEntry {
- key_span: self.key_span,
- value: self.value.map(f),
- }
- }
-}
-
-impl<V: Debug> Debug for SpannedEntry<V> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if f.alternate() {
- f.write_str("key")?;
- self.key_span.fmt(f)?;
- f.write_str(" ")?;
- }
- self.value.fmt(f)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::Dict;
-
- #[test]
- fn test_dict_different_key_types_dont_interfere() {
- let mut dict = Dict::new();
- dict.insert(10, "hello");
- dict.insert("twenty", "there");
- assert_eq!(dict.len(), 2);
- assert_eq!(dict[10], "hello");
- assert_eq!(dict["twenty"], "there");
- }
-
- #[test]
- fn test_dict_push_skips_already_inserted_keys() {
- let mut dict = Dict::new();
- dict.insert(2, "2");
- dict.push("0");
- dict.insert(3, "3");
- dict.push("1");
- dict.push("4");
- assert_eq!(dict.len(), 5);
- assert_eq!(dict[0], "0");
- assert_eq!(dict[1], "1");
- assert_eq!(dict[2], "2");
- assert_eq!(dict[3], "3");
- assert_eq!(dict[4], "4");
- }
-
- #[test]
- fn test_dict_push_remove_push_reuses_index() {
- let mut dict = Dict::new();
- dict.push("0");
- dict.push("1");
- dict.push("2");
- dict.remove(1);
- dict.push("a");
- dict.push("3");
- assert_eq!(dict.len(), 4);
- assert_eq!(dict[0], "0");
- assert_eq!(dict[1], "a");
- assert_eq!(dict[2], "2");
- assert_eq!(dict[3], "3");
- }
-
- #[test]
- fn test_dict_first_and_last_are_correct() {
- let mut dict = Dict::new();
- assert_eq!(dict.first(), None);
- assert_eq!(dict.last(), None);
- dict.insert(4, "hi");
- dict.insert("string", "hi");
- assert_eq!(dict.first(), Some((4, &"hi")));
- assert_eq!(dict.last(), Some((4, &"hi")));
- dict.insert(2, "bye");
- assert_eq!(dict.first(), Some((2, &"bye")));
- assert_eq!(dict.last(), Some((4, &"hi")));
- }
-
- #[test]
- fn test_dict_format_debug() {
- let mut dict = Dict::new();
- assert_eq!(format!("{:?}", dict), "()");
- assert_eq!(format!("{:#?}", dict), "()");
-
- dict.insert(10, "hello");
- dict.insert("twenty", "there");
- dict.insert("sp ace", "quotes");
- assert_eq!(
- format!("{:?}", dict),
- r#"(10: "hello", "sp ace": "quotes", twenty: "there")"#,
- );
- assert_eq!(format!("{:#?}", dict).lines().collect::<Vec<_>>(), [
- "(",
- r#" 10: "hello","#,
- r#" "sp ace": "quotes","#,
- r#" twenty: "there","#,
- ")",
- ]);
- }
-}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 56946210..62bd444c 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -3,12 +3,10 @@
#[macro_use]
mod value;
mod args;
-mod dict;
mod scope;
mod state;
pub use args::*;
-pub use dict::*;
pub use scope::*;
pub use state::*;
pub use value::*;
@@ -451,7 +449,13 @@ impl Eval for Spanned<&Lit> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match *self.v {
- Lit::Ident(ref v) => Value::Ident(v.clone()),
+ Lit::Ident(ref v) => match ctx.state.scope.get(v.as_str()) {
+ Some(value) => value.clone(),
+ None => {
+ ctx.diag(error!(self.span, "unknown variable"));
+ Value::Error
+ }
+ },
Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v),
@@ -459,28 +463,29 @@ impl Eval for Spanned<&Lit> {
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()),
+ Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)),
Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Lit::Content(ref v) => Value::Content(v.clone()),
}
}
}
-impl Eval for Spanned<&LitDict> {
- type Output = ValueDict;
+
+impl Eval for Spanned<&Array> {
+ type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let mut dict = ValueDict::new();
+ self.v.iter().map(|expr| expr.as_ref().eval(ctx)).collect()
+ }
+}
- for entry in &self.v.0 {
- let val = entry.expr.as_ref().eval(ctx);
- let spanned = val.with_span(entry.expr.span);
- if let Some(key) = &entry.key {
- dict.insert(&key.v, SpannedEntry::new(key.span, spanned));
- } else {
- dict.push(SpannedEntry::value(spanned));
- }
- }
+impl Eval for Spanned<&Dict> {
+ type Output = ValueDict;
- dict
+ fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ self.v
+ .iter()
+ .map(|Named { name, expr }| (name.v.0.clone(), expr.as_ref().eval(ctx)))
+ .collect()
}
}
@@ -490,19 +495,27 @@ impl Eval for Spanned<&ExprCall> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let name = &self.v.name.v;
let span = self.v.name.span;
- let dict = self.v.args.as_ref().eval(ctx);
- if let Some(func) = ctx.state.scope.get(name) {
- let args = Args(dict.with_span(self.v.args.span));
- ctx.feedback.decos.push(Deco::Resolved.with_span(span));
- (func.clone())(args, ctx)
- } else {
- if !name.is_empty() {
- ctx.diag(error!(span, "unknown function"));
- ctx.feedback.decos.push(Deco::Unresolved.with_span(span));
+ if let Some(value) = ctx.state.scope.get(name) {
+ if let Value::Func(func) = value {
+ let func = func.clone();
+ ctx.feedback.decos.push(Deco::Resolved.with_span(span));
+
+ let mut args = self.v.args.as_ref().eval(ctx);
+ let returned = func(ctx, &mut args);
+ args.finish(ctx);
+
+ return returned;
+ } else {
+ let ty = value.type_name();
+ ctx.diag(error!(span, "a value of type {} is not callable", ty));
}
- Value::Dict(dict)
+ } else if !name.is_empty() {
+ ctx.diag(error!(span, "unknown function"));
}
+
+ ctx.feedback.decos.push(Deco::Unresolved.with_span(span));
+ Value::Error
}
}
@@ -554,7 +567,7 @@ fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
Relative(v) => Relative(-v),
Linear(v) => Linear(-v),
v => {
- ctx.diag(error!(span, "cannot negate {}", v.ty()));
+ ctx.diag(error!(span, "cannot negate {}", v.type_name()));
Value::Error
}
}
@@ -589,7 +602,12 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Content(a), Content(b)) => Content(concat(a, b)),
(a, b) => {
- ctx.diag(error!(span, "cannot add {} and {}", a.ty(), b.ty()));
+ ctx.diag(error!(
+ span,
+ "cannot add {} and {}",
+ a.type_name(),
+ b.type_name()
+ ));
Value::Error
}
}
@@ -617,7 +635,12 @@ fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Linear(a), Linear(b)) => Linear(a - b),
(a, b) => {
- ctx.diag(error!(span, "cannot subtract {1} from {0}", a.ty(), b.ty()));
+ ctx.diag(error!(
+ span,
+ "cannot subtract {1} from {0}",
+ a.type_name(),
+ b.type_name()
+ ));
Value::Error
}
}
@@ -652,7 +675,12 @@ fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)),
(a, b) => {
- ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty()));
+ ctx.diag(error!(
+ span,
+ "cannot multiply {} with {}",
+ a.type_name(),
+ b.type_name()
+ ));
Value::Error
}
}
@@ -677,7 +705,12 @@ fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Linear(a), Float(b)) => Linear(a / b),
(a, b) => {
- ctx.diag(error!(span, "cannot divide {} by {}", a.ty(), b.ty()));
+ ctx.diag(error!(
+ span,
+ "cannot divide {} by {}",
+ a.type_name(),
+ b.type_name()
+ ));
Value::Error
}
}
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index c0624393..c9ce1423 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -3,12 +3,12 @@
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
-use super::value::ValueFunc;
+use super::Value;
/// A map from identifiers to functions.
#[derive(Default, Clone, PartialEq)]
pub struct Scope {
- functions: HashMap<String, ValueFunc>,
+ values: HashMap<String, Value>,
}
impl Scope {
@@ -18,19 +18,19 @@ impl Scope {
Self::default()
}
- /// Return the function with the given name if there is one.
- pub fn get(&self, name: &str) -> Option<&ValueFunc> {
- self.functions.get(name)
+ /// Return the value of the given variable.
+ pub fn get(&self, var: &str) -> Option<&Value> {
+ self.values.get(var)
}
- /// Associate the given name with the function.
- pub fn set(&mut self, name: impl Into<String>, function: ValueFunc) {
- self.functions.insert(name.into(), function);
+ /// Store the value for the given variable.
+ pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
+ self.values.insert(var.into(), value.into());
}
}
impl Debug for Scope {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_set().entries(self.functions.keys()).finish()
+ self.values.fmt(f)
}
}
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)
- }
}
- }
+ };
}