use std::fmt::{self, Debug, Formatter}; use ecow::{eco_format, EcoVec}; use super::{Array, Cast, Dict, Str, Value}; use crate::diag::{bail, At, SourceResult}; use crate::syntax::{Span, Spanned}; use crate::util::pretty_array_like; /// Evaluated arguments to a function. #[derive(Clone, PartialEq, Hash)] pub struct Args { /// The span of the whole argument list. pub span: Span, /// The positional and named arguments. pub items: EcoVec, } /// An argument to a function call: `12` or `draw: false`. #[derive(Clone, PartialEq, Hash)] pub struct Arg { /// The span of the whole argument. pub span: Span, /// The name of the argument (`None` for positional arguments). pub name: Option, /// The value of the argument. pub value: Spanned, } impl Args { /// Create positional arguments from a span and values. pub fn new(span: Span, values: impl IntoIterator) -> Self { let items = values .into_iter() .map(|value| Arg { span, name: None, value: Spanned::new(value, span) }) .collect(); Self { span, items } } /// Push a positional argument. pub fn push(&mut self, span: Span, value: Value) { self.items.push(Arg { span: self.span, name: None, value: Spanned::new(value, span), }) } /// Consume and cast the first positional argument if there is one. pub fn eat(&mut self) -> SourceResult> where T: Cast>, { for (i, slot) in self.items.iter().enumerate() { if slot.name.is_none() { let value = self.items.remove(i).value; let span = value.span; return T::cast(value).at(span).map(Some); } } Ok(None) } /// Consume and cast the first positional argument. /// /// Returns a `missing argument: {what}` error if no positional argument is /// left. pub fn expect(&mut self, what: &str) -> SourceResult where T: Cast>, { match self.eat()? { Some(v) => Ok(v), None => bail!(self.span, "missing argument: {}", what), } } /// Find and consume the first castable positional argument. pub fn find(&mut self) -> SourceResult> where T: Cast>, { for (i, slot) in self.items.iter().enumerate() { if slot.name.is_none() && T::is(&slot.value) { let value = self.items.remove(i).value; let span = value.span; return T::cast(value).at(span).map(Some); } } Ok(None) } /// Find and consume all castable positional arguments. pub fn all(&mut self) -> SourceResult> where T: Cast>, { let mut list = vec![]; while let Some(value) = self.find()? { list.push(value); } Ok(list) } /// Cast and remove the value for the given named argument, returning an /// error if the conversion fails. pub fn named(&mut self, name: &str) -> SourceResult> where T: Cast>, { // We don't quit once we have a match because when multiple matches // exist, we want to remove all of them and use the last one. let mut i = 0; let mut found = None; while i < self.items.len() { if self.items[i].name.as_deref() == Some(name) { let value = self.items.remove(i).value; let span = value.span; found = Some(T::cast(value).at(span)?); } else { i += 1; } } Ok(found) } /// Same as named, but with fallback to find. pub fn named_or_find(&mut self, name: &str) -> SourceResult> where T: Cast>, { match self.named(name)? { Some(value) => Ok(Some(value)), None => self.find(), } } /// Take out all arguments into a new instance. pub fn take(&mut self) -> Self { Self { span: self.span, items: std::mem::take(&mut self.items), } } /// Return an "unexpected argument" error if there is any remaining /// argument. pub fn finish(self) -> SourceResult<()> { if let Some(arg) = self.items.first() { bail!(arg.span, "unexpected argument"); } Ok(()) } /// Extract the positional arguments as an array. pub fn to_pos(&self) -> Array { self.items .iter() .filter(|item| item.name.is_none()) .map(|item| item.value.v.clone()) .collect() } /// Extract the named arguments as a dictionary. pub fn to_named(&self) -> Dict { self.items .iter() .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone()))) .collect() } } impl Debug for Args { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let pieces: Vec<_> = self.items.iter().map(|arg| eco_format!("{arg:?}")).collect(); f.write_str(&pretty_array_like(&pieces, false)) } } impl Debug for Arg { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if let Some(name) = &self.name { f.write_str(name)?; f.write_str(": ")?; } Debug::fmt(&self.value.v, f) } }