summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-04 22:36:20 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-04 22:36:20 +0200
commit605ab104c5e041c345007020d277b4c6267debe6 (patch)
treec18f3333a0c0e0527ad1039a498cb210300f7fd9 /src/eval
parentef8aa763faa59fd62c90c6d6245e8d2c5eece35e (diff)
Better argument parsing 🥙
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs134
-rw-r--r--src/eval/convert.rs194
-rw-r--r--src/eval/dict.rs207
-rw-r--r--src/eval/mod.rs9
-rw-r--r--src/eval/value.rs382
5 files changed, 478 insertions, 448 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
new file mode 100644
index 00000000..d71cc374
--- /dev/null
+++ b/src/eval/args.rs
@@ -0,0 +1,134 @@
+//! Simplifies argument parsing.
+
+use std::mem;
+
+use super::*;
+
+/// A wrapper around a dictionary value that simplifies argument parsing in
+/// functions.
+pub struct Args(pub Spanned<ValueDict>);
+
+impl Args {
+ /// Retrieve and remove the argument associated with the given key if there
+ /// is any.
+ ///
+ /// Generates an error if the key exists, but the value can't be converted
+ /// into the type `T`.
+ pub fn get<'a, K, T>(&mut self, ctx: &mut LayoutContext, key: K) -> Option<T>
+ where
+ K: Into<RefKey<'a>>,
+ T: Convert,
+ {
+ self.0.v.remove(key).and_then(|entry| {
+ let span = entry.value.span;
+ let (t, diag) = T::convert(entry.value);
+ if let Some(diag) = diag {
+ ctx.f.diags.push(diag.span_with(span))
+ }
+ t.ok()
+ })
+ }
+
+ /// Retrieve and remove the first matching positional argument.
+ pub fn find<T>(&mut self) -> Option<T>
+ where
+ T: Convert,
+ {
+ for (&key, entry) in self.0.v.nums_mut() {
+ let span = entry.value.span;
+ match T::convert(mem::take(&mut entry.value)).0 {
+ Ok(t) => {
+ self.0.v.remove(key);
+ return Some(t);
+ }
+ Err(v) => entry.value = v.span_with(span),
+ }
+ }
+ None
+ }
+
+ /// Retrieve and remove all matching positional arguments.
+ pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_
+ where
+ T: Convert,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (&key, entry) in self.0.v.nums_mut().skip(skip) {
+ let span = entry.value.span;
+ match T::convert(mem::take(&mut entry.value)).0 {
+ Ok(t) => {
+ self.0.v.remove(key);
+ return Some(t);
+ }
+ Err(v) => entry.value = v.span_with(span),
+ }
+ skip += 1;
+ }
+ None
+ })
+ }
+
+ /// Retrieve and remove all matching keyword arguments.
+ pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
+ where
+ T: Convert,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (key, entry) in self.0.v.strs_mut().skip(skip) {
+ let span = entry.value.span;
+ match T::convert(mem::take(&mut entry.value)).0 {
+ Ok(t) => {
+ let key = key.clone();
+ self.0.v.remove(&key);
+ return Some((key, t));
+ }
+ Err(v) => entry.value = v.span_with(span),
+ }
+ skip += 1;
+ }
+
+ None
+ })
+ }
+
+ /// Generated _unexpected argument_ errors for all remaining entries.
+ pub fn done(&self, ctx: &mut LayoutContext) {
+ for entry in self.0.v.values() {
+ let span = entry.key_span.join(entry.value.span);
+ error!(@ctx.f, span, "unexpected argument");
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn entry(value: Value) -> SpannedEntry<Value> {
+ SpannedEntry::value(Spanned::zero(value))
+ }
+
+ #[test]
+ fn test_args_find() {
+ let mut args = Args(Spanned::zero(Dict::new()));
+ args.0.v.insert(1, entry(Value::Bool(false)));
+ args.0.v.insert(2, entry(Value::Str("hi".to_string())));
+ assert_eq!(args.find::<String>(), Some("hi".to_string()));
+ assert_eq!(args.0.v.len(), 1);
+ assert_eq!(args.find::<bool>(), Some(false));
+ assert!(args.0.v.is_empty());
+ }
+
+ #[test]
+ fn test_args_find_all() {
+ let mut args = Args(Spanned::zero(Dict::new()));
+ args.0.v.insert(1, entry(Value::Bool(false)));
+ args.0.v.insert(3, entry(Value::Float(0.0)));
+ args.0.v.insert(7, entry(Value::Bool(true)));
+ assert_eq!(args.find_all::<bool>().collect::<Vec<_>>(), [false, true]);
+ assert_eq!(args.0.v.len(), 1);
+ assert_eq!(args.0.v[3].value.v, Value::Float(0.0));
+ }
+}
diff --git a/src/eval/convert.rs b/src/eval/convert.rs
new file mode 100644
index 00000000..a32d5912
--- /dev/null
+++ b/src/eval/convert.rs
@@ -0,0 +1,194 @@
+//! Conversion from values into other types.
+
+use std::ops::Deref;
+
+use fontdock::{FontStretch, FontStyle, FontWeight};
+
+use super::*;
+use crate::diag::Diag;
+use crate::geom::Linear;
+use crate::layout::{Dir, SpecAlign};
+use crate::paper::Paper;
+
+/// Types that values can be converted into.
+pub trait Convert: Sized {
+ /// Convert a value into `Self`.
+ ///
+ /// If the conversion works out, this should return `Ok(...)` with an
+ /// instance of `Self`. If it doesn't, it should return `Err(...)` giving
+ /// back the original value.
+ ///
+ /// In addition to the result, the method can return an optional diagnostic
+ /// to warn even when the conversion suceeded or to explain the problem when
+ /// the conversion failed.
+ ///
+ /// The function takes a `Spanned<Value>` instead of just a `Value` so that
+ /// this trait can be blanket implemented for `Spanned<T>` where `T:
+ /// Convert`.
+ fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>);
+}
+
+impl<T: Convert> Convert for Spanned<T> {
+ fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
+ let span = value.span;
+ let (result, diag) = T::convert(value);
+ (result.map(|v| v.span_with(span)), diag)
+ }
+}
+
+/// A value type that matches [length] values.
+///
+/// [length]: enum.Value.html#variant.Length
+pub struct Absolute(pub f64);
+
+impl From<Absolute> for f64 {
+ fn from(abs: Absolute) -> f64 {
+ abs.0
+ }
+}
+
+/// A value type that matches [relative] values.
+///
+/// [relative]: enum.Value.html#variant.Relative
+pub struct Relative(pub f64);
+
+impl From<Relative> for f64 {
+ fn from(rel: Relative) -> f64 {
+ rel.0
+ }
+}
+
+/// A value type that matches [identifier] and [string] values.
+///
+/// [identifier]: enum.Value.html#variant.Ident
+/// [string]: enum.Value.html#variant.Str
+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()
+ }
+}
+
+macro_rules! impl_match {
+ ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
+ impl Convert for $type {
+ fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
+ #[allow(unreachable_patterns)]
+ match value.v {
+ $($p => (Ok($r), None)),*,
+ v => {
+ let err = error!("expected {}, found {}", $name, v.ty());
+ (Err(v), Some(err))
+ },
+ }
+ }
+ }
+ };
+}
+
+impl_match!(Value, "value", v => v);
+impl_match!(Ident, "ident", Value::Ident(v) => v);
+impl_match!(bool, "bool", Value::Bool(v) => v);
+impl_match!(i64, "int", Value::Int(v) => v);
+impl_match!(f64, "float",
+ Value::Int(v) => v as f64,
+ Value::Float(v) => v,
+);
+impl_match!(Absolute, "length", Value::Length(v) => Absolute(v));
+impl_match!(Relative, "relative", Value::Relative(v) => Relative(v));
+impl_match!(Linear, "linear",
+ Value::Linear(v) => v,
+ Value::Length(v) => Linear::abs(v),
+ Value::Relative(v) => Linear::rel(v),
+);
+impl_match!(String, "string", Value::Str(v) => v);
+impl_match!(SynTree, "tree", Value::Content(v) => v);
+impl_match!(ValueDict, "dict", Value::Dict(v) => v);
+impl_match!(ValueFunc, "function", Value::Func(v) => v);
+impl_match!(StringLike, "ident or string",
+ Value::Ident(Ident(v)) => StringLike(v),
+ Value::Str(v) => StringLike(v),
+);
+
+macro_rules! impl_ident {
+ ($type:ty, $name:expr, $parse:expr) => {
+ impl Convert for $type {
+ fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
+ match value.v {
+ Value::Ident(id) => {
+ if let Some(thing) = $parse(&id) {
+ (Ok(thing), None)
+ } else {
+ (Err(Value::Ident(id)), Some(error!("invalid {}", $name)))
+ }
+ }
+ v => {
+ let err = error!("expected {}, found {}", $name, v.ty());
+ (Err(v), Some(err))
+ }
+ }
+ }
+ }
+ };
+}
+
+impl_ident!(Dir, "direction", |v| match v {
+ "ltr" => Some(Self::LTR),
+ "rtl" => Some(Self::RTL),
+ "ttb" => Some(Self::TTB),
+ "btt" => Some(Self::BTT),
+ _ => None,
+});
+
+impl_ident!(SpecAlign, "alignment", |v| match v {
+ "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 Convert for FontWeight {
+ fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
+ match value.v {
+ Value::Int(number) => {
+ let [min, max] = [100, 900];
+ let warning = if number < min {
+ Some(warning!("the minimum font weight is {}", min))
+ } else if number > max {
+ Some(warning!("the maximum font weight is {}", max))
+ } else {
+ None
+ };
+ let weight = Self::from_number(number.min(max).max(min) as u16);
+ (Ok(weight), warning)
+ }
+ Value::Ident(id) => {
+ if let Some(thing) = FontWeight::from_str(&id) {
+ (Ok(thing), None)
+ } else {
+ (Err(Value::Ident(id)), Some(error!("invalid font weight")))
+ }
+ }
+ v => {
+ let err =
+ error!("expected font weight (name or number), found {}", v.ty());
+ (Err(v), Some(err))
+ }
+ }
+ }
+}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index 8d926833..1374c840 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -2,7 +2,7 @@
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
-use std::iter::Extend;
+use std::iter::{Extend, FromIterator};
use std::ops::Index;
use crate::syntax::{Span, Spanned};
@@ -115,25 +115,95 @@ impl<V> Dict<V> {
self.nums.insert(self.lowest_free, value);
self.lowest_free += 1;
}
+}
+
+impl<'a, K, V> Index<K> for Dict<V>
+where
+ K: Into<RefKey<'a>>,
+{
+ type Output = V;
+
+ fn index(&self, index: K) -> &Self::Output {
+ self.get(index).expect("key not in dict")
+ }
+}
+
+impl<V: Eq> Eq for Dict<V> {}
+
+impl<V: PartialEq> PartialEq for Dict<V> {
+ fn eq(&self, other: &Self) -> bool {
+ self.iter().eq(other.iter())
+ }
+}
+
+impl<V> Default for Dict<V> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<V: Debug> Debug for Dict<V> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if self.is_empty() {
+ return f.write_str("()");
+ }
+
+ let mut builder = f.debug_tuple("");
+
+ struct Entry<'a>(bool, &'a dyn Display, &'a dyn Debug);
+ impl<'a> Debug for Entry<'a> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if self.0 {
+ f.write_str("\"")?;
+ }
+ self.1.fmt(f)?;
+ if self.0 {
+ f.write_str("\"")?;
+ }
+ 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()
+ }
+}
+/// Iteration.
+impl<V> Dict<V> {
/// Iterator over all borrowed keys and values.
pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
self.into_iter()
}
+ /// Iterator over all borrowed keys and values.
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = (RefKey, &mut V)> {
+ self.into_iter()
+ }
+
/// Iterate over all values in the dictionary.
pub fn values(&self) -> impl Iterator<Item = &V> {
self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
}
- /// Iterate over 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()
+ /// Iterate over all values in the dictionary.
+ pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
+ self.nums
+ .iter_mut()
+ .map(|(_, v)| v)
+ .chain(self.strs.iter_mut().map(|(_, v)| v))
}
/// Move into an owned iterator over all values in the dictionary.
@@ -145,27 +215,49 @@ impl<V> Dict<V> {
}
/// Iterate over the number key-value pairs.
+ pub fn nums(&self) -> std::collections::btree_map::Iter<u64, V> {
+ self.nums.iter()
+ }
+
+ /// Iterate mutably over the number key-value pairs.
+ pub fn nums_mut(&mut self) -> std::collections::btree_map::IterMut<u64, V> {
+ self.nums.iter_mut()
+ }
+
+ /// Iterate over the number key-value pairs.
pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
self.nums.into_iter()
}
/// Iterate over the string key-value pairs.
+ pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
+ self.strs.iter()
+ }
+
+ /// Iterate mutably over the string key-value pairs.
+ pub fn strs_mut(&mut self) -> std::collections::btree_map::IterMut<String, V> {
+ self.strs.iter_mut()
+ }
+
+ /// Iterate over the string key-value pairs.
pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
self.strs.into_iter()
}
}
-impl<V> Default for Dict<V> {
- fn default() -> Self {
- Self::new()
+impl<V> Extend<(DictKey, V)> for Dict<V> {
+ fn extend<T: IntoIterator<Item = (DictKey, V)>>(&mut self, iter: T) {
+ for (key, value) in iter.into_iter() {
+ self.insert(key, value);
+ }
}
}
-impl<V: 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> FromIterator<(DictKey, V)> for Dict<V> {
+ fn from_iter<T: IntoIterator<Item = (DictKey, V)>>(iter: T) -> Self {
+ let mut v = Self::new();
+ v.extend(iter);
+ v
}
}
@@ -203,70 +295,35 @@ impl<'a, V> IntoIterator for &'a Dict<V> {
>;
fn into_iter(self) -> Self::IntoIter {
- let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
+ let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
nums.chain(strs)
}
}
-impl<V> Extend<(DictKey, V)> for Dict<V> {
- fn extend<T>(&mut self, iter: T)
- where
- T: IntoIterator<Item = (DictKey, V)>,
- {
- for (key, value) in iter.into_iter() {
- self.insert(key, value);
- }
- }
-}
-
-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: 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));
- }
+impl<'a, V> IntoIterator for &'a mut Dict<V> {
+ type Item = (RefKey<'a>, &'a mut V);
+ type IntoIter = std::iter::Chain<
+ std::iter::Map<
+ std::collections::btree_map::IterMut<'a, u64, V>,
+ fn((&'a u64, &'a mut V)) -> (RefKey<'a>, &'a mut V),
+ >,
+ std::iter::Map<
+ std::collections::btree_map::IterMut<'a, String, V>,
+ fn((&'a String, &'a mut V)) -> (RefKey<'a>, &'a mut V),
+ >,
+ >;
- builder.finish()
+ fn into_iter(self) -> Self::IntoIter {
+ let nums = self
+ .nums
+ .iter_mut()
+ .map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
+ let strs = self
+ .strs
+ .iter_mut()
+ .map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
+ nums.chain(strs)
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 3796669b..4ec29056 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -1,10 +1,14 @@
//! Evaluation of syntax trees.
+mod args;
+mod convert;
mod dict;
mod scope;
mod state;
mod value;
+pub use args::*;
+pub use convert::*;
pub use dict::*;
pub use scope::*;
pub use state::*;
@@ -88,9 +92,10 @@ impl Eval for ExprCall {
async fn eval(&self, ctx: &mut LayoutContext) -> Self::Output {
let name = &self.name.v;
let span = self.name.span;
- let args = self.args.eval(ctx).await;
+ let dict = self.args.v.eval(ctx).await;
if let Some(func) = ctx.state.scope.get(name) {
+ let args = Args(dict.span_with(self.args.span));
ctx.f.decos.push(Deco::Resolved.span_with(span));
(func.clone())(args, ctx).await
} else {
@@ -98,7 +103,7 @@ impl Eval for ExprCall {
error!(@ctx.f, span, "unknown function");
ctx.f.decos.push(Deco::Unresolved.span_with(span));
}
- Value::Dict(args)
+ Value::Dict(dict)
}
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 16c3dca8..2d83c8d0 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,15 +4,12 @@ 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 super::{Args, Dict, SpannedEntry};
use crate::color::RgbaColor;
use crate::geom::Linear;
-use crate::layout::{Command, Dir, LayoutContext, SpecAlign};
-use crate::paper::Paper;
+use crate::layout::{Command, LayoutContext};
use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree};
-use crate::{DynFuture, Feedback};
+use crate::DynFuture;
/// A computational value.
#[derive(Clone, PartialEq)]
@@ -78,6 +75,12 @@ impl Value {
}
}
+impl Default for Value {
+ fn default() -> Self {
+ Value::None
+ }
+}
+
impl Spanned<Value> {
/// Transform this value into something layoutable.
///
@@ -156,13 +159,13 @@ impl Debug for Value {
pub struct ValueFunc(pub Rc<Func>);
/// The signature of executable functions.
-pub type Func = dyn Fn(ValueDict, &mut LayoutContext) -> DynFuture<Value>;
+pub type Func = dyn Fn(Args, &mut LayoutContext) -> DynFuture<Value>;
impl ValueFunc {
/// Create a new function value from a rust function or closure.
pub fn new<F: 'static>(f: F) -> Self
where
- F: Fn(ValueDict, &mut LayoutContext) -> DynFuture<Value>,
+ F: Fn(Args, &mut LayoutContext) -> DynFuture<Value>,
{
Self(Rc::new(f))
}
@@ -197,366 +200,3 @@ impl Debug for ValueFunc {
/// (false, 12cm, greeting="hi")
/// ```
pub type ValueDict = Dict<SpannedEntry<Value>>;
-
-impl ValueDict {
- /// 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.value.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.value.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.value.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.value.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.value.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() {
- error!(@f, entry.key_span.join(entry.value.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.ty()
- );
- 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.ty()
- );
- 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 })
- }
-}
-
-/// A value type that matches [length] values.
-///
-/// [length]: enum.Value.html#variant.Length
-pub struct Absolute(pub f64);
-
-impl From<Absolute> for f64 {
- fn from(abs: Absolute) -> f64 {
- abs.0
- }
-}
-
-/// A value type that matches [relative] values.
-///
-/// [relative]: enum.Value.html#variant.Relative
-pub struct Relative(pub f64);
-
-impl From<Relative> for f64 {
- fn from(rel: Relative) -> f64 {
- rel.0
- }
-}
-
-/// A value type that matches [identifier] and [string] values.
-///
-/// [identifier]: enum.Value.html#variant.Ident
-/// [string]: enum.Value.html#variant.Str
-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!(Value, "value", v => v.clone());
-impl_match!(Ident, "identifier", Value::Ident(v) => v.clone());
-impl_match!(bool, "bool", &Value::Bool(v) => v);
-impl_match!(i64, "integer", &Value::Int(v) => v);
-impl_match!(f64, "float",
- &Value::Int(v) => v as f64,
- &Value::Float(v) => v,
-);
-impl_match!(Absolute, "length", &Value::Length(v) => Absolute(v));
-impl_match!(Relative, "relative", &Value::Relative(v) => Relative(v));
-impl_match!(Linear, "linear",
- &Value::Linear(v) => v,
- &Value::Length(v) => Linear::abs(v),
- &Value::Relative(v) => Linear::rel(v),
-);
-impl_match!(String, "string", Value::Str(v) => v.clone());
-impl_match!(SynTree, "tree", Value::Content(v) => v.clone());
-impl_match!(ValueDict, "dict", Value::Dict(v) => v.clone());
-impl_match!(ValueFunc, "function", Value::Func(v) => v.clone());
-impl_match!(StringLike, "identifier or string",
- Value::Ident(Ident(v)) => StringLike(v.clone()),
- Value::Str(v) => StringLike(v.clone()),
-);
-
-impl_ident!(Dir, "direction", |v| match v {
- "ltr" => Some(Self::LTR),
- "rtl" => Some(Self::RTL),
- "ttb" => Some(Self::TTB),
- "btt" => Some(Self::BTT),
- _ => None,
-});
-
-impl_ident!(SpecAlign, "alignment", |v| match v {
- "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::Int(weight) => {
- const MIN: i64 = 100;
- const MAX: i64 = 900;
- let weight = if weight < MIN {
- error!(@f, value.span, "the minimum font weight is {}", MIN);
- MIN
- } else if weight > MAX {
- error!(@f, value.span, "the maximum font weight is {}", MAX);
- MAX
- } else {
- weight
- };
- Self::from_number(weight 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 integer), found {}",
- other.ty(),
- );
- None
- }
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn entry(value: Value) -> SpannedEntry<Value> {
- SpannedEntry::value(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.diags, [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.diags, [error!(Span::ZERO, "expected float, 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::Float(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].value.v, Value::Float(0.0));
- }
-}