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        Record => {
166            let fields = node.as_record(src)?;
167            if fields.len() == 0 {
168                return f.write_str("{:}");
169            }
170            f.write_char('{')?;
171            for (idx, (name, value)) in node.as_record(src)?.enumerate() {
172                if idx != 0 {
173                    f.write_str(", ")?;
174                }
175                write!(f, "{name}: ")?;
176                fmt_node(f, value, src)?;
177            }
178            f.write_char('}')
179        }
180        VariantWithPayload => {
181            let (label, payload) = node.as_variant(src)?;
182            if Keyword::decode(label).is_some() {
183                f.write_char('%')?;
184            }
185            fmt_variant(f, label, payload, src)
186        }
187        OptionSome => fmt_variant(f, "some", node.as_option()?, src),
188        OptionNone => fmt_variant(f, "none", None, src),
189        ResultOk => fmt_variant(f, "ok", node.as_result()?.unwrap(), src),
190        ResultErr => fmt_variant(f, "err", node.as_result()?.unwrap_err(), src),
191        Flags => {
192            f.write_char('{')?;
193            for (idx, flag) in node.as_flags(src)?.enumerate() {
194                if idx != 0 {
195                    f.write_str(", ")?;
196                }
197                f.write_str(flag)?;
198            }
199            f.write_char('}')
200        }
201    }
202}
203
204fn fmt_sequence<'a>(
205    f: &mut impl core::fmt::Write,
206    open: char,
207    close: char,
208    nodes: impl Iterator<Item = &'a Node>,
209    src: &str,
210) -> core::fmt::Result {
211    f.write_char(open)?;
212    for (idx, node) in nodes.enumerate() {
213        if idx != 0 {
214            f.write_str(", ")?;
215        }
216        fmt_node(f, node, src)?;
217    }
218    f.write_char(close)
219}
220
221fn fmt_variant(
222    f: &mut impl core::fmt::Write,
223    case: &str,
224    payload: Option<&Node>,
225    src: &str,
226) -> core::fmt::Result {
227    f.write_str(case)?;
228    if let Some(node) = payload {
229        f.write_char('(')?;
230        fmt_node(f, node, src)?;
231        f.write_char(')')?;
232    }
233    Ok(())
234}
235
236impl From<ParserError> for core::fmt::Error {
237    fn from(_: ParserError) -> Self {
238        Self
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use alloc::string::ToString;
245
246    use super::*;
247
248    #[test]
249    fn round_trips() {
250        for src in [
251            "true",
252            "18446744073709551616",
253            "-9223372036854775808",
254            "[-3.1415, 0, inf, nan, -inf]",
255            "['☃', '\\n']",
256            r#""☃☃☃""#,
257            "(1, false)",
258            "{:}",
259            "{code: red}",
260            "left(1)",
261            "[some(1), none]",
262            "[ok(1), err(2)]",
263            "[ok, err]",
264            "%inf(inf)",
265            "%some",
266            "%none(none)",
267        ] {
268            let val = UntypedValue::parse(src).unwrap();
269            let encoded = val.to_string();
270            assert_eq!(encoded, src);
271        }
272    }
273}