Skip to main content

wasm_wave/
untyped.rs

1//! Untyped value
2
3use alloc::{borrow::Cow, vec::Vec};
4
5use crate::{
6    Parser, WasmValue,
7    ast::Node,
8    lex::Keyword,
9    parser::{ParserError, ParserErrorKind},
10};
11
12/// An UntypedValue is a parsed but not type-checked WAVE value.
13#[derive(Clone, Debug)]
14pub struct UntypedValue<'source> {
15    source: Cow<'source, str>,
16    node: Node,
17}
18
19impl<'source> UntypedValue<'source> {
20    pub(crate) fn new(source: impl Into<Cow<'source, str>>, node: Node) -> Self {
21        Self {
22            source: source.into(),
23            node,
24        }
25    }
26
27    /// Parses an untyped value from WAVE.
28    pub fn parse(source: &'source str) -> Result<Self, ParserError> {
29        let mut parser = Parser::new(source);
30        let val = parser.parse_raw_value()?;
31        parser.finish()?;
32        Ok(val)
33    }
34
35    /// Creates an owned value, copying the entire source string if necessary.
36    pub fn into_owned(self) -> UntypedValue<'static> {
37        UntypedValue::new(self.source.into_owned(), self.node)
38    }
39
40    /// Returns the source this value was parsed from.
41    pub fn source(&self) -> &str {
42        &self.source
43    }
44
45    /// Returns this value's root node.
46    pub fn node(&self) -> &Node {
47        &self.node
48    }
49
50    /// Converts this untyped value into the given typed value.
51    pub fn to_wasm_value<V: WasmValue>(&self, ty: &V::Type) -> Result<V, ParserError> {
52        self.node.to_wasm_value(ty, &self.source)
53    }
54}
55
56impl core::fmt::Display for UntypedValue<'_> {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        fmt_node(f, &self.node, &self.source)
59    }
60}
61
62/// An UntypedFuncCall is a parsed but not type-checked WAVE function call.
63///
64/// WAVE function calls have the form `<name>(<params...>)`.
65pub struct UntypedFuncCall<'source> {
66    source: Cow<'source, str>,
67    name: logos::Span,
68    params: Option<Node>,
69}
70
71impl<'source> UntypedFuncCall<'source> {
72    pub(crate) fn new(
73        source: impl Into<Cow<'source, str>>,
74        name: logos::Span,
75        params: Option<Node>,
76    ) -> Self {
77        Self {
78            source: source.into(),
79            name,
80            params,
81        }
82    }
83
84    /// Parses an untyped function call from WAVE.
85    pub fn parse(source: &'source str) -> Result<Self, ParserError> {
86        crate::parser::parse_raw_func_call(source)
87    }
88
89    /// Creates an owned function call, copying the entire source string if necessary.
90    pub fn into_owned(self) -> UntypedFuncCall<'static> {
91        UntypedFuncCall::new(
92            self.source.into_owned(),
93            self.name.clone(),
94            self.params.clone(),
95        )
96    }
97
98    /// Returns the source this function call was parsed from.
99    pub fn source(&self) -> &str {
100        &self.source
101    }
102
103    /// Returns the function parameters node.
104    ///
105    /// Returns `None` if the function call has no parameters.
106    pub fn params_node(&self) -> Option<&Node> {
107        self.params.as_ref()
108    }
109
110    /// Returns the function name.
111    pub fn name(&self) -> &str {
112        &self.source[self.name.clone()]
113    }
114
115    /// Returns the function name, as parsed by wit-parser.
116    #[cfg(feature = "wit")]
117    pub fn item_name(&self) -> Result<wit_parser::ItemName, anyhow::Error> {
118        use core::str::FromStr;
119        wit_parser::ItemName::from_str(self.name())
120    }
121
122    /// Converts the untyped parameters into the given types.
123    ///
124    /// Any number of trailing option-typed values may be omitted; those will
125    /// be returned as `none` values.
126    pub fn to_wasm_params<'types, V: WasmValue + 'static>(
127        &self,
128        types: impl IntoIterator<Item = &'types V::Type, IntoIter: ExactSizeIterator>,
129    ) -> Result<Vec<V>, ParserError> {
130        match &self.params {
131            Some(params) => params.to_wasm_params(types, self.source()),
132            None => {
133                let num_params = types.into_iter().len();
134                if num_params > 0 {
135                    return Err(ParserError::with_detail(
136                        ParserErrorKind::InvalidParams,
137                        self.name.clone(),
138                        alloc::format!("no params provided, but {num_params} required"),
139                    ));
140                }
141                Ok(Vec::new())
142            }
143        }
144    }
145}
146
147impl core::fmt::Display for UntypedFuncCall<'_> {
148    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149        f.write_str(&self.source[self.name.clone()])?;
150        match &self.params {
151            Some(params) => fmt_node(f, params, &self.source),
152            None => f.write_str("()"),
153        }
154    }
155}
156
157fn fmt_node(f: &mut impl core::fmt::Write, node: &Node, src: &str) -> core::fmt::Result {
158    use crate::ast::NodeType::*;
159    match node.ty() {
160        BoolTrue | BoolFalse | Number | Char | String | MultilineString | Label => {
161            f.write_str(node.slice(src))
162        }
163        Tuple => fmt_sequence(f, '(', ')', node.as_tuple()?, src),
164        List => fmt_sequence(f, '[', ']', node.as_list()?, src),
165        TupleSet => {
166            let entries = node.as_map()?;
167            if entries.len() == 0 {
168                return f.write_str("{:}");
169            }
170            f.write_char('{')?;
171            for (idx, (key, value)) in entries.enumerate() {
172                if idx != 0 {
173                    f.write_str(", ")?;
174                }
175                fmt_node(f, key, src)?;
176                f.write_str(": ")?;
177                fmt_node(f, value, src)?;
178            }
179            f.write_char('}')
180        }
181        EmptySet => f.write_str("{}"),
182        VariantWithPayload => {
183            let (label, payload) = node.as_variant(src)?;
184            if Keyword::decode(label).is_some() {
185                f.write_char('%')?;
186            }
187            fmt_variant(f, label, payload, src)
188        }
189        OptionSome => fmt_variant(f, "some", node.as_option()?, src),
190        OptionNone => fmt_variant(f, "none", None, src),
191        ResultOk => fmt_variant(f, "ok", node.as_result()?.unwrap(), src),
192        ResultErr => fmt_variant(f, "err", node.as_result()?.unwrap_err(), src),
193        Flags => {
194            f.write_char('{')?;
195            for (idx, flag) in node.as_flags(src)?.enumerate() {
196                if idx != 0 {
197                    f.write_str(", ")?;
198                }
199                f.write_str(flag)?;
200            }
201            f.write_char('}')
202        }
203    }
204}
205
206fn fmt_sequence<'a>(
207    f: &mut impl core::fmt::Write,
208    open: char,
209    close: char,
210    nodes: impl Iterator<Item = &'a Node>,
211    src: &str,
212) -> core::fmt::Result {
213    f.write_char(open)?;
214    for (idx, node) in nodes.enumerate() {
215        if idx != 0 {
216            f.write_str(", ")?;
217        }
218        fmt_node(f, node, src)?;
219    }
220    f.write_char(close)
221}
222
223fn fmt_variant(
224    f: &mut impl core::fmt::Write,
225    case: &str,
226    payload: Option<&Node>,
227    src: &str,
228) -> core::fmt::Result {
229    f.write_str(case)?;
230    if let Some(node) = payload {
231        f.write_char('(')?;
232        fmt_node(f, node, src)?;
233        f.write_char(')')?;
234    }
235    Ok(())
236}
237
238impl From<ParserError> for core::fmt::Error {
239    fn from(_: ParserError) -> Self {
240        Self
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use alloc::string::ToString;
247
248    use super::*;
249
250    #[test]
251    fn round_trips() {
252        for src in [
253            "true",
254            "18446744073709551616",
255            "-9223372036854775808",
256            "[-3.1415, 0, inf, nan, -inf]",
257            "['☃', '\\n']",
258            r#""☃☃☃""#,
259            "(1, false)",
260            "{:}",
261            "{code: red}",
262            "left(1)",
263            "[some(1), none]",
264            "[ok(1), err(2)]",
265            "[ok, err]",
266            "%inf(inf)",
267            "%some",
268            "%none(none)",
269            r#"{"a": 1, "b": {2: {true: '3'}}, "a": 4}"#,
270            "{-1: some(x), 0: none, 1: ok(y)}",
271        ] {
272            let val = UntypedValue::parse(src).unwrap();
273            let encoded = val.to_string();
274            assert_eq!(encoded, src);
275        }
276    }
277}