diff options
Diffstat (limited to 'src/eval/args.rs')
| -rw-r--r-- | src/eval/args.rs | 246 |
1 files changed, 100 insertions, 146 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs index 43c30daf..ea248ec4 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -1,184 +1,138 @@ -//! Simplifies argument parsing. +use super::*; + +/// Evaluated arguments to a function. +#[derive(Debug)] +pub struct Args { + span: Span, + pos: SpanVec<Value>, + named: Vec<(Spanned<String>, Spanned<Value>)>, +} -use std::mem; +impl Eval for Spanned<&Arguments> { + type Output = Args; -use super::{Conv, EvalContext, RefKey, TryFromValue, Value, ValueDict}; -use crate::diag::Diag; -use crate::syntax::{Span, SpanVec, Spanned, WithSpan}; + fn eval(self, ctx: &mut EvalContext) -> Self::Output { + let mut pos = vec![]; + let mut named = vec![]; -/// A wrapper around a dictionary value that simplifies argument parsing in -/// functions. -pub struct Args(pub Spanned<ValueDict>); + for arg in self.v { + match arg { + Argument::Pos(expr) => { + pos.push(expr.as_ref().eval(ctx).with_span(expr.span)); + } + Argument::Named(Named { name, expr }) => { + named.push(( + name.as_ref().map(|id| id.0.clone()), + expr.as_ref().eval(ctx).with_span(expr.span), + )); + } + } + } -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 EvalContext, key: K) -> Option<T> - where - K: Into<RefKey<'a>>, - T: TryFromValue, - { - self.0.v.remove(key).and_then(|entry| { - let span = entry.value.span; - conv_diag( - T::try_from_value(entry.value), - &mut ctx.feedback.diags, - span, - ) - }) + Args { span: self.span, pos, named } } +} - /// This is the same as [`get`](Self::get), except that it generates an error about a - /// missing argument with the given `name` if the key does not exist. - pub fn need<'a, K, T>( - &mut self, - ctx: &mut EvalContext, - key: K, - name: &str, - ) -> Option<T> +impl Args { + /// Find the first convertible positional argument. + pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T> where - K: Into<RefKey<'a>>, - T: TryFromValue, + T: Cast<Spanned<Value>>, { - if let Some(entry) = self.0.v.remove(key) { - let span = entry.value.span; - conv_diag( - T::try_from_value(entry.value), - &mut ctx.feedback.diags, - span, - ) - } else { - ctx.diag(error!(self.0.span, "missing argument: {}", name)); - None - } + self.pos.iter_mut().find_map(move |slot| try_cast(ctx, slot)) } - /// Retrieve and remove the first matching positional argument. - pub fn find<T>(&mut self) -> Option<T> + /// Find the first convertible positional argument, producing an error if + /// no match was found. + pub fn require<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T> where - T: TryFromValue, + T: Cast<Spanned<Value>>, { - for (&key, entry) in self.0.v.nums_mut() { - let span = entry.value.span; - let slot = &mut entry.value; - let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span); - if let Some(t) = conv { - self.0.v.remove(key); - return Some(t); - } + let found = self.find(ctx); + if found.is_none() { + ctx.diag(error!(self.span, "missing argument: {}", what)); } - None + found } - /// Retrieve and remove all matching positional arguments. - pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_ + /// Filter out all convertible positional arguments. + pub fn filter<'a, T>( + &'a mut self, + ctx: &'a mut EvalContext, + ) -> impl Iterator<Item = T> + 'a where - T: TryFromValue, + T: Cast<Spanned<Value>>, { - 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; - let slot = &mut entry.value; - let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span); - if let Some(t) = conv { - self.0.v.remove(key); - return Some(t); - } - skip += 1; - } - None - }) + self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot)) } - /// Retrieve and remove all matching named arguments. - pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_ + /// Convert the value for the given named argument. + /// + /// Generates an error if the conversion fails. + pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> where - T: TryFromValue, + T: Cast<Spanned<Value>>, { - 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; - let slot = &mut entry.value; - let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span); - if let Some(t) = conv { - let key = key.clone(); - self.0.v.remove(&key); - return Some((key, t)); - } - skip += 1; - } - - None - }) + let index = self.named.iter().position(|(k, _)| k.v.as_str() == name)?; + let value = self.named.remove(index).1; + cast(ctx, value) } - /// Generated _unexpected argument_ errors for all remaining entries. - pub fn done(&self, ctx: &mut EvalContext) { - for entry in self.0.v.values() { - let span = entry.key_span.join(entry.value.span); - ctx.diag(error!(span, "unexpected argument")); + /// Generate "unexpected argument" errors for all remaining arguments. + pub fn finish(self, ctx: &mut EvalContext) { + let a = self.pos.iter().map(|v| v.as_ref()); + let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span))); + for value in a.chain(b) { + if value.v != &Value::Error { + ctx.diag(error!(value.span, "unexpected argument")); + } } } } -fn conv_diag<T>(conv: Conv<T>, diags: &mut SpanVec<Diag>, span: Span) -> Option<T> { - match conv { - Conv::Ok(t) => Some(t), - Conv::Warn(t, warn) => { - diags.push(warn.with_span(span)); +/// Cast the value into `T`, generating an error if the conversion fails. +fn cast<T>(ctx: &mut EvalContext, value: Spanned<Value>) -> Option<T> +where + T: Cast<Spanned<Value>>, +{ + let span = value.span; + match T::cast(value) { + CastResult::Ok(t) => Some(t), + CastResult::Warn(t, m) => { + ctx.diag(warning!(span, "{}", m)); Some(t) } - Conv::Err(_, err) => { - diags.push(err.with_span(span)); + CastResult::Err(value) => { + ctx.diag(error!( + span, + "expected {}, found {}", + T::TYPE_NAME, + value.v.type_name() + )); None } } } -fn conv_put_back<T>(conv: Conv<T>, slot: &mut Spanned<Value>, span: Span) -> Option<T> { - match conv { - Conv::Ok(t) => Some(t), - Conv::Warn(t, _) => Some(t), - Conv::Err(v, _) => { - *slot = v.with_span(span); +/// Try to cast the value in the slot into `T`, putting it back if the +/// conversion fails. +fn try_cast<T>(ctx: &mut EvalContext, slot: &mut Spanned<Value>) -> Option<T> +where + T: Cast<Spanned<Value>>, +{ + // Replace with error placeholder when conversion works since error values + // are ignored when generating "unexpected argument" errors. + let value = std::mem::replace(slot, Spanned::zero(Value::Error)); + let span = value.span; + match T::cast(value) { + CastResult::Ok(t) => Some(t), + CastResult::Warn(t, m) => { + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + *slot = value; None } } } - -#[cfg(test)] -mod tests { - use super::super::{Dict, SpannedEntry, Value}; - 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)); - } -} |
