diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-01 15:13:04 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-01 15:13:04 +0200 |
| commit | e676ab53ddbab367179ee2ab214bb41ff2ee0c11 (patch) | |
| tree | f003f6eb6698494310da5771249bbb61a8da27a0 /src/compute/value.rs | |
| parent | 7c12f0c07f9d4ed473027dbd00cbbc00e4a3050c (diff) | |
Rename compute to eval ✏
Diffstat (limited to 'src/compute/value.rs')
| -rw-r--r-- | src/compute/value.rs | 496 |
1 files changed, 0 insertions, 496 deletions
diff --git a/src/compute/value.rs b/src/compute/value.rs deleted file mode 100644 index cfbc302c..00000000 --- a/src/compute/value.rs +++ /dev/null @@ -1,496 +0,0 @@ -//! Computational values: Syntactical expressions can be evaluated into these. - -use std::fmt::{self, Debug, Formatter}; -use std::ops::Deref; -use std::rc::Rc; - -use fontdock::{FontStretch, FontStyle, FontWeight}; - -use super::dict::{Dict, SpannedEntry}; -use crate::color::RgbaColor; -use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign}; -use crate::length::{Length, ScaleLength}; -use crate::paper::Paper; -use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree}; -use crate::{DynFuture, Feedback, Pass}; - -/// A computational value. -#[derive(Clone)] -pub enum Value { - /// An identifier: `ident`. - Ident(Ident), - /// A string: `"string"`. - Str(String), - /// A boolean: `true, false`. - Bool(bool), - /// A number: `1.2, 200%`. - Number(f64), - /// A length: `2cm, 5.2in`. - Length(Length), - /// A color value with alpha channel: `#f79143ff`. - Color(RgbaColor), - /// A dictionary value: `(false, 12cm, greeting="hi")`. - Dict(DictValue), - /// A syntax tree containing typesetting content. - Tree(SynTree), - /// An executable function. - Func(FuncValue), - /// Layouting commands. - Commands(Commands), -} - -impl Value { - /// A natural-language name of the type of this expression, e.g. - /// "identifier". - pub fn name(&self) -> &'static str { - use Value::*; - match self { - Ident(_) => "identifier", - Str(_) => "string", - Bool(_) => "bool", - Number(_) => "number", - Length(_) => "length", - Color(_) => "color", - Dict(_) => "dict", - Tree(_) => "syntax tree", - Func(_) => "function", - Commands(_) => "commands", - } - } -} - -impl Spanned<Value> { - /// Transform this value into something layoutable. - /// - /// If this is already a command-value, it is simply unwrapped, otherwise - /// the value is represented as layoutable content in a reasonable way. - pub fn into_commands(self) -> Commands { - match self.v { - Value::Commands(commands) => commands, - Value::Tree(tree) => vec![Command::LayoutSyntaxTree(tree)], - - // Forward to each entry, separated with spaces. - Value::Dict(dict) => { - let mut commands = vec![]; - let mut end = None; - for entry in dict.into_values() { - if let Some(last_end) = end { - let span = Span::new(last_end, entry.key.start); - let tree = vec![SynNode::Spacing.span_with(span)]; - commands.push(Command::LayoutSyntaxTree(tree)); - } - - end = Some(entry.val.span.end); - commands.extend(entry.val.into_commands()); - } - commands - } - - // Format with debug. - val => { - let fmt = format!("{:?}", val); - let tree = vec![SynNode::Text(fmt).span_with(self.span)]; - vec![Command::LayoutSyntaxTree(tree)] - } - } - } -} - -impl Debug for Value { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use Value::*; - match self { - Ident(i) => i.fmt(f), - Str(s) => s.fmt(f), - Bool(b) => b.fmt(f), - Number(n) => n.fmt(f), - Length(s) => s.fmt(f), - Color(c) => c.fmt(f), - Dict(t) => t.fmt(f), - Tree(t) => t.fmt(f), - Func(_) => f.pad("<function>"), - Commands(c) => c.fmt(f), - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - use Value::*; - match (self, other) { - (Ident(a), Ident(b)) => a == b, - (Str(a), Str(b)) => a == b, - (Bool(a), Bool(b)) => a == b, - (Number(a), Number(b)) => a == b, - (Length(a), Length(b)) => a == b, - (Color(a), Color(b)) => a == b, - (Dict(a), Dict(b)) => a == b, - (Tree(a), Tree(b)) => a == b, - (Func(a), Func(b)) => Rc::ptr_eq(a, b), - (Commands(a), Commands(b)) => a == b, - _ => false, - } - } -} - -/// An executable function value. -/// -/// The first argument is a dictionary containing the arguments passed to the -/// function. The function may be asynchronous (as such it returns a dynamic -/// future) and it may emit diagnostics, which are contained in the returned -/// `Pass`. In the end, the function must evaluate to `Value`. Your typical -/// typesetting function will return a `Commands` value which will instruct the -/// layouting engine to do what the function pleases. -/// -/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable. -pub type FuncValue = - Rc<dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>>; - -/// A dictionary of values. -/// -/// # Example -/// ```typst -/// (false, 12cm, greeting="hi") -/// ``` -pub type DictValue = Dict<SpannedEntry<Value>>; - -impl DictValue { - /// Retrieve and remove the matching value with the lowest number key, - /// skipping and ignoring all non-matching entries with lower keys. - pub fn take<T: TryFromValue>(&mut self) -> Option<T> { - for (&key, entry) in self.nums() { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) { - self.remove(key); - return Some(val); - } - } - None - } - - /// Retrieve and remove the matching value with the lowest number key, - /// removing and generating errors for all non-matching entries with lower - /// keys. - /// - /// Generates an error at `err_span` when no matching value was found. - pub fn expect<T: TryFromValue>( - &mut self, - name: &str, - span: Span, - f: &mut Feedback, - ) -> Option<T> { - while let Some((num, _)) = self.first() { - let entry = self.remove(num).unwrap(); - if let Some(val) = T::try_from_value(entry.val.as_ref(), f) { - return Some(val); - } - } - error!(@f, span, "missing argument: {}", name); - None - } - - /// Retrieve and remove a matching value associated with the given key if - /// there is any. - /// - /// Generates an error if the key exists but the value does not match. - pub fn take_key<T>(&mut self, key: &str, f: &mut Feedback) -> Option<T> - where - T: TryFromValue, - { - self.remove(key).and_then(|entry| { - let expr = entry.val.as_ref(); - T::try_from_value(expr, f) - }) - } - - /// Retrieve and remove all matching pairs with number keys, skipping and - /// ignoring non-matching entries. - /// - /// The pairs are returned in order of increasing keys. - pub fn take_all_num<'a, T>(&'a mut self) -> impl Iterator<Item = (u64, T)> + 'a - where - T: TryFromValue, - { - let mut skip = 0; - std::iter::from_fn(move || { - for (&key, entry) in self.nums().skip(skip) { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) { - self.remove(key); - return Some((key, val)); - } - skip += 1; - } - - None - }) - } - - - /// Retrieve and remove all matching values with number keys, skipping and - /// ignoring non-matching entries. - /// - /// The values are returned in order of increasing keys. - pub fn take_all_num_vals<'a, T: 'a>(&'a mut self) -> impl Iterator<Item = T> + 'a - where - T: TryFromValue, - { - self.take_all_num::<T>().map(|(_, v)| v) - } - - /// Retrieve and remove all matching pairs with string keys, skipping and - /// ignoring non-matching entries. - /// - /// The pairs are returned in order of increasing keys. - pub fn take_all_str<'a, T>(&'a mut self) -> impl Iterator<Item = (String, T)> + 'a - where - T: TryFromValue, - { - let mut skip = 0; - std::iter::from_fn(move || { - for (key, entry) in self.strs().skip(skip) { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) { - let key = key.clone(); - self.remove(&key); - return Some((key, val)); - } - skip += 1; - } - - None - }) - } - - /// Generated `"unexpected argument"` errors for all remaining entries. - pub fn unexpected(&self, f: &mut Feedback) { - for entry in self.values() { - let span = Span::merge(entry.key, entry.val.span); - error!(@f, span, "unexpected argument"); - } - } -} - -/// A trait for converting values into more specific types. -pub trait TryFromValue: Sized { - // This trait takes references because we don't want to move the value - // out of its origin in case this returns `None`. This solution is not - // perfect because we need to do some cloning in the impls for this trait, - // but we haven't got a better solution, for now. - - /// Try to convert a value to this type. - /// - /// Returns `None` and generates an appropriate error if the value is not - /// valid for this type. - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self>; -} - -macro_rules! impl_match { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl TryFromValue for $type { - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> { - #[allow(unreachable_patterns)] - match value.v { - $($p => Some($r)),*, - other => { - error!( - @f, value.span, - "expected {}, found {}", $name, other.name() - ); - None - } - } - } - } - }; -} - -macro_rules! impl_ident { - ($type:ty, $name:expr, $parse:expr) => { - impl TryFromValue for $type { - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> { - if let Value::Ident(ident) = value.v { - let val = $parse(ident); - if val.is_none() { - error!(@f, value.span, "invalid {}", $name); - } - val - } else { - error!( - @f, value.span, - "expected {}, found {}", $name, value.v.name() - ); - None - } - } - } - }; -} - -impl<T: TryFromValue> TryFromValue for Spanned<T> { - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> { - let span = value.span; - T::try_from_value(value, f).map(|v| Spanned { v, span }) - } -} - -impl_match!(Value, "value", v => v.clone()); -impl_match!(Ident, "identifier", Value::Ident(i) => i.clone()); -impl_match!(String, "string", Value::Str(s) => s.clone()); -impl_match!(bool, "bool", &Value::Bool(b) => b); -impl_match!(f64, "number", &Value::Number(n) => n); -impl_match!(Length, "length", &Value::Length(l) => l); -impl_match!(SynTree, "tree", Value::Tree(t) => t.clone()); -impl_match!(DictValue, "dict", Value::Dict(t) => t.clone()); -impl_match!(FuncValue, "function", Value::Func(f) => f.clone()); -impl_match!(ScaleLength, "number or length", - &Value::Length(length) => ScaleLength::Absolute(length), - &Value::Number(scale) => ScaleLength::Scaled(scale), -); - -/// A value type that matches identifiers and strings and implements -/// `Into<String>`. -pub struct StringLike(pub String); - -impl From<StringLike> for String { - fn from(like: StringLike) -> String { - like.0 - } -} - -impl Deref for StringLike { - type Target = str; - - fn deref(&self) -> &str { - self.0.as_str() - } -} - -impl_match!(StringLike, "identifier or string", - Value::Ident(Ident(s)) => StringLike(s.clone()), - Value::Str(s) => StringLike(s.clone()), -); - -impl_ident!(Dir, "direction", |s| match s { - "ltr" => Some(Self::LTR), - "rtl" => Some(Self::RTL), - "ttb" => Some(Self::TTB), - "btt" => Some(Self::BTT), - _ => None, -}); - -impl_ident!(SpecAlign, "alignment", |s| match s { - "left" => Some(Self::Left), - "right" => Some(Self::Right), - "top" => Some(Self::Top), - "bottom" => Some(Self::Bottom), - "center" => Some(Self::Center), - _ => None, -}); - -impl_ident!(FontStyle, "font style", Self::from_str); -impl_ident!(FontStretch, "font stretch", Self::from_str); -impl_ident!(Paper, "paper", Self::from_name); - -impl TryFromValue for FontWeight { - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> { - match value.v { - &Value::Number(weight) => { - const MIN: u16 = 100; - const MAX: u16 = 900; - - if weight < MIN as f64 { - error!(@f, value.span, "the minimum font weight is {}", MIN); - Some(Self::THIN) - } else if weight > MAX as f64 { - error!(@f, value.span, "the maximum font weight is {}", MAX); - Some(Self::BLACK) - } else { - FontWeight::from_number(weight.round() as u16) - } - } - Value::Ident(ident) => { - let weight = Self::from_str(ident); - if weight.is_none() { - error!(@f, value.span, "invalid font weight"); - } - weight - } - other => { - error!( - @f, value.span, - "expected font weight (name or number), found {}", - other.name(), - ); - None - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn entry(value: Value) -> SpannedEntry<Value> { - SpannedEntry::val(Spanned::zero(value)) - } - - #[test] - fn test_dict_take_removes_correct_entry() { - let mut dict = Dict::new(); - dict.insert(1, entry(Value::Bool(false))); - dict.insert(2, entry(Value::Str("hi".to_string()))); - assert_eq!(dict.take::<String>(), Some("hi".to_string())); - assert_eq!(dict.len(), 1); - assert_eq!(dict.take::<bool>(), Some(false)); - assert!(dict.is_empty()); - } - - #[test] - fn test_dict_expect_errors_about_previous_entries() { - let mut f = Feedback::new(); - let mut dict = Dict::new(); - dict.insert(1, entry(Value::Bool(false))); - dict.insert(3, entry(Value::Str("hi".to_string()))); - dict.insert(5, entry(Value::Bool(true))); - assert_eq!( - dict.expect::<String>("", Span::ZERO, &mut f), - Some("hi".to_string()) - ); - assert_eq!(f.diagnostics, [error!( - Span::ZERO, - "expected string, found bool" - )]); - assert_eq!(dict.len(), 1); - } - - #[test] - fn test_dict_take_with_key_removes_the_entry() { - let mut f = Feedback::new(); - let mut dict = Dict::new(); - dict.insert(1, entry(Value::Bool(false))); - dict.insert("hi", entry(Value::Bool(true))); - assert_eq!(dict.take::<bool>(), Some(false)); - assert_eq!(dict.take_key::<f64>("hi", &mut f), None); - assert_eq!(f.diagnostics, [error!( - Span::ZERO, - "expected number, found bool" - )]); - assert!(dict.is_empty()); - } - - #[test] - fn test_dict_take_all_removes_the_correct_entries() { - let mut dict = Dict::new(); - dict.insert(1, entry(Value::Bool(false))); - dict.insert(3, entry(Value::Number(0.0))); - dict.insert(7, entry(Value::Bool(true))); - assert_eq!(dict.take_all_num::<bool>().collect::<Vec<_>>(), [ - (1, false), - (7, true) - ],); - assert_eq!(dict.len(), 1); - assert_eq!(dict[3].val.v, Value::Number(0.0)); - } -} |
