summaryrefslogtreecommitdiff
path: root/src/compute
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-01 15:13:04 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-01 15:13:04 +0200
commite676ab53ddbab367179ee2ab214bb41ff2ee0c11 (patch)
treef003f6eb6698494310da5771249bbb61a8da27a0 /src/compute
parent7c12f0c07f9d4ed473027dbd00cbbc00e4a3050c (diff)
Rename compute to eval ✏
Diffstat (limited to 'src/compute')
-rw-r--r--src/compute/dict.rs417
-rw-r--r--src/compute/mod.rs9
-rw-r--r--src/compute/scope.rs35
-rw-r--r--src/compute/value.rs496
4 files changed, 0 insertions, 957 deletions
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<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;
- }
-
- /// Iterator over all borrowed keys and values.
- pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
- 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<Item = &V> {
- 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<u64, V> {
- self.nums.iter()
- }
-
- /// Iterate over the string key-value pairs.
- pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
- self.strs.iter()
- }
-
- /// Move into an owned iterator over owned keys and values.
- pub fn into_iter(self) -> impl Iterator<Item = (DictKey, V)> {
- 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<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 into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
- self.nums.into_iter()
- }
-
- /// Iterate over the string key-value pairs.
- pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
- self.strs.into_iter()
- }
-}
-
-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> Default for Dict<V> {
- fn default() -> Self {
- Self::new()
- }
-}
-
-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: 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("\"")?;
- }
- 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<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 tracks key and value span.
-#[derive(Clone, PartialEq)]
-pub struct SpannedEntry<V> {
- pub key: Span,
- pub val: Spanned<V>,
-}
-
-impl<V> SpannedEntry<V> {
- /// Create a new entry.
- pub fn new(key: Span, val: Spanned<V>) -> Self {
- Self { key, val }
- }
-
- /// Create an entry with the same span for key and value.
- pub fn val(val: Spanned<V>) -> Self {
- Self { key: val.span, val }
- }
-
- /// Convert from `&SpannedEntry<T>` 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<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
- SpannedEntry { key: self.key, val: self.val.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.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::<Vec<_>>(), [
- "(",
- 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<String, FuncValue>,
-}
-
-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<String>, 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<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));
- }
-}