From e676ab53ddbab367179ee2ab214bb41ff2ee0c11 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 1 Oct 2020 15:13:04 +0200 Subject: =?UTF-8?q?Rename=20compute=20to=20eval=20=E2=9C=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compute/dict.rs | 417 ------------------------------------------- src/compute/mod.rs | 9 - src/compute/scope.rs | 35 ---- src/compute/value.rs | 496 --------------------------------------------------- src/eval/dict.rs | 417 +++++++++++++++++++++++++++++++++++++++++++ src/eval/mod.rs | 9 + src/eval/scope.rs | 35 ++++ src/eval/value.rs | 496 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/layout/mod.rs | 2 +- src/lib.rs | 4 +- src/library/mod.rs | 2 +- src/parse/mod.rs | 2 +- src/parse/tests.rs | 2 +- src/prelude.rs | 2 +- src/syntax/expr.rs | 2 +- src/syntax/lit.rs | 2 +- 16 files changed, 966 insertions(+), 966 deletions(-) delete mode 100644 src/compute/dict.rs delete mode 100644 src/compute/mod.rs delete mode 100644 src/compute/scope.rs delete mode 100644 src/compute/value.rs create mode 100644 src/eval/dict.rs create mode 100644 src/eval/mod.rs create mode 100644 src/eval/scope.rs create mode 100644 src/eval/value.rs diff --git a/src/compute/dict.rs b/src/compute/dict.rs deleted file mode 100644 index e6216572..00000000 --- a/src/compute/dict.rs +++ /dev/null @@ -1,417 +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::ops::Index; - -use crate::syntax::{Span, Spanned}; - -/// A dictionary data structure, which maps from integers (`u64`) or 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 { - nums: BTreeMap, - strs: BTreeMap, - lowest_free: u64, -} - -impl Dict { - /// 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>, - { - 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>, - { - 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(&mut self, key: K, value: V) - where - K: Into, - { - 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 - where - K: Into>, - { - 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; - } - - /// Iterator over all borrowed keys and values. - pub fn iter(&self) -> impl Iterator { - self.nums() - .map(|(&k, v)| (RefKey::Num(k), v)) - .chain(self.strs().map(|(k, v)| (RefKey::Str(k), v))) - } - - /// Iterate over all values in the dictionary. - pub fn values(&self) -> impl Iterator { - self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v)) - } - - /// Iterate over the number key-value pairs. - pub fn nums(&self) -> std::collections::btree_map::Iter { - self.nums.iter() - } - - /// Iterate over the string key-value pairs. - pub fn strs(&self) -> std::collections::btree_map::Iter { - self.strs.iter() - } - - /// Move into an owned iterator over owned keys and values. - pub fn into_iter(self) -> impl Iterator { - self.nums - .into_iter() - .map(|(k, v)| (DictKey::Num(k), v)) - .chain(self.strs.into_iter().map(|(k, v)| (DictKey::Str(k), v))) - } - - /// Move into an owned iterator over all values in the dictionary. - pub fn into_values(self) -> impl Iterator { - self.nums - .into_iter() - .map(|(_, v)| v) - .chain(self.strs.into_iter().map(|(_, v)| v)) - } - - /// Iterate over the number key-value pairs. - pub fn into_nums(self) -> std::collections::btree_map::IntoIter { - self.nums.into_iter() - } - - /// Iterate over the string key-value pairs. - pub fn into_strs(self) -> std::collections::btree_map::IntoIter { - self.strs.into_iter() - } -} - -impl<'a, K, V> Index for Dict -where - K: Into>, -{ - type Output = V; - - fn index(&self, index: K) -> &Self::Output { - self.get(index).expect("key not in dict") - } -} - -impl Default for Dict { - fn default() -> Self { - Self::new() - } -} - -impl Eq for Dict {} - -impl PartialEq for Dict { - fn eq(&self, other: &Self) -> bool { - self.iter().eq(other.iter()) - } -} - -impl Debug for Dict { - 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("\"")?; - } - if f.alternate() { - f.write_str(" = ")?; - } else { - 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() - } -} - -/// 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> 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 for DictKey { - fn from(num: u64) -> Self { - Self::Num(num) - } -} - -impl From 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 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 tracks key and value span. -#[derive(Clone, PartialEq)] -pub struct SpannedEntry { - pub key: Span, - pub val: Spanned, -} - -impl SpannedEntry { - /// Create a new entry. - pub fn new(key: Span, val: Spanned) -> Self { - Self { key, val } - } - - /// Create an entry with the same span for key and value. - pub fn val(val: Spanned) -> Self { - Self { key: val.span, val } - } - - /// Convert from `&SpannedEntry` to `SpannedEntry<&T>` - pub fn as_ref(&self) -> SpannedEntry<&V> { - SpannedEntry { key: self.key, val: self.val.as_ref() } - } - - /// Map the entry to a different value type. - pub fn map(self, f: impl FnOnce(V) -> U) -> SpannedEntry { - SpannedEntry { key: self.key, val: self.val.map(f) } - } -} - -impl Debug for SpannedEntry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - f.write_str("key")?; - self.key.fmt(f)?; - f.write_str(" ")?; - } - self.val.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::>(), [ - "(", - r#" 10 = "hello","#, - r#" "sp ace" = "quotes","#, - r#" twenty = "there","#, - ")", - ]); - } -} diff --git a/src/compute/mod.rs b/src/compute/mod.rs deleted file mode 100644 index 8f2226b1..00000000 --- a/src/compute/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Building blocks for the computational part. - -mod dict; -mod scope; -mod value; - -pub use dict::*; -pub use scope::*; -pub use value::*; diff --git a/src/compute/scope.rs b/src/compute/scope.rs deleted file mode 100644 index 8e6576d1..00000000 --- a/src/compute/scope.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Mapping from identifiers to functions. - -use std::collections::HashMap; -use std::fmt::{self, Debug, Formatter}; - -use super::value::FuncValue; - -/// A map from identifiers to functions. -pub struct Scope { - functions: HashMap, -} - -impl Scope { - // Create a new empty scope with a fallback function that is invoked when no - // match is found. - pub fn new() -> Self { - Self { functions: HashMap::new() } - } - - /// Associate the given name with the function. - pub fn insert(&mut self, name: impl Into, function: FuncValue) { - self.functions.insert(name.into(), function); - } - - /// Return the function with the given name if there is one. - pub fn func(&self, name: &str) -> Option<&FuncValue> { - self.functions.get(name) - } -} - -impl Debug for Scope { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_set().entries(self.functions.keys()).finish() - } -} 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 { - /// 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(""), - 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) -> DynFuture>>; - -/// A dictionary of values. -/// -/// # Example -/// ```typst -/// (false, 12cm, greeting="hi") -/// ``` -pub type DictValue = Dict>; - -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(&mut self) -> Option { - 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( - &mut self, - name: &str, - span: Span, - f: &mut Feedback, - ) -> Option { - 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(&mut self, key: &str, f: &mut Feedback) -> Option - 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 + '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 + 'a - where - T: TryFromValue, - { - self.take_all_num::().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 + '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; -} - -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 { - #[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 { - 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 TryFromValue for Spanned { - fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option { - 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`. -pub struct StringLike(pub String); - -impl From 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 { - 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 { - 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::(), Some("hi".to_string())); - assert_eq!(dict.len(), 1); - assert_eq!(dict.take::(), 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::("", 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::(), Some(false)); - assert_eq!(dict.take_key::("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::().collect::>(), [ - (1, false), - (7, true) - ],); - assert_eq!(dict.len(), 1); - assert_eq!(dict[3].val.v, Value::Number(0.0)); - } -} diff --git a/src/eval/dict.rs b/src/eval/dict.rs new file mode 100644 index 00000000..e6216572 --- /dev/null +++ b/src/eval/dict.rs @@ -0,0 +1,417 @@ +//! A key-value map that can also model array-like structures. + +use std::collections::BTreeMap; +use std::fmt::{self, Debug, Display, Formatter}; +use std::ops::Index; + +use crate::syntax::{Span, Spanned}; + +/// A dictionary data structure, which maps from integers (`u64`) or 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 { + nums: BTreeMap, + strs: BTreeMap, + lowest_free: u64, +} + +impl Dict { + /// 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>, + { + 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>, + { + 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(&mut self, key: K, value: V) + where + K: Into, + { + 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 + where + K: Into>, + { + 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; + } + + /// Iterator over all borrowed keys and values. + pub fn iter(&self) -> impl Iterator { + self.nums() + .map(|(&k, v)| (RefKey::Num(k), v)) + .chain(self.strs().map(|(k, v)| (RefKey::Str(k), v))) + } + + /// Iterate over all values in the dictionary. + pub fn values(&self) -> impl Iterator { + self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v)) + } + + /// Iterate over the number key-value pairs. + pub fn nums(&self) -> std::collections::btree_map::Iter { + self.nums.iter() + } + + /// Iterate over the string key-value pairs. + pub fn strs(&self) -> std::collections::btree_map::Iter { + self.strs.iter() + } + + /// Move into an owned iterator over owned keys and values. + pub fn into_iter(self) -> impl Iterator { + self.nums + .into_iter() + .map(|(k, v)| (DictKey::Num(k), v)) + .chain(self.strs.into_iter().map(|(k, v)| (DictKey::Str(k), v))) + } + + /// Move into an owned iterator over all values in the dictionary. + pub fn into_values(self) -> impl Iterator { + self.nums + .into_iter() + .map(|(_, v)| v) + .chain(self.strs.into_iter().map(|(_, v)| v)) + } + + /// Iterate over the number key-value pairs. + pub fn into_nums(self) -> std::collections::btree_map::IntoIter { + self.nums.into_iter() + } + + /// Iterate over the string key-value pairs. + pub fn into_strs(self) -> std::collections::btree_map::IntoIter { + self.strs.into_iter() + } +} + +impl<'a, K, V> Index for Dict +where + K: Into>, +{ + type Output = V; + + fn index(&self, index: K) -> &Self::Output { + self.get(index).expect("key not in dict") + } +} + +impl Default for Dict { + fn default() -> Self { + Self::new() + } +} + +impl Eq for Dict {} + +impl PartialEq for Dict { + fn eq(&self, other: &Self) -> bool { + self.iter().eq(other.iter()) + } +} + +impl Debug for Dict { + 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("\"")?; + } + if f.alternate() { + f.write_str(" = ")?; + } else { + 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() + } +} + +/// 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> 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 for DictKey { + fn from(num: u64) -> Self { + Self::Num(num) + } +} + +impl From 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 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 tracks key and value span. +#[derive(Clone, PartialEq)] +pub struct SpannedEntry { + pub key: Span, + pub val: Spanned, +} + +impl SpannedEntry { + /// Create a new entry. + pub fn new(key: Span, val: Spanned) -> Self { + Self { key, val } + } + + /// Create an entry with the same span for key and value. + pub fn val(val: Spanned) -> Self { + Self { key: val.span, val } + } + + /// Convert from `&SpannedEntry` to `SpannedEntry<&T>` + pub fn as_ref(&self) -> SpannedEntry<&V> { + SpannedEntry { key: self.key, val: self.val.as_ref() } + } + + /// Map the entry to a different value type. + pub fn map(self, f: impl FnOnce(V) -> U) -> SpannedEntry { + SpannedEntry { key: self.key, val: self.val.map(f) } + } +} + +impl Debug for SpannedEntry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if f.alternate() { + f.write_str("key")?; + self.key.fmt(f)?; + f.write_str(" ")?; + } + self.val.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::>(), [ + "(", + r#" 10 = "hello","#, + r#" "sp ace" = "quotes","#, + r#" twenty = "there","#, + ")", + ]); + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs new file mode 100644 index 00000000..65fc71c4 --- /dev/null +++ b/src/eval/mod.rs @@ -0,0 +1,9 @@ +//! Evaluation of syntax trees. + +mod dict; +mod scope; +mod value; + +pub use dict::*; +pub use scope::*; +pub use value::*; diff --git a/src/eval/scope.rs b/src/eval/scope.rs new file mode 100644 index 00000000..8e6576d1 --- /dev/null +++ b/src/eval/scope.rs @@ -0,0 +1,35 @@ +//! Mapping from identifiers to functions. + +use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; + +use super::value::FuncValue; + +/// A map from identifiers to functions. +pub struct Scope { + functions: HashMap, +} + +impl Scope { + // Create a new empty scope with a fallback function that is invoked when no + // match is found. + pub fn new() -> Self { + Self { functions: HashMap::new() } + } + + /// Associate the given name with the function. + pub fn insert(&mut self, name: impl Into, function: FuncValue) { + self.functions.insert(name.into(), function); + } + + /// Return the function with the given name if there is one. + pub fn func(&self, name: &str) -> Option<&FuncValue> { + self.functions.get(name) + } +} + +impl Debug for Scope { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_set().entries(self.functions.keys()).finish() + } +} diff --git a/src/eval/value.rs b/src/eval/value.rs new file mode 100644 index 00000000..cfbc302c --- /dev/null +++ b/src/eval/value.rs @@ -0,0 +1,496 @@ +//! 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 { + /// 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(""), + 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) -> DynFuture>>; + +/// A dictionary of values. +/// +/// # Example +/// ```typst +/// (false, 12cm, greeting="hi") +/// ``` +pub type DictValue = Dict>; + +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(&mut self) -> Option { + 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( + &mut self, + name: &str, + span: Span, + f: &mut Feedback, + ) -> Option { + 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(&mut self, key: &str, f: &mut Feedback) -> Option + 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 + '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 + 'a + where + T: TryFromValue, + { + self.take_all_num::().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 + '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; +} + +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 { + #[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 { + 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 TryFromValue for Spanned { + fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option { + 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`. +pub struct StringLike(pub String); + +impl From 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 { + 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 { + 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::(), Some("hi".to_string())); + assert_eq!(dict.len(), 1); + assert_eq!(dict.take::(), 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::("", 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::(), Some(false)); + assert_eq!(dict.take_key::("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::().collect::>(), [ + (1, false), + (7, true) + ],); + assert_eq!(dict.len(), 1); + assert_eq!(dict[3].val.v, Value::Number(0.0)); + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 5f32167a..0843e80a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -21,7 +21,7 @@ pub mod prelude { pub use primitive::*; pub use tree::layout_tree as layout; -use crate::compute::Scope; +use crate::eval::Scope; use crate::font::SharedFontLoader; use crate::geom::{Margins, Size}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; diff --git a/src/lib.rs b/src/lib.rs index 6d52fd37..43152542 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ mod macros; pub mod diagnostic; pub mod color; -pub mod compute; +pub mod eval; pub mod export; pub mod font; pub mod geom; @@ -44,8 +44,8 @@ use std::fmt::Debug; use std::future::Future; use std::pin::Pin; -use crate::compute::{Scope, Value}; use crate::diagnostic::Diagnostic; +use crate::eval::{Scope, Value}; use crate::font::SharedFontLoader; use crate::layout::{Commands, MultiLayout}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; diff --git a/src/library/mod.rs b/src/library/mod.rs index 0e99a5c5..a5fdfc4c 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -16,7 +16,7 @@ pub use spacing::*; use std::rc::Rc; -use crate::compute::Scope; +use crate::eval::Scope; use crate::prelude::*; macro_rules! std { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ea030ce1..cc0b6378 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -13,7 +13,7 @@ pub use tokens::*; use std::str::FromStr; use crate::color::RgbaColor; -use crate::compute::DictKey; +use crate::eval::DictKey; use crate::syntax::*; use crate::{Feedback, Pass}; diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 7ffbeeda..9d6b673f 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; use super::parse; use crate::color::RgbaColor; -use crate::compute::DictKey; +use crate::eval::DictKey; use crate::length::Length; use crate::syntax::*; diff --git a/src/prelude.rs b/src/prelude.rs index 2938efd0..8f358430 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,7 @@ //! A prelude for building custom functions. pub use super::*; -pub use crate::compute::*; +pub use crate::eval::*; pub use crate::layout::prelude::*; pub use crate::layout::Command::{self, *}; pub use crate::layout::Commands; diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 8705b6bb..7f4d03d5 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -2,7 +2,7 @@ use super::span::{SpanWith, Spanned}; use super::{Decoration, Ident, Lit, LitDict}; -use crate::compute::Value; +use crate::eval::Value; use crate::layout::LayoutContext; use crate::Feedback; diff --git a/src/syntax/lit.rs b/src/syntax/lit.rs index 9684e5d3..3cd94583 100644 --- a/src/syntax/lit.rs +++ b/src/syntax/lit.rs @@ -2,7 +2,7 @@ use super::{Expr, Ident, SpanWith, Spanned, SynTree}; use crate::color::RgbaColor; -use crate::compute::{DictKey, DictValue, SpannedEntry, Value}; +use crate::eval::{DictKey, DictValue, SpannedEntry, Value}; use crate::layout::LayoutContext; use crate::length::Length; use crate::{DynFuture, Feedback}; -- cgit v1.2.3