microcad_lang/eval/
argument_match.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Argument match trait
5
6use crate::{eval::*, value::*};
7
8/// Matching of `ParameterList` with `ArgumentValueList` into Tuple
9#[derive(Default)]
10pub struct ArgumentMatch<'a> {
11    arguments: Vec<(&'a Identifier, &'a ArgumentValue)>,
12    params: Vec<(&'a Identifier, &'a ParameterValue)>,
13    result: Tuple,
14}
15
16impl<'a> ArgumentMatch<'a> {
17    /// Match a `ParameterList` with an `ArgumentValueList` into a tuple.
18    ///
19    /// Returns `Ok(Tuple)`` if matches or Err() if not
20    pub fn find_match(
21        arguments: &'a ArgumentValueList,
22        params: &'a ParameterValueList,
23    ) -> EvalResult<Tuple> {
24        let am = Self::new(arguments, params)?;
25        am.check_exact_types(params)?;
26        Ok(am.result)
27    }
28
29    /// Match a `ParameterList` with an `ArgumentValueList` into an vector of tuples.
30    ///
31    /// Returns `Ok(Tuple)`` if matches or Err() if not
32    pub fn find_multi_match(
33        arguments: &'a ArgumentValueList,
34        params: &'a ParameterValueList,
35    ) -> EvalResult<Vec<Tuple>> {
36        Ok(Self::new(arguments, params)?.multiply(params))
37    }
38
39    /// Create new instance and start matching.
40    fn new(arguments: &'a ArgumentValueList, params: &'a ParameterValueList) -> EvalResult<Self> {
41        let mut am = Self {
42            arguments: arguments.iter().map(|(id, v)| (id, v)).collect(),
43            params: params.iter().collect(),
44            result: Tuple::new_named(std::collections::HashMap::new(), arguments.src_ref()),
45        };
46
47        fn match_exact(left: &Type, right: &Type) -> bool {
48            left == right
49        }
50
51        fn match_auto(left: &Type, right: &Type) -> bool {
52            left.is_matching(right)
53        }
54
55        am.match_ids();
56        am.match_types(true, match_exact);
57        am.match_types(false, match_auto);
58        am.match_defaults();
59        am.match_types(false, match_auto);
60        am.check_missing()?;
61
62        Ok(am)
63    }
64
65    /// Match arguments by id
66    fn match_ids(&mut self) {
67        if !self.arguments.is_empty() {
68            log::trace!("find id match for:\n{self:?}");
69            self.arguments.retain(|(id, arg)| {
70                let id = match (id.is_empty(), &arg.inline_id) {
71                    (true, Some(id)) => id,
72                    _ => id,
73                };
74
75                if !id.is_empty() {
76                    if let Some(n) = self.params.iter().position(|(i, _)| *i == id) {
77                        if let Some(ty) = &self.params[n].1.specified_type {
78                            if !arg.ty().is_matching(ty) {
79                                return true;
80                            }
81                        }
82                        let (id, _) = self.params.swap_remove(n);
83                        log::trace!(
84                            "{found} parameter by id: {id:?}",
85                            found = crate::mark!(MATCH)
86                        );
87                        self.result.insert((*id).clone(), arg.value.clone());
88                        return false;
89                    }
90                }
91                true
92            });
93        }
94    }
95
96    /// Match arguments by type
97    fn match_types(&mut self, mut exclude_defaults: bool, match_fn: impl Fn(&Type, &Type) -> bool) {
98        if !self.arguments.is_empty() {
99            if exclude_defaults {
100                log::trace!("find type matches for (defaults):\n{self:?}");
101            } else {
102                log::trace!("find type matches for:\n{self:?}");
103            }
104            self.arguments.retain(|(arg_id, arg)| {
105                // filter params by type
106                let same_type: Vec<_> = self
107                    .params
108                    .iter()
109                    .enumerate()
110                    .filter(|(..)| arg_id.is_empty())
111                    .filter_map(|(n, (id, param))| {
112                        if param.ty() == Type::Invalid
113                            || if let Some(ty) = &param.specified_type {
114                                match_fn(&arg.ty(), ty)
115                            } else {
116                                false
117                            }
118                        {
119                            Some((n, id, param))
120                        } else {
121                            None
122                        }
123                    })
124                    .collect();
125
126                // if type check is exact ignore exclusion
127                if same_type.len() == 1 {
128                    exclude_defaults = false;
129                }
130                // ignore params with defaults
131                let mut same_type = same_type
132                    .iter()
133                    .filter(|(.., param)| !exclude_defaults || param.default_value.is_none());
134
135                if let Some((n, id, _)) = same_type.next() {
136                    if same_type.next().is_none() {
137                        log::trace!(
138                            "{found} parameter by type: {id:?}",
139                            found = crate::mark!(MATCH)
140                        );
141                        self.result.insert((**id).clone(), arg.value.clone());
142                        self.params.swap_remove(*n);
143                        return false;
144                    } else {
145                        log::debug!("more than one parameter with that type")
146                    }
147                } else {
148                    log::debug!("no parameter with that type (or id mismatch)")
149                }
150                true
151            })
152        }
153    }
154
155    /// Fill arguments with defaults
156    fn match_defaults(&mut self) {
157        if !self.params.is_empty() {
158            log::trace!("find default match for:\n{self:?}");
159            // remove missing that can be found
160            self.params.retain(|(id, param)| {
161                // check for any default value
162                if let Some(def) = &param.default_value {
163                    // paranoia check if type is compatible
164                    if def.ty() == param.ty() {
165                        log::trace!(
166                            "{found} argument by default: {id:?} = {def}",
167                            found = crate::mark!(MATCH)
168                        );
169                        self.result.insert((*id).clone(), def.clone());
170                        return false;
171                    }
172                }
173                true
174            })
175        }
176    }
177
178    /// Return error if params are missing or arguments are to many
179    fn check_missing(&self) -> EvalResult<()> {
180        if !self.params.is_empty() {
181            let mut missing: IdentifierList =
182                self.params.iter().map(|(id, _)| (*id).clone()).collect();
183            missing.sort();
184            Err(EvalError::MissingArguments(missing))
185        } else if !self.arguments.is_empty() {
186            let mut too_many: IdentifierList =
187                self.arguments.iter().map(|(id, _)| (*id).clone()).collect();
188            too_many.sort();
189            Err(EvalError::TooManyArguments(too_many))
190        } else {
191            Ok(())
192        }
193    }
194
195    fn check_exact_types(&self, params: &ParameterValueList) -> EvalResult<()> {
196        let multipliers = Self::multipliers(&self.result, params);
197        if multipliers.is_empty() {
198            return Ok(());
199        }
200        Err(EvalError::MultiplicityNotAllowed(multipliers))
201    }
202
203    /// Process parameter multiplicity
204    ///
205    /// Return one or many tuples.
206    fn multiply(&self, params: &ParameterValueList) -> Vec<Tuple> {
207        let ids: IdentifierList = Self::multipliers(&self.result, params);
208        if !ids.is_empty() {
209            let mut result = Vec::new();
210            self.result.multiplicity(ids, |t| result.push(t));
211            result
212        } else {
213            vec![self.result.clone()]
214        }
215    }
216
217    /// Return the multipliers' ids in the arguments.
218    fn multipliers(args: &impl ValueAccess, params: &ParameterValueList) -> IdentifierList {
219        let mut result: IdentifierList = params
220            .iter()
221            .filter_map(|(id, param)| {
222                if let Some(a) = args.by_id(id) {
223                    if a.ty().is_array_of(&param.ty()) {
224                        return Some(id);
225                    }
226                }
227                None
228            })
229            .cloned()
230            .collect();
231        result.sort();
232        result
233    }
234}
235
236impl std::fmt::Debug for ArgumentMatch<'_> {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        write!(
239            f,
240            "   Arguments: {args}\n  Parameters: {params}",
241            args = self
242                .arguments
243                .iter()
244                .map(|(id, arg)| format!("{id:?} = {arg:?}"))
245                .collect::<Vec<_>>()
246                .join(", "),
247            params = self
248                .params
249                .iter()
250                .map(|(id, param)| format!("{id:?} = {param:?}"))
251                .collect::<Vec<_>>()
252                .join(", "),
253        )
254    }
255}
256
257#[test]
258fn argument_matching() {
259    use microcad_core::Length;
260    let params: ParameterValueList = [
261        crate::parameter!(a: Scalar),
262        crate::parameter!(b: Length),
263        crate::parameter!(c: Scalar),
264        crate::parameter!(d: Length = Length::mm(4.0)),
265    ]
266    .into_iter()
267    .collect();
268
269    let arguments: ArgumentValueList = [
270        crate::argument!(a: Scalar = 1.0),
271        crate::argument!(b: Length = Length::mm(2.0)),
272        crate::argument!(Scalar = 3.0),
273    ]
274    .into_iter()
275    .collect();
276
277    let result = ArgumentMatch::find_match(&arguments, &params).expect("expect valid arguments");
278
279    assert_eq!(result, crate::tuple!("(a=1.0, b=2.0mm, c=3.0, d=4.0mm)"));
280}
281
282#[test]
283fn argument_match_fail() {
284    use microcad_core::Length;
285
286    let params: ParameterValueList = [
287        crate::parameter!(x: Scalar),
288        crate::parameter!(y: Length),
289        crate::parameter!(z: Area),
290    ]
291    .into_iter()
292    .collect();
293    let arguments: ArgumentValueList = [
294        crate::argument!(x: Scalar = 1.0),
295        crate::argument!(Length = Length::mm(1.0)),
296    ]
297    .into_iter()
298    .collect();
299    assert!(ArgumentMatch::find_match(&arguments, &params).is_err());
300}