wai_bindgen_gen_wasmtime_py/
source.rs

1use heck::*;
2use wai_bindgen_gen_core::wai_parser::*;
3
4use crate::dependencies::Dependencies;
5
6/// A [Source] represents some unit of Python code
7/// and keeps track of its indent.
8#[derive(Default)]
9pub struct Source {
10    s: String,
11    indent: usize,
12}
13
14impl Source {
15    /// Appends a string slice to this [Source].
16    ///
17    /// Strings without newlines, they are simply appended.
18    /// Strings with newlines are appended and also new lines
19    /// are indented based on the current indent level.
20    pub fn push_str(&mut self, src: &str) {
21        let lines = src.lines().collect::<Vec<_>>();
22        let mut trim = None;
23        for (i, line) in lines.iter().enumerate() {
24            self.s.push_str(if lines.len() == 1 {
25                line
26            } else {
27                let trim = match trim {
28                    Some(n) => n,
29                    None => {
30                        let val = line.len() - line.trim_start().len();
31                        if !line.is_empty() {
32                            trim = Some(val);
33                        }
34                        val
35                    }
36                };
37                line.get(trim..).unwrap_or("")
38            });
39            if i != lines.len() - 1 || src.ends_with('\n') {
40                self.newline();
41            }
42        }
43    }
44
45    /// Prints the documentation as comments
46    /// e.g.
47    /// > \# Line one of docs node
48    /// >
49    /// > \# Line two of docs node
50    pub fn comment(&mut self, docs: &Docs) {
51        let docs = match &docs.contents {
52            Some(docs) => docs,
53            None => return,
54        };
55        for line in docs.lines() {
56            self.push_str(&format!("# {}\n", line));
57        }
58    }
59
60    /// Prints the documentation as comments
61    /// e.g.
62    /// > """
63    /// >
64    /// > Line one of docs node
65    /// >
66    /// > Line two of docs node
67    /// >
68    /// > """
69    pub fn docstring(&mut self, docs: &Docs) {
70        let docs = match &docs.contents {
71            Some(docs) => docs,
72            None => return,
73        };
74        let triple_quote = r#"""""#;
75        self.push_str(triple_quote);
76        self.newline();
77        for line in docs.lines() {
78            self.push_str(line);
79            self.newline();
80        }
81        self.push_str(triple_quote);
82        self.newline();
83    }
84
85    /// Indent the source one level.
86    pub fn indent(&mut self) {
87        self.indent += 4;
88        self.s.push_str("    ");
89    }
90
91    /// Unindent, or in Python terms "dedent",
92    /// the source one level.
93    pub fn dedent(&mut self) {
94        self.indent -= 4;
95        assert!(self.s.ends_with("    "));
96        self.s.pop();
97        self.s.pop();
98        self.s.pop();
99        self.s.pop();
100    }
101
102    /// Go to the next line and apply any indent.
103    pub fn newline(&mut self) {
104        self.s.push('\n');
105        for _ in 0..self.indent {
106            self.s.push(' ');
107        }
108    }
109}
110
111impl std::ops::Deref for Source {
112    type Target = str;
113    fn deref(&self) -> &str {
114        &self.s
115    }
116}
117
118impl From<Source> for String {
119    fn from(s: Source) -> String {
120        s.s
121    }
122}
123
124/// [SourceBuilder] combines together a [Source]
125/// with other contextual information and state.
126///
127/// This allows you to generate code for the Source using
128/// high-level tools that take care of updating dependencies
129/// and retrieving interface details.
130///
131/// You can create a [SourceBuilder] easily using a [Source]
132/// ```
133/// # use wai_bindgen_gen_wasmtime_py::dependencies::Dependencies;
134/// # use wai_bindgen_gen_core::wai_parser::{Interface, Type};
135/// # use wai_bindgen_gen_wasmtime_py::source::Source;
136/// # let mut deps = Dependencies::default();
137/// # let mut interface = Interface::default();
138/// # let iface = &interface;
139/// let mut source = Source::default();
140/// let mut builder = source.builder(&mut deps, iface);
141/// builder.print_ty(&Type::Bool, false);
142/// ```
143pub struct SourceBuilder<'s, 'd, 'i> {
144    source: &'s mut Source,
145    pub deps: &'d mut Dependencies,
146    iface: &'i Interface,
147}
148
149impl<'s, 'd, 'i> Source {
150    /// Create a [SourceBuilder] for the current source.
151    pub fn builder(
152        &'s mut self,
153        deps: &'d mut Dependencies,
154        iface: &'i Interface,
155    ) -> SourceBuilder<'s, 'd, 'i> {
156        SourceBuilder {
157            source: self,
158            deps,
159            iface,
160        }
161    }
162}
163
164impl<'s, 'd, 'i> SourceBuilder<'s, 'd, 'i> {
165    /// See [Dependencies::pyimport].
166    pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
167        self.deps.pyimport(module, name)
168    }
169
170    /// Appends a type's Python representation to this `Source`.
171    /// Records any required intrinsics and imports in the `deps`.
172    /// Uses Python forward reference syntax (e.g. 'Foo')
173    /// on the root type only if `forward_ref` is true.
174    pub fn print_ty(&mut self, ty: &Type, forward_ref: bool) {
175        match ty {
176            Type::Unit => self.push_str("None"),
177            Type::Bool => self.push_str("bool"),
178            Type::U8
179            | Type::S8
180            | Type::U16
181            | Type::S16
182            | Type::U32
183            | Type::S32
184            | Type::U64
185            | Type::S64 => self.push_str("int"),
186            Type::Float32 | Type::Float64 => self.push_str("float"),
187            Type::Char => self.push_str("str"),
188            Type::String => self.push_str("str"),
189            Type::Handle(id) => {
190                if forward_ref {
191                    self.push_str("'");
192                }
193                let handle_name = &self.iface.resources[*id].name.to_camel_case();
194                self.source.push_str(handle_name);
195                if forward_ref {
196                    self.push_str("'");
197                }
198            }
199            Type::Id(id) => {
200                let ty = &self.iface.types[*id];
201                if let Some(name) = &ty.name {
202                    self.push_str(&name.to_camel_case());
203                    return;
204                }
205                match &ty.kind {
206                    TypeDefKind::Type(t) => self.print_ty(t, forward_ref),
207                    TypeDefKind::Tuple(t) => self.print_tuple(t),
208                    TypeDefKind::Record(_)
209                    | TypeDefKind::Flags(_)
210                    | TypeDefKind::Enum(_)
211                    | TypeDefKind::Variant(_)
212                    | TypeDefKind::Union(_) => {
213                        unreachable!()
214                    }
215                    TypeDefKind::Option(t) => {
216                        self.deps.pyimport("typing", "Optional");
217                        self.push_str("Optional[");
218                        self.print_ty(t, true);
219                        self.push_str("]");
220                    }
221                    TypeDefKind::Expected(e) => {
222                        self.deps.needs_expected = true;
223                        self.push_str("Expected[");
224                        self.print_ty(&e.ok, true);
225                        self.push_str(", ");
226                        self.print_ty(&e.err, true);
227                        self.push_str("]");
228                    }
229                    TypeDefKind::List(t) => self.print_list(t),
230                    TypeDefKind::Future(t) => {
231                        self.push_str("Future[");
232                        self.print_ty(t, true);
233                        self.push_str("]");
234                    }
235                    TypeDefKind::Stream(s) => {
236                        self.push_str("Stream[");
237                        self.print_ty(&s.element, true);
238                        self.push_str(", ");
239                        self.print_ty(&s.end, true);
240                        self.push_str("]");
241                    }
242                }
243            }
244        }
245    }
246
247    /// Appends a tuple type's Python representation to this `Source`.
248    /// Records any required intrinsics and imports in the `deps`.
249    /// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
250    pub fn print_tuple(&mut self, tuple: &Tuple) {
251        if tuple.types.is_empty() {
252            return self.push_str("None");
253        }
254        self.deps.pyimport("typing", "Tuple");
255        self.push_str("Tuple[");
256        for (i, t) in tuple.types.iter().enumerate() {
257            if i > 0 {
258                self.push_str(", ");
259            }
260            self.print_ty(t, true);
261        }
262        self.push_str("]");
263    }
264
265    /// Appends a Python type representing a sequence of the `element` type to this `Source`.
266    /// If the element type is `Type::U8`, the result type is `bytes` otherwise it is a `List[T]`
267    /// Records any required intrinsics and imports in the `deps`.
268    /// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
269    pub fn print_list(&mut self, element: &Type) {
270        match element {
271            Type::U8 => self.push_str("bytes"),
272            t => {
273                self.deps.pyimport("typing", "List");
274                self.push_str("List[");
275                self.print_ty(t, true);
276                self.push_str("]");
277            }
278        }
279    }
280
281    /// Print variable declaration.
282    /// Brings name into scope and binds type to it.
283    pub fn print_var_declaration<'a>(&mut self, name: &'a str, ty: &Type) {
284        self.push_str(name);
285        self.push_str(": ");
286        self.print_ty(ty, true);
287        self.push_str("\n");
288    }
289
290    pub fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec<String> {
291        if !in_import {
292            if let FunctionKind::Static { .. } = func.kind {
293                self.push_str("@classmethod\n");
294            }
295        }
296        self.source.push_str("def ");
297        match &func.kind {
298            FunctionKind::Method { .. } => self.source.push_str(&func.item_name().to_snake_case()),
299            FunctionKind::Static { .. } if !in_import => {
300                self.source.push_str(&func.item_name().to_snake_case())
301            }
302            _ => self.source.push_str(&func.name.to_snake_case()),
303        }
304        if in_import {
305            self.source.push_str("(self");
306        } else if let FunctionKind::Static { .. } = func.kind {
307            self.source.push_str("(cls, caller: wasmtime.Store, obj: '");
308            self.source.push_str(&self.iface.name.to_camel_case());
309            self.source.push_str("'");
310        } else {
311            self.source.push_str("(self, caller: wasmtime.Store");
312        }
313        let mut params = Vec::new();
314        for (i, (param, ty)) in func.params.iter().enumerate() {
315            if i == 0 {
316                if let FunctionKind::Method { .. } = func.kind {
317                    params.push("self".to_string());
318                    continue;
319                }
320            }
321            self.source.push_str(", ");
322            self.source.push_str(&param.to_snake_case());
323            params.push(param.to_snake_case());
324            self.source.push_str(": ");
325            self.print_ty(ty, true);
326        }
327        self.source.push_str(") -> ");
328        self.print_ty(&func.result, true);
329        params
330    }
331
332    /// Print a wrapped union definition.
333    /// e.g.
334    /// ```py
335    /// @dataclass
336    /// class Foo0:
337    ///     value: int
338    ///
339    /// @dataclass
340    /// class Foo1:
341    ///     value: int
342    ///
343    /// Foo = Union[Foo0, Foo1]
344    /// ```
345    pub fn print_union_wrapped(&mut self, name: &str, union: &Union, docs: &Docs) {
346        self.deps.pyimport("dataclasses", "dataclass");
347        let mut cases = Vec::new();
348        let name = name.to_camel_case();
349        for (i, case) in union.cases.iter().enumerate() {
350            self.source.push_str("@dataclass\n");
351            let name = format!("{name}{i}");
352            self.source.push_str(&format!("class {name}:\n"));
353            self.source.indent();
354            self.source.docstring(&case.docs);
355            self.source.push_str("value: ");
356            self.print_ty(&case.ty, true);
357            self.source.newline();
358            self.source.dedent();
359            self.source.newline();
360            cases.push(name);
361        }
362
363        self.deps.pyimport("typing", "Union");
364        self.source.comment(docs);
365        self.source
366            .push_str(&format!("{name} = Union[{}]\n", cases.join(", ")));
367        self.source.newline();
368    }
369
370    pub fn print_union_raw(&mut self, name: &str, union: &Union, docs: &Docs) {
371        self.deps.pyimport("typing", "Union");
372        self.source.comment(docs);
373        for case in union.cases.iter() {
374            self.source.comment(&case.docs);
375        }
376        self.source.push_str(&name.to_camel_case());
377        self.source.push_str(" = Union[");
378        let mut first = true;
379        for case in union.cases.iter() {
380            if !first {
381                self.source.push_str(",");
382            }
383            self.print_ty(&case.ty, true);
384            first = false;
385        }
386        self.source.push_str("]\n\n");
387    }
388}
389
390impl<'s, 'd, 'i> std::ops::Deref for SourceBuilder<'s, 'd, 'i> {
391    type Target = Source;
392    fn deref(&self) -> &Source {
393        self.source
394    }
395}
396
397impl<'s, 'd, 'i> std::ops::DerefMut for SourceBuilder<'s, 'd, 'i> {
398    fn deref_mut(&mut self) -> &mut Source {
399        self.source
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use std::collections::{BTreeMap, BTreeSet};
406
407    use super::*;
408
409    #[test]
410    fn simple_append() {
411        let mut s = Source::default();
412        s.push_str("x");
413        assert_eq!(s.s, "x");
414        s.push_str("y");
415        assert_eq!(s.s, "xy");
416        s.push_str("z ");
417        assert_eq!(s.s, "xyz ");
418        s.push_str(" a ");
419        assert_eq!(s.s, "xyz  a ");
420        s.push_str("\na");
421        assert_eq!(s.s, "xyz  a \na");
422    }
423
424    #[test]
425    fn trim_ws() {
426        let mut s = Source::default();
427        s.push_str("def foo():\n  return 1\n");
428        assert_eq!(s.s, "def foo():\n  return 1\n");
429    }
430
431    #[test]
432    fn print_ty_forward_ref() {
433        let mut deps = Dependencies::default();
434        let mut iface = Interface::default();
435        // Set up a Resource type to refer to
436        let resource_id = iface.resources.alloc(Resource {
437            docs: Docs::default(),
438            name: "foo".into(),
439            supertype: None,
440            foreign_module: None,
441        });
442        iface.resource_lookup.insert("foo".into(), resource_id);
443        let handle_ty = Type::Handle(resource_id);
444        // ForwardRef usage can be controlled by an argument to print_ty
445        let mut s1 = Source::default();
446        let mut builder = s1.builder(&mut deps, &iface);
447        builder.print_ty(&handle_ty, true);
448        drop(builder);
449        assert_eq!(s1.s, "'Foo'");
450
451        let mut s2 = Source::default();
452        let mut builder = s2.builder(&mut deps, &iface);
453        builder.print_ty(&handle_ty, false);
454        drop(builder);
455        assert_eq!(s2.s, "Foo");
456
457        // ForwardRef is used for any types within other types
458        // Even if the outer type is itself not allowed to be one
459        let option_id = iface.types.alloc(TypeDef {
460            docs: Docs::default(),
461            kind: TypeDefKind::Option(handle_ty),
462            name: None,
463            foreign_module: None,
464        });
465        let option_ty = Type::Id(option_id);
466        let mut s3 = Source::default();
467        let mut builder = s3.builder(&mut deps, &iface);
468        builder.print_ty(&option_ty, false);
469        drop(builder);
470        assert_eq!(s3.s, "Optional['Foo']");
471    }
472
473    #[test]
474    fn print_list_bytes() {
475        // If the element type is u8, it is interpreted as `bytes`
476        let mut deps = Dependencies::default();
477        let iface = Interface::default();
478        let mut source = Source::default();
479        let mut builder = source.builder(&mut deps, &iface);
480        builder.print_list(&Type::U8);
481        drop(builder);
482        assert_eq!(source.s, "bytes");
483        assert_eq!(deps.pyimports, BTreeMap::default());
484    }
485
486    #[test]
487    fn print_list_non_bytes() {
488        // If the element type is u8, it is interpreted as `bytes`
489        let mut deps = Dependencies::default();
490        let iface = Interface::default();
491        let mut source = Source::default();
492        let mut builder = source.builder(&mut deps, &iface);
493        builder.print_list(&Type::Float32);
494        drop(builder);
495        assert_eq!(source.s, "List[float]");
496        assert_eq!(
497            deps.pyimports,
498            BTreeMap::from([("typing".into(), Some(BTreeSet::from(["List".into()])))])
499        );
500    }
501}