summaryrefslogtreecommitdiff
path: root/src/eval/args.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-03-12 14:24:24 +0100
committerLaurenz <laurmaedje@gmail.com>2022-03-12 14:24:24 +0100
commit2890a156d27c02a101137bf01dc2046597110bd1 (patch)
treec6bdeb48242c0fbd5b5e13120ca3c8f502d41b75 /src/eval/args.rs
parent5ac7eb3860ebd3247f6486c227e816894cb8fd91 (diff)
Remove classes and improve naming
Diffstat (limited to 'src/eval/args.rs')
-rw-r--r--src/eval/args.rs202
1 files changed, 202 insertions, 0 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
new file mode 100644
index 00000000..67da9865
--- /dev/null
+++ b/src/eval/args.rs
@@ -0,0 +1,202 @@
+use std::fmt::{self, Debug, Formatter, Write};
+
+use super::{Cast, Value};
+use crate::diag::{At, TypResult};
+use crate::syntax::{Span, Spanned};
+use crate::util::EcoString;
+
+/// 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: Vec<Arg>,
+}
+
+/// 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<EcoString>,
+ /// The value of the argument.
+ pub value: Spanned<Value>,
+}
+
+impl Args {
+ /// Create positional arguments from a span and values.
+ pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self {
+ Self {
+ span,
+ items: values
+ .into_iter()
+ .map(|value| Arg {
+ span,
+ name: None,
+ value: Spanned::new(value, span),
+ })
+ .collect(),
+ }
+ }
+
+ /// Consume and cast the first positional argument.
+ ///
+ /// Returns a `missing argument: {what}` error if no positional argument is
+ /// left.
+ pub fn expect<T>(&mut self, what: &str) -> TypResult<T>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ match self.eat()? {
+ Some(v) => Ok(v),
+ None => bail!(self.span, "missing argument: {}", what),
+ }
+ }
+
+ /// Consume and cast the first positional argument if there is one.
+ pub fn eat<T>(&mut self) -> TypResult<Option<T>>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ 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)
+ }
+
+ /// Find and consume the first castable positional argument.
+ pub fn find<T>(&mut self) -> TypResult<Option<T>>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ 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<T>(&mut self) -> TypResult<Vec<T>>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ 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<T>(&mut self, name: &str) -> TypResult<Option<T>>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ // 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<T>(&mut self, name: &str) -> TypResult<Option<T>>
+ where
+ T: Cast<Spanned<Value>>,
+ {
+ 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) -> TypResult<()> {
+ if let Some(arg) = self.items.first() {
+ bail!(arg.span, "unexpected argument");
+ }
+ Ok(())
+ }
+
+ /// Reinterpret these arguments as actually being an array index.
+ pub fn into_index(self) -> TypResult<i64> {
+ self.into_castable("index")
+ }
+
+ /// Reinterpret these arguments as actually being a dictionary key.
+ pub fn into_key(self) -> TypResult<EcoString> {
+ self.into_castable("key")
+ }
+
+ /// Reinterpret these arguments as actually being a single castable thing.
+ fn into_castable<T: Cast>(self, what: &str) -> TypResult<T> {
+ let mut iter = self.items.into_iter();
+ let value = match iter.next() {
+ Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?,
+ None => {
+ bail!(self.span, "missing {}", what);
+ }
+ Some(Arg { name: Some(_), span, .. }) => {
+ bail!(span, "named pair is not allowed here");
+ }
+ };
+
+ if let Some(arg) = iter.next() {
+ bail!(arg.span, "only one {} is allowed", what);
+ }
+
+ Ok(value)
+ }
+}
+
+impl Debug for Args {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_char('(')?;
+ for (i, arg) in self.items.iter().enumerate() {
+ arg.fmt(f)?;
+ if i + 1 < self.items.len() {
+ f.write_str(", ")?;
+ }
+ }
+ f.write_char(')')
+ }
+}
+
+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)
+ }
+}