summaryrefslogtreecommitdiff
path: root/src/eval/args.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
commit1c40dc42e7bc7b799b77f06d25414aca59a044ba (patch)
treeea8bdedaebf59f5bc601346b0108236c7264a29d /src/eval/args.rs
parent8cad78481cd52680317032c3bb84cacda5666489 (diff)
Dynamic values, Types, Arrays, and Dictionaries 🚀
- Identifiers are now evaluated as variables instead of being plain values - Constants like `left` or `bold` are stored as dynamic values containing the respective rust types - We now distinguish between arrays and dictionaries to make things more intuitive (at the cost of a bit more complex parsing) - Spans were removed from collections (arrays, dictionaries), function arguments still have spans for the top-level values to enable good diagnostics
Diffstat (limited to 'src/eval/args.rs')
-rw-r--r--src/eval/args.rs246
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));
- }
-}