summaryrefslogtreecommitdiff
path: root/src/eval/methods.rs
blob: 017591b44dda429030ac5814c907d84141d44377 (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
//! Methods on values.

use super::{Args, StrExt, Value};
use crate::diag::{At, TypResult};
use crate::syntax::Span;
use crate::Context;

/// Call a method on a value.
pub fn call(
    ctx: &mut Context,
    value: Value,
    method: &str,
    mut args: Args,
    span: Span,
) -> TypResult<Value> {
    let name = value.type_name();
    let missing = || Err(missing_method(name, method)).at(span);

    let output = match value {
        Value::Str(string) => match method {
            "len" => Value::Int(string.len() as i64),
            "trim" => Value::Str(string.trim().into()),
            "split" => Value::Array(string.split(args.eat()?)),
            _ => missing()?,
        },

        Value::Array(array) => match method {
            "len" => Value::Int(array.len()),
            "slice" => {
                let start = args.expect("start")?;
                let mut end = args.eat()?;
                if end.is_none() {
                    end = args.named("count")?.map(|c: i64| start + c);
                }
                Value::Array(array.slice(start, end).at(span)?)
            }
            "map" => Value::Array(array.map(ctx, args.expect("function")?)?),
            "filter" => Value::Array(array.filter(ctx, args.expect("function")?)?),
            "flatten" => Value::Array(array.flatten()),
            "find" => array.find(args.expect("value")?).map_or(Value::None, Value::Int),
            "join" => {
                let sep = args.eat()?;
                let last = args.named("last")?;
                array.join(sep, last).at(span)?
            }
            "sorted" => Value::Array(array.sorted().at(span)?),
            _ => missing()?,
        },

        Value::Dict(dict) => match method {
            "len" => Value::Int(dict.len()),
            "keys" => Value::Array(dict.keys()),
            "values" => Value::Array(dict.values()),
            "pairs" => Value::Array(dict.map(ctx, args.expect("function")?)?),
            _ => missing()?,
        },

        Value::Func(func) => match method {
            "with" => Value::Func(func.clone().with(args.take())),
            _ => missing()?,
        },

        Value::Args(args) => match method {
            "positional" => Value::Array(args.to_positional()),
            "named" => Value::Dict(args.to_named()),
            _ => missing()?,
        },

        _ => missing()?,
    };

    args.finish()?;
    Ok(output)
}

/// Call a mutating method on a value.
pub fn call_mut(
    _: &mut Context,
    value: &mut Value,
    method: &str,
    mut args: Args,
    span: Span,
) -> TypResult<()> {
    let name = value.type_name();
    let missing = || Err(missing_method(name, method)).at(span);

    match value {
        Value::Array(array) => match method {
            "push" => array.push(args.expect("value")?),
            "pop" => array.pop().at(span)?,
            "insert" => {
                array.insert(args.expect("index")?, args.expect("value")?).at(span)?
            }
            "remove" => array.remove(args.expect("index")?).at(span)?,
            _ => missing()?,
        },

        Value::Dict(dict) => match method {
            "remove" => dict.remove(args.expect("key")?).at(span)?,
            _ => missing()?,
        },

        _ => missing()?,
    }

    args.finish()?;
    Ok(())
}

/// Whether a specific method is mutating.
pub fn is_mutating(method: &str) -> bool {
    matches!(method, "push" | "pop" | "insert" | "remove")
}

/// The missing method error message.
#[cold]
fn missing_method(type_name: &str, method: &str) -> String {
    format!("type {type_name} has no method `{method}`")
}