diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-04 22:36:20 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-04 22:36:20 +0200 |
| commit | 605ab104c5e041c345007020d277b4c6267debe6 (patch) | |
| tree | c18f3333a0c0e0527ad1039a498cb210300f7fd9 | |
| parent | ef8aa763faa59fd62c90c6d6245e8d2c5eece35e (diff) | |
Better argument parsing 🥙
| -rw-r--r-- | src/eval/args.rs | 134 | ||||
| -rw-r--r-- | src/eval/convert.rs | 194 | ||||
| -rw-r--r-- | src/eval/dict.rs | 207 | ||||
| -rw-r--r-- | src/eval/mod.rs | 9 | ||||
| -rw-r--r-- | src/eval/value.rs | 382 | ||||
| -rw-r--r-- | src/library/align.rs | 44 | ||||
| -rw-r--r-- | src/library/boxed.rs | 15 | ||||
| -rw-r--r-- | src/library/color.rs | 16 | ||||
| -rw-r--r-- | src/library/font.rs | 37 | ||||
| -rw-r--r-- | src/library/page.rs | 26 | ||||
| -rw-r--r-- | src/library/spacing.rs | 13 | ||||
| -rw-r--r-- | src/parse/mod.rs | 20 | ||||
| -rw-r--r-- | src/parse/tests.rs | 74 | ||||
| -rw-r--r-- | src/prelude.rs | 2 | ||||
| -rw-r--r-- | src/syntax/ast/expr.rs | 2 | ||||
| -rw-r--r-- | src/syntax/span.rs | 14 |
16 files changed, 622 insertions, 567 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)); - } -} diff --git a/src/library/align.rs b/src/library/align.rs index 0c145e5f..674ecceb 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -14,21 +14,41 @@ use super::*; /// - `vertical`: Any of `top`, `bottom` or `center`. /// /// There may not be two alignment specifications for the same axis. -pub async fn align(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { - let content = args.take::<SynTree>(); - let h = args.take_key::<Spanned<SpecAlign>>("horizontal", &mut ctx.f); - let v = args.take_key::<Spanned<SpecAlign>>("vertical", &mut ctx.f); - let all = args - .take_all_num_vals::<Spanned<SpecAlign>>() +pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value { + let body = args.find::<SynTree>(); + + let h = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal"); + let v = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical"); + let pos = args.find_all::<Spanned<SpecAlign>>(); + + let iter = pos .map(|align| (align.v.axis(), align)) .chain(h.into_iter().map(|align| (Some(SpecAxis::Horizontal), align))) .chain(v.into_iter().map(|align| (Some(SpecAxis::Vertical), align))); + let aligns = parse_aligns(ctx, iter); + + args.done(ctx); + Value::Commands(match body { + Some(tree) => vec![ + SetAlignment(aligns), + LayoutSyntaxTree(tree), + SetAlignment(ctx.state.align), + ], + None => vec![SetAlignment(aligns)], + }) +} + +/// Deduplicate alignments and deduce to which axes they apply. +fn parse_aligns( + ctx: &mut LayoutContext, + iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>, +) -> LayoutAlign { let mut aligns = ctx.state.align; let mut had = [false; 2]; let mut deferred_center = false; - for (axis, align) in all { + for (axis, align) in iter { // Check whether we know which axis this alignment belongs to. We don't // if the alignment is `center` for a positional argument. Then we set // `deferred_center` to true and handle the situation once we know more. @@ -80,13 +100,5 @@ pub async fn align(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { aligns.primary = GenAlign::Center; } - args.unexpected(&mut ctx.f); - Value::Commands(match content { - Some(tree) => vec![ - SetAlignment(aligns), - LayoutSyntaxTree(tree), - SetAlignment(ctx.state.align), - ], - None => vec![SetAlignment(aligns)], - }) + aligns } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index c97df8d0..81e3a96a 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -6,31 +6,32 @@ use crate::geom::Linear; /// # Keyword arguments /// - `width`: The width of the box (length or relative to parent's width). /// - `height`: The height of the box (length or relative to parent's height). -pub async fn boxed(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { - let content = args.take::<SynTree>().unwrap_or_default(); +pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value { + let body = args.find::<SynTree>().unwrap_or_default(); + let width = args.get::<_, Linear>(ctx, "width"); + let height = args.get::<_, Linear>(ctx, "height"); + args.done(ctx); let constraints = &mut ctx.constraints; constraints.base = constraints.spaces[0].size; constraints.spaces.truncate(1); constraints.repeat = false; - if let Some(width) = args.take_key::<Linear>("width", &mut ctx.f) { + if let Some(width) = width { let abs = width.eval(constraints.base.width); constraints.base.width = abs; constraints.spaces[0].size.width = abs; constraints.spaces[0].expansion.horizontal = true; } - if let Some(height) = args.take_key::<Linear>("height", &mut ctx.f) { + if let Some(height) = height { let abs = height.eval(constraints.base.height); constraints.base.height = abs; constraints.spaces[0].size.height = abs; constraints.spaces[0].expansion.vertical = true; } - args.unexpected(&mut ctx.f); - - let layouted = layout_tree(&content, ctx).await; + let layouted = layout_tree(&body, ctx).await; let layout = layouted.into_iter().next().unwrap(); Value::Commands(vec![Add(layout)]) diff --git a/src/library/color.rs b/src/library/color.rs index 22029b1f..143ce70a 100644 --- a/src/library/color.rs +++ b/src/library/color.rs @@ -2,24 +2,22 @@ use super::*; use crate::color::RgbaColor; /// `rgb`: Create an RGB(A) color. -pub async fn rgb(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { - let mut f = Feedback::new(); - - let r = args.expect::<Spanned<i64>>("red value", Span::ZERO, &mut f); - let g = args.expect::<Spanned<i64>>("green value", Span::ZERO, &mut f); - let b = args.expect::<Spanned<i64>>("blue value", Span::ZERO, &mut f); - let a = args.take::<Spanned<i64>>(); +pub async fn rgb(mut args: Args, ctx: &mut LayoutContext) -> Value { + let r = args.get::<_, Spanned<i64>>(ctx, 0); + let g = args.get::<_, Spanned<i64>>(ctx, 1); + let b = args.get::<_, Spanned<i64>>(ctx, 2); + let a = args.get::<_, Spanned<i64>>(ctx, 3); + args.done(ctx); let mut clamp = |component: Option<Spanned<i64>>, default| { component.map_or(default, |c| { if c.v < 0 || c.v > 255 { - error!(@f, c.span, "should be between 0 and 255") + error!(@ctx.f, c.span, "should be between 0 and 255") } c.v.max(0).min(255) as u8 }) }; - args.unexpected(&mut ctx.f); Value::Color(RgbaColor::new( clamp(r, 0), clamp(g, 0), diff --git a/src/library/font.rs b/src/library/font.rs index cfda3beb..0a28beaa 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -49,13 +49,13 @@ use crate::geom::Linear; /// ```typst /// [font: "My Serif", serif] /// ``` -pub async fn font(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { +pub async fn font(mut args: Args, ctx: &mut LayoutContext) -> Value { let mut text = ctx.state.text.clone(); - let mut updated_fallback = false; + let mut needs_flatten = false; - let content = args.take::<SynTree>(); + let body = args.find::<SynTree>(); - if let Some(linear) = args.take::<Linear>() { + if let Some(linear) = args.find::<Linear>() { if linear.rel == 0.0 { text.font_size.base = linear.abs; text.font_size.scale = Linear::rel(1.0); @@ -64,44 +64,41 @@ pub async fn font(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { } } - let list: Vec<_> = args - .take_all_num_vals::<StringLike>() - .map(|s| s.to_lowercase()) - .collect(); - + let list: Vec<_> = args.find_all::<StringLike>().map(|s| s.to_lowercase()).collect(); if !list.is_empty() { text.fallback.list = list; - updated_fallback = true; + needs_flatten = true; } - if let Some(style) = args.take_key::<FontStyle>("style", &mut ctx.f) { + if let Some(style) = args.get::<_, FontStyle>(ctx, "style") { text.variant.style = style; } - if let Some(weight) = args.take_key::<FontWeight>("weight", &mut ctx.f) { + if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") { text.variant.weight = weight; } - if let Some(stretch) = args.take_key::<FontStretch>("stretch", &mut ctx.f) { + if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") { text.variant.stretch = stretch; } - for (class, mut dict) in args.take_all_str::<ValueDict>() { - let fallback = dict - .take_all_num_vals::<StringLike>() + for (class, dict) in args.find_all_str::<Spanned<ValueDict>>() { + let fallback = Args(dict) + .find_all::<StringLike>() .map(|s| s.to_lowercase()) .collect(); text.fallback.update_class_list(class, fallback); - updated_fallback = true; + needs_flatten = true; } - if updated_fallback { + args.done(ctx); + + if needs_flatten { text.fallback.flatten(); } - args.unexpected(&mut ctx.f); - Value::Commands(match content { + Value::Commands(match body { Some(tree) => vec![ SetTextState(text), LayoutSyntaxTree(tree), diff --git a/src/library/page.rs b/src/library/page.rs index b557650b..db76d2a1 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -19,54 +19,54 @@ use crate::paper::{Paper, PaperClass}; /// - `top`: The top margin (length or relative to height). /// - `bottom`: The bottom margin (length or relative to height). /// - `flip`: Flips custom or paper-defined width and height (boolean). -pub async fn page(mut args: ValueDict, ctx: &mut LayoutContext) -> Value { +pub async fn page(mut args: Args, ctx: &mut LayoutContext) -> Value { let mut page = ctx.state.page.clone(); - if let Some(paper) = args.take::<Paper>() { + if let Some(paper) = args.find::<Paper>() { page.class = paper.class; page.size = paper.size(); } - if let Some(Absolute(width)) = args.take_key::<Absolute>("width", &mut ctx.f) { + if let Some(Absolute(width)) = args.get::<_, Absolute>(ctx, "width") { page.class = PaperClass::Custom; page.size.width = width; } - if let Some(Absolute(height)) = args.take_key::<Absolute>("height", &mut ctx.f) { + if let Some(Absolute(height)) = args.get::<_, Absolute>(ctx, "height") { page.class = PaperClass::Custom; page.size.height = height; } - if let Some(margins) = args.take_key::<Linear>("margins", &mut ctx.f) { + if let Some(margins) = args.get::<_, Linear>(ctx, "margins") { page.margins = Sides::uniform(Some(margins)); } - if let Some(left) = args.take_key::<Linear>("left", &mut ctx.f) { + if let Some(left) = args.get::<_, Linear>(ctx, "left") { page.margins.left = Some(left); } - if let Some(top) = args.take_key::<Linear>("top", &mut ctx.f) { + if let Some(top) = args.get::<_, Linear>(ctx, "top") { page.margins.top = Some(top); } - if let Some(right) = args.take_key::<Linear>("right", &mut ctx.f) { + if let Some(right) = args.get::<_, Linear>(ctx, "right") { page.margins.right = Some(right); } - if let Some(bottom) = args.take_key::<Linear>("bottom", &mut ctx.f) { + if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") { page.margins.bottom = Some(bottom); } - if args.take_key::<bool>("flip", &mut ctx.f).unwrap_or(false) { + if args.get::<_, bool>(ctx, "flip").unwrap_or(false) { mem::swap(&mut page.size.width, &mut page.size.height); } - args.unexpected(&mut ctx.f); + args.done(ctx); Value::Commands(vec![SetPageState(page)]) } /// `pagebreak`: Ends the current page. -pub async fn pagebreak(args: ValueDict, ctx: &mut LayoutContext) -> Value { - args.unexpected(&mut ctx.f); +pub async fn pagebreak(args: Args, ctx: &mut LayoutContext) -> Value { + args.done(ctx); Value::Commands(vec![BreakPage]) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 9ca68263..b97f4640 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -6,7 +6,7 @@ use crate::layout::SpacingKind; /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn h(args: ValueDict, ctx: &mut LayoutContext) -> Value { +pub async fn h(args: Args, ctx: &mut LayoutContext) -> Value { spacing(args, ctx, SpecAxis::Horizontal) } @@ -14,16 +14,17 @@ pub async fn h(args: ValueDict, ctx: &mut LayoutContext) -> Value { /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn v(args: ValueDict, ctx: &mut LayoutContext) -> Value { +pub async fn v(args: Args, ctx: &mut LayoutContext) -> Value { spacing(args, ctx, SpecAxis::Vertical) } -fn spacing(mut args: ValueDict, ctx: &mut LayoutContext, axis: SpecAxis) -> Value { - let spacing = args.expect::<Linear>("spacing", Span::ZERO, &mut ctx.f); - args.unexpected(&mut ctx.f); +fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value { + let spacing = args.get::<_, Linear>(ctx, 0); + args.done(ctx); + Value::Commands(if let Some(spacing) = spacing { - let axis = axis.to_gen(ctx.state.sys); let spacing = spacing.eval(ctx.state.text.font_size()); + let axis = axis.to_gen(ctx.state.sys); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ca2375f2..7fbbf141 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -163,14 +163,14 @@ fn bracket_call(p: &mut Parser) -> ExprCall { if p.peek() == Some(Token::LeftBracket) { let expr = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p)))); inner.span.expand(expr.span); - inner.v.args.0.push(LitDictEntry { key: None, expr }); + inner.v.args.v.0.push(LitDictEntry { key: None, expr }); } while let Some(mut top) = outer.pop() { let span = inner.span; let node = inner.map(Expr::Call).map(SynNode::Expr); let expr = Expr::Lit(Lit::Content(vec![node])).span_with(span); - top.v.args.0.push(LitDictEntry { key: None, expr }); + top.v.args.v.0.push(LitDictEntry { key: None, expr }); inner = top; } @@ -194,14 +194,16 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall { p.skip_white(); let args = if p.eat_if(Token::Colon) { - dict_contents(p).0 + p.span(|p| dict_contents(p).0) } else { // Ignore the rest if there's no colon. - if !p.eof() { - p.diag_expected_at("colon", p.pos()); - } - p.eat_while(|_| true); - LitDict::new() + p.span(|p| { + if !p.eof() { + p.diag_expected_at("colon", p.pos()); + } + p.eat_while(|_| true); + LitDict::new() + }) }; p.end_group(); @@ -221,7 +223,7 @@ fn bracket_body(p: &mut Parser) -> SynTree { /// Parse a parenthesized function call. fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall { p.start_group(Group::Paren); - let args = dict_contents(p).0; + let args = p.span(|p| dict_contents(p).0); p.end_group(); ExprCall { name, args } } diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 6738998a..108b4b29 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -76,6 +76,37 @@ fn Str(string: &str) -> Expr { Expr::Lit(Lit::Str(string.to_string())) } +macro_rules! Call { + (@$name:expr $(, $span:expr)? $(; $($tts:tt)*)?) => {{ + let name = Into::<Spanned<&str>>::into($name); + #[allow(unused)] + let mut span = Span::ZERO; + $(span = $span.into();)? + ExprCall { + name: name.map(|n| Ident(n.to_string())), + args: Dict![@$($($tts)*)?].span_with(span), + } + }}; + ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; +} +fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr { + Expr::Unary(ExprUnary { + op: op.into(), + expr: expr.into().map(Box::new), + }) +} +fn Binary( + op: impl Into<Spanned<BinOp>>, + lhs: impl Into<Spanned<Expr>>, + rhs: impl Into<Spanned<Expr>>, +) -> Expr { + Expr::Binary(ExprBinary { + lhs: lhs.into().map(Box::new), + op: op.into(), + rhs: rhs.into().map(Box::new), + }) +} + macro_rules! Dict { (@dict=$dict:expr,) => {}; (@dict=$dict:expr, $key:expr => $expr:expr $(, $($tts:tt)*)?) => {{ @@ -91,7 +122,7 @@ macro_rules! Dict { Dict![@dict=$dict, $($($tts)*)?]; }; (@$($tts:tt)*) => {{ - #[allow(unused_mut)] + #[allow(unused)] let mut dict = LitDict::new(); Dict![@dict=dict, $($tts)*]; dict @@ -106,35 +137,6 @@ macro_rules! Tree { ($($tts:tt)*) => { Expr::Lit(Lit::Content(Tree![@$($tts)*])) }; } -macro_rules! Call { - (@$name:expr $(; $($tts:tt)*)?) => {{ - let name = Into::<Spanned<&str>>::into($name); - ExprCall { - name: name.map(|n| Ident(n.to_string())), - args: Dict![@$($($tts)*)?], - } - }}; - ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; -} - -fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr { - Expr::Unary(ExprUnary { - op: op.into(), - expr: expr.into().map(Box::new), - }) -} -fn Binary( - op: impl Into<Spanned<BinOp>>, - lhs: impl Into<Spanned<Expr>>, - rhs: impl Into<Spanned<Expr>>, -) -> Expr { - Expr::Binary(ExprBinary { - lhs: lhs.into().map(Box::new), - op: op.into(), - rhs: rhs.into().map(Box::new), - }) -} - // ------------------------------------ Test Macros ----------------------------------- // // Test syntax trees with or without spans. @@ -387,7 +389,7 @@ fn test_parse_function_bodies() { // Spanned. ts!(" [box][Oh my]" => s(0, 1, S), - s(1, 13, F!(s(2, 5, "box"); + s(1, 13, F!(s(2, 5, "box"), 5 .. 5; s(6, 13, Tree![ s(7, 9, T("Oh")), s(9, 10, S), s(10, 12, T("my")), ]) @@ -431,7 +433,7 @@ fn test_parse_values() { s(13, 13, "expected closing bracket")); // Spanned. - ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.4))))); + ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Float(1.4))))); } #[test] @@ -468,7 +470,7 @@ fn test_parse_expressions() { // Spanned. ts!("[val: 1 + 3]" => s(0, 12, F!( - s(1, 4, "val"); s(6, 11, Binary( + s(1, 4, "val"), 5 .. 11; s(6, 11, Binary( s(8, 9, Add), s(6, 7, Int(1)), s(10, 11, Int(3)) @@ -476,7 +478,7 @@ fn test_parse_expressions() { ))); // Span of parenthesized expression contains parens. - ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Int(1))))); + ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1))))); // Invalid expressions. v!("4pt--" => Len(Length::pt(4.0))); @@ -504,8 +506,8 @@ fn test_parse_dicts() { // Spanned with spacing around keyword arguments. ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0, 30, F!( - s(1, 4, "val"); - s(8, 10, "hi") => s(25, 29, Str("s\n")) + s(1, 4, "val"), + 5 .. 29; s(8, 10, "hi") => s(25, 29, Str("s\n")) ))); e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => ); } diff --git a/src/prelude.rs b/src/prelude.rs index 618d1537..7cca0a5c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,7 @@ //! A prelude for building custom functions. #[doc(no_inline)] -pub use crate::eval::{Dict, Value, ValueDict}; +pub use crate::eval::{Args, Dict, Value, ValueDict}; #[doc(no_inline)] pub use crate::layout::{layout_tree, primitive::*, Command, LayoutContext}; #[doc(no_inline)] diff --git a/src/syntax/ast/expr.rs b/src/syntax/ast/expr.rs index 4f440d33..433dcca2 100644 --- a/src/syntax/ast/expr.rs +++ b/src/syntax/ast/expr.rs @@ -21,7 +21,7 @@ pub struct ExprCall { /// The name of the function. pub name: Spanned<Ident>, /// The arguments to the function. - pub args: LitDict, + pub args: Spanned<LitDict>, } /// A unary operation: `-x`. diff --git a/src/syntax/span.rs b/src/syntax/span.rs index e9e1021c..6ba9c44a 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -40,7 +40,7 @@ impl<T> Offset for SpanVec<T> { } /// A value with the span it corresponds to in the source code. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Spanned<T> { /// The spanned value. @@ -178,6 +178,12 @@ impl PartialEq for Span { } } +impl Default for Span { + fn default() -> Self { + Span::ZERO + } +} + impl<T> From<T> for Span where T: Into<Pos> + Copy, @@ -229,6 +235,12 @@ impl From<u32> for Pos { } } +impl From<i32> for Pos { + fn from(index: i32) -> Self { + Self(index as u32) + } +} + impl From<usize> for Pos { fn from(index: usize) -> Self { Self(index as u32) |
