rsass/sass/
call_args.rs

1use super::{Call, CallError, Name, Value};
2use crate::ordermap::OrderMap;
3use crate::value::ListSeparator;
4use crate::{css, Error, Invalid, ScopeRef};
5use std::default::Default;
6
7/// the actual arguments of a function or mixin call.
8///
9/// Each argument has a Value.  Arguments may be named.
10/// If the optional name is None, the argument is positional.
11#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd)]
12pub struct CallArgs {
13    positional: Vec<Value>,
14    // Ordered for formattig.
15    named: OrderMap<Name, Value>,
16    trailing_comma: bool,
17}
18
19impl CallArgs {
20    /// Create a new callargs from a vec of name-value pairs.
21    ///
22    /// The names are `None` for positional arguments.
23    pub fn new(
24        v: Vec<(Option<Name>, Value)>,
25        trailing_comma: bool,
26    ) -> Result<Self, Invalid> {
27        let mut positional = Vec::new();
28        let mut named = OrderMap::new();
29        for (name, value) in v {
30            if let Some(name) = name {
31                if let Some(_old) = named.insert(name, value) {
32                    return Err(Invalid::DuplicateArgument);
33                }
34            } else if named.is_empty() || is_splat(&value).is_some() {
35                positional.push(value);
36            } else {
37                return Err(Invalid::PositionalArgAfterNamed);
38            }
39        }
40        Ok(Self {
41            positional,
42            named,
43            trailing_comma,
44        })
45    }
46
47    /// Create a new `CallArgs` from one single unnamed argument.
48    pub fn new_single(value: Value) -> Self {
49        Self {
50            positional: vec![value],
51            named: Default::default(),
52            trailing_comma: false,
53        }
54    }
55
56    /// Evaluate these sass `CallArgs` to css `CallArgs`.
57    pub fn evaluate(&self, scope: ScopeRef) -> Result<Call, CallError> {
58        let named = self.named.iter().try_fold(
59            OrderMap::new(),
60            |mut acc, (name, arg)| {
61                let arg = arg.do_evaluate(scope.clone(), true)?;
62                acc.insert(name.clone(), arg);
63                Ok::<_, Error>(acc)
64            },
65        )?;
66        let mut result = css::CallArgs {
67            positional: Vec::new(),
68            named,
69            trailing_comma: self.trailing_comma,
70        };
71        for arg in &self.positional {
72            match is_splat(arg) {
73                Some([one]) => match one.do_evaluate(scope.clone(), true)? {
74                    css::Value::ArgList(args) => {
75                        result.positional.extend(args.positional);
76                        for (name, value) in args.named {
77                            if let Some(_existing) =
78                                result.named.insert(name, value)
79                            {
80                                return Err(Invalid::DuplicateArgument.into());
81                            }
82                        }
83                    }
84                    css::Value::Map(map) => {
85                        result
86                            .add_from_value_map(map)
87                            .map_err(CallError::msg)?;
88                    }
89                    css::Value::List(items, ..) => {
90                        for item in items {
91                            result.positional.push(item);
92                        }
93                    }
94                    css::Value::Null => (),
95                    item => {
96                        result.positional.push(item);
97                        result.trailing_comma = false;
98                    }
99                },
100                Some(splat) => {
101                    for arg in splat {
102                        result
103                            .positional
104                            .push(arg.do_evaluate(scope.clone(), true)?);
105                    }
106                }
107                None => {
108                    result
109                        .positional
110                        .push(arg.do_evaluate(scope.clone(), true)?);
111                }
112            }
113        }
114        Ok(Call {
115            args: result,
116            scope,
117        })
118    }
119
120    /// Evaluate a single argument
121    ///
122    /// Only used by the `if` function, which is the only sass
123    /// function that evaluates its arguments lazily.
124    pub fn evaluate_single(
125        &self,
126        scope: ScopeRef,
127        name: Name,
128        num: usize,
129    ) -> Result<css::Value, Error> {
130        // TODO: Error if both name and posinal exists?
131        if let Some(v) = self.named.get(&name) {
132            return v.do_evaluate(scope, true);
133        }
134        let mut i = 0;
135        for a in &self.positional {
136            match is_splat(a) {
137                Some([one]) => match one.do_evaluate(scope.clone(), true)? {
138                    css::Value::ArgList(args) => {
139                        if let Some(v) = args
140                            .named
141                            .get(&name)
142                            .or_else(|| args.positional.get(num - i))
143                        {
144                            return Ok(v.clone());
145                        }
146                        i += if args.named.is_empty() {
147                            args.len()
148                        } else {
149                            num + 1
150                        };
151                    }
152                    css::Value::Map(map) => {
153                        if let Some(v) = map.get(&name.to_string().into()) {
154                            return Ok(v.clone());
155                        }
156                        i += num + 1;
157                    }
158                    css::Value::List(items, ..) => {
159                        if let Some(v) = items.get(num - i) {
160                            return Ok(v.clone());
161                        }
162                        i += items.len();
163                    }
164                    css::Value::Null => (),
165                    v => {
166                        if i == num {
167                            return Ok(v);
168                        }
169                        i += 1;
170                    }
171                },
172                Some(splat) => {
173                    if let Some(v) = splat.get(num - i) {
174                        return v.do_evaluate(scope, true);
175                    }
176                    i += splat.len();
177                }
178                None => {
179                    if i == num {
180                        return a.do_evaluate(scope, true);
181                    }
182                    i += 1;
183                }
184            }
185        }
186        Ok(css::Value::Null)
187    }
188}
189
190fn is_splat(arg: &Value) -> Option<&[Value]> {
191    if let Value::List(list, sep, false) = arg {
192        if let Some((Value::Literal(v, ..), splat)) = list.split_last() {
193            if v.is_unquoted()
194                && v.single_raw() == Some("...")
195                && sep.unwrap_or_default() == ListSeparator::Space
196            {
197                return Some(splat);
198            }
199        }
200    }
201    None
202}