summaryrefslogtreecommitdiff
path: root/src/eval/args.rs
blob: ea248ec423c6f4bb0a2c4d0c9f13ac81364aeb82 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use super::*;

/// Evaluated arguments to a function.
#[derive(Debug)]
pub struct Args {
    span: Span,
    pos: SpanVec<Value>,
    named: Vec<(Spanned<String>, Spanned<Value>)>,
}

impl Eval for Spanned<&Arguments> {
    type Output = Args;

    fn eval(self, ctx: &mut EvalContext) -> Self::Output {
        let mut pos = vec![];
        let mut named = vec![];

        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),
                    ));
                }
            }
        }

        Args { span: self.span, pos, named }
    }
}

impl Args {
    /// Find the first convertible positional argument.
    pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
    where
        T: Cast<Spanned<Value>>,
    {
        self.pos.iter_mut().find_map(move |slot| try_cast(ctx, slot))
    }

    /// 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: Cast<Spanned<Value>>,
    {
        let found = self.find(ctx);
        if found.is_none() {
            ctx.diag(error!(self.span, "missing argument: {}", what));
        }
        found
    }

    /// 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: Cast<Spanned<Value>>,
    {
        self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot))
    }

    /// 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: Cast<Spanned<Value>>,
    {
        let index = self.named.iter().position(|(k, _)| k.v.as_str() == name)?;
        let value = self.named.remove(index).1;
        cast(ctx, value)
    }

    /// 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"));
            }
        }
    }
}

/// 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)
        }
        CastResult::Err(value) => {
            ctx.diag(error!(
                span,
                "expected {}, found {}",
                T::TYPE_NAME,
                value.v.type_name()
            ));
            None
        }
    }
}

/// 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
        }
    }
}