Skip to main content

xrust/transform/
callable.rs

1//! # Callables
2//! Sequence constructors that are invoked by stylesheet code, such as named templates and functions.
3//! The difference between them is that named templates have named parameters,
4//! whereas functions have positional parameters.
5
6// TODO: tunneling parameters
7
8use crate::item::Node;
9use crate::transform::context::StaticContext;
10use crate::transform::{NamespaceMap, Transform};
11use crate::{Context, Error, ErrorKind, Sequence};
12use qualname::QName;
13use std::collections::HashMap;
14use url::Url;
15
16#[derive(Clone, Debug)]
17pub struct Callable<N: Node> {
18    pub body: Transform<N>,
19    pub parameters: FormalParameters<N>,
20    // TODO: return type
21}
22
23impl<N: Node> Callable<N> {
24    pub fn new(body: Transform<N>, parameters: FormalParameters<N>) -> Self {
25        Callable { body, parameters }
26    }
27}
28
29// TODO: parameter type ("as" attribute)
30#[derive(Clone, Debug)]
31pub enum FormalParameters<N: Node> {
32    Named(Vec<(QName, Option<Transform<N>>)>), // parameter name, default value
33    Positional(Vec<QName>),
34}
35#[derive(Clone, Debug)]
36pub enum ActualParameters<N: Node> {
37    Named(Vec<(QName, Transform<N>)>), // parameter name, value
38    Positional(Vec<Transform<N>>),
39}
40
41/// Invoke a callable component
42pub fn invoke<
43    N: Node,
44    F: FnMut(&str) -> Result<(), Error>,
45    G: FnMut(&str) -> Result<N, Error>,
46    H: FnMut(&Url) -> Result<String, Error>,
47>(
48    ctxt: &Context<N>,
49    stctxt: &mut StaticContext<N, F, G, H>,
50    qn: &QName,
51    a: &ActualParameters<N>,
52    _ns: &NamespaceMap,
53) -> Result<Sequence<N>, Error> {
54    match ctxt.callables.get(qn) {
55        Some(t) => {
56            match &t.parameters {
57                FormalParameters::Named(v) => {
58                    let mut newctxt = ctxt.clone();
59                    // Put the actual parameters in a HashMap for easy access
60                    let mut actuals = HashMap::new();
61                    if let ActualParameters::Named(av) = a {
62                        av.iter().try_for_each(|(a_name, a_value)| {
63                            actuals.insert(a_name, ctxt.dispatch(stctxt, a_value)?);
64                            Ok(())
65                        })?
66                    } else {
67                        return Err(Error::new(ErrorKind::TypeError, "argument mismatch"));
68                    }
69                    // Match each actual parameter to a formal parameter by name
70                    v.iter().try_for_each(|(name, dflt)| {
71                        match actuals.get(name) {
72                            Some(val) => {
73                                newctxt.var_push(name.to_string(), val.clone());
74                                Ok(())
75                            }
76                            None => {
77                                // Use default value
78                                if let Some(d) = dflt {
79                                    newctxt.var_push(name.to_string(), ctxt.dispatch(stctxt, d)?)
80                                } else {
81                                    newctxt.var_push(name.to_string(), vec![])
82                                }
83                                Ok(())
84                            }
85                        }
86                    })?;
87
88                    // Check that the maximum depth limit will not be exceeded,
89                    // if there is one set
90
91                    if let Some(md) = ctxt.max_depth {
92                        if md == ctxt.depth {
93                            return Err(Error::new(
94                                crate::ErrorKind::LimitExceeded,
95                                format!("exceeded evaluation depth ({})", ctxt.depth),
96                            ));
97                        }
98                    }
99                    newctxt.depth = ctxt.depth + 1;
100
101                    newctxt.dispatch(stctxt, &t.body)
102                }
103                FormalParameters::Positional(v) => {
104                    if let ActualParameters::Positional(av) = a {
105                        // Make sure number of parameters are equal, then set up variables by position
106                        if v.len() == av.len() {
107                            let mut newctxt = ctxt.clone();
108                            v.iter().zip(av.iter()).try_for_each(|(qn, t)| {
109                                newctxt.var_push(qn.to_string(), ctxt.dispatch(stctxt, t)?);
110                                Ok(())
111                            })?;
112
113                            // Check that the maximum depth limit will not be exceeded,
114                            // if there is one set
115
116                            if let Some(md) = ctxt.max_depth {
117                                if md == ctxt.depth {
118                                    return Err(Error::new(
119                                        crate::ErrorKind::LimitExceeded,
120                                        format!("exceeded evaluation depth ({})", ctxt.depth),
121                                    ));
122                                }
123                            }
124                            newctxt.depth = ctxt.depth + 1;
125
126                            newctxt.dispatch(stctxt, &t.body)
127                        } else {
128                            Err(Error::new(ErrorKind::TypeError, "argument mismatch"))
129                        }
130                    } else {
131                        Err(Error::new(ErrorKind::TypeError, "argument mismatch"))
132                    }
133                }
134            }
135        }
136        None => Err(Error::new(
137            ErrorKind::Unknown,
138            format!("unknown callable \"{}\"", qn),
139        )),
140    }
141}