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 /src/eval/args.rs | |
| parent | ef8aa763faa59fd62c90c6d6245e8d2c5eece35e (diff) | |
Better argument parsing 🥙
Diffstat (limited to 'src/eval/args.rs')
| -rw-r--r-- | src/eval/args.rs | 134 |
1 files changed, 134 insertions, 0 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)); + } +} |
