wast_encoder/encoder/
mod.rs

1use std::{
2    fmt::{Debug, Formatter, Write},
3    ops::AddAssign,
4    sync::Arc,
5};
6
7use nyar_error::NyarError;
8
9use crate::{
10    dag::DependenciesTrace,
11    helpers::{ComponentDefine, LowerFunction},
12    wasi_types::functions::{WasiFunctionBody, WasiNativeFunction},
13    DependentGraph, Identifier, WasiFunction, WasiInstance, WasiType,
14};
15
16mod for_instance;
17
18pub struct CanonicalWasi {
19    pub name: Arc<str>,
20    pub graph: DependentGraph,
21    pub imports: Vec<CanonicalImport>,
22    pub type_signatures: bool,
23    pub indent_text: &'static str,
24}
25
26pub(crate) struct WastEncoder<'a, W> {
27    pub source: &'a CanonicalWasi,
28    pub writer: W,
29    pub indent: usize,
30    pub stack: Vec<WasiType>,
31}
32
33impl CanonicalWasi {
34    pub fn get_function(&self, symbol: &Identifier) -> Option<&WasiNativeFunction> {
35        match self.graph.types.get(symbol) {
36            Some(WasiType::Function(s)) => Some(s),
37            _ => None,
38        }
39    }
40
41    pub fn draw_mermaid(&self) -> String {
42        let mut out = String::new();
43        out.push_str("flowchart LR\n");
44        for _ in &self.imports {}
45
46        for import in &self.imports {
47            match import {
48                CanonicalImport::Instance(v) => {
49                    out.push_str(&format!("    subgraph \"{}\"\n", v.module));
50                    for wasi in v.resources.values() {
51                        out.push_str(&format!("        {:#}[\"{}\"]:::resource\n", wasi.symbol, wasi.wasi_name));
52                    }
53                    for wasi in v.functions.values() {
54                        out.push_str(&format!("        {:#}[\"{}\"]:::function\n", wasi.symbol, wasi.symbol.wasi_id()));
55                    }
56                    for wasi in v.functions.values() {
57                        let mut types = vec![];
58                        wasi.collect_wasi_types(&self.graph, &mut types);
59                        for ty in types {
60                            match ty.language_id() {
61                                None => {}
62                                Some(s) => {
63                                    out.push_str(&format!("        {:#} -.-> {:#}\n", s, wasi.symbol));
64                                }
65                            }
66                        }
67                    }
68                    out.push_str("    end\n");
69                }
70                CanonicalImport::Type(WasiType::Variant(v)) => {
71                    let mut types = vec![];
72                    v.collect_wasi_types(&self.graph, &mut types);
73                    for ty in types {
74                        match ty.language_id() {
75                            Some(lhs) => {
76                                out.push_str(&format!("    {:#} -.-> {:#}\n", lhs, v.symbol));
77                            }
78                            _ => {}
79                        }
80                    }
81                }
82                _ => {}
83            }
84        }
85        out
86    }
87}
88
89pub enum CanonicalImport {
90    MockMemory,
91    Instance(WasiInstance),
92    Type(WasiType),
93}
94
95impl Debug for CanonicalImport {
96    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::MockMemory => f.write_str("MockMemory"),
99            Self::Instance(v) => Debug::fmt(v, f),
100            Self::Type(v) => Debug::fmt(v, f),
101        }
102    }
103}
104
105impl Default for CanonicalWasi {
106    fn default() -> Self {
107        Self { name: Arc::from("root"), graph: Default::default(), imports: vec![], type_signatures: true, indent_text: "    " }
108    }
109}
110
111impl AddAssign<WasiInstance> for CanonicalWasi {
112    fn add_assign(&mut self, rhs: WasiInstance) {
113        self.imports.push(CanonicalImport::Instance(rhs));
114    }
115}
116
117impl LowerFunction for CanonicalImport {
118    fn lower_function<W: Write>(&self, w: &mut WastEncoder<W>) -> std::fmt::Result {
119        match self {
120            CanonicalImport::MockMemory => {}
121            CanonicalImport::Instance(v) => {
122                for x in v.functions.values() {
123                    w.newline()?;
124                    x.lower_function(w)?;
125                }
126            }
127            CanonicalImport::Type(_) => {}
128        }
129        Ok(())
130    }
131    fn lower_import<W: Write>(&self, w: &mut WastEncoder<W>) -> std::fmt::Result {
132        match self {
133            CanonicalImport::MockMemory => {}
134            CanonicalImport::Instance(v) => {
135                for x in v.functions.values() {
136                    w.newline()?;
137                    x.lower_import(w)?;
138                }
139            }
140            CanonicalImport::Type(_) => {}
141        }
142        Ok(())
143    }
144}
145
146impl CanonicalWasi {
147    pub fn new(graph: DependentGraph) -> Result<Self, NyarError> {
148        let dag = match graph.resolve_imports() {
149            Ok(o) => o,
150            Err(_) => Err(NyarError::custom("graph error"))?,
151        };
152        let mut this = CanonicalWasi::default();
153        this.graph = graph;
154        this.imports.push(CanonicalImport::MockMemory);
155        this.imports.extend(dag);
156        Ok(this)
157    }
158    pub fn add_instance(&mut self, instance: WasiInstance) {
159        self.imports.push(CanonicalImport::Instance(instance));
160    }
161    pub fn encode(&self) -> String {
162        let mut output = String::with_capacity(1024);
163        let mut encoder = WastEncoder::new(&self, &mut output);
164        encoder.encode().unwrap();
165        output
166    }
167}
168
169impl<'a, W: Write> WastEncoder<'a, W> {
170    pub fn new(source: &'a CanonicalWasi, writer: W) -> Self {
171        Self { source, writer, indent: 0, stack: vec![] }
172    }
173}
174
175impl<'a, W: Write> Write for WastEncoder<'a, W> {
176    fn write_str(&mut self, s: &str) -> std::fmt::Result {
177        self.writer.write_str(s)
178    }
179}
180
181impl<'a, W: Write> WastEncoder<'a, W> {
182    pub fn encode(&mut self) -> std::fmt::Result {
183        write!(self.writer, "(component ${}", self.source.name)?;
184        self.indent();
185        for import in &self.source.imports {
186            self.newline()?;
187            import.component_define(self)?;
188        }
189        for import in &self.source.imports {
190            import.lower_function(self)?;
191        }
192        {
193            self.newline()?;
194            write!(self, "(core module $Main")?;
195            self.indent();
196
197            for import in &self.source.imports {
198                self.newline()?;
199                import.lower_import(self)?;
200            }
201
202            self.dedent(1);
203        }
204        {
205            //     (core instance $main (instantiate $Main
206            //         (with "wasi:debugger/print" (instance
207            //             (export "print-i8" (func $print_i8))
208            //         ))
209            //         (with "wasi:cli/stderr@0.2.0" (instance
210            //             (export "get-stderr" (func $std::io::standard_error))
211            //         ))
212            //     ))
213            self.newline()?;
214            write!(self, "(core instance $main (instantiate $Main")?;
215            self.indent();
216            for import in &self.source.imports {
217                match import {
218                    CanonicalImport::MockMemory => {}
219                    CanonicalImport::Instance(v) => {
220                        self.newline()?;
221                        write!(self, "(with \"{}\" (instance", v.module)?;
222                        self.indent();
223                        for x in v.functions.values() {
224                            self.newline()?;
225                            match &x.body {
226                                WasiFunctionBody::External { wasi_name, .. } => {
227                                    write!(self, "(export \"{}\" (func {}))", wasi_name, x.symbol.wasi_id())?;
228                                }
229                                WasiFunctionBody::Normal { .. } => {}
230                            }
231                        }
232                        self.dedent(2);
233                    }
234                    CanonicalImport::Type(_) => {}
235                }
236            }
237            self.dedent(2);
238        }
239
240        self.dedent(1);
241        Ok(())
242    }
243    pub fn indent(&mut self) {
244        self.indent += 1;
245    }
246    pub fn dedent(&mut self, end: usize) {
247        self.indent -= 1;
248        self.newline().ok();
249        for _ in 0..end {
250            self.write_char(')').ok();
251        }
252    }
253    pub fn newline(&mut self) -> std::fmt::Result {
254        self.write_str("\n")?;
255        let range = (0..self.indent).into_iter();
256        for _ in range {
257            let indent = self.source.indent_text.as_ref();
258            self.writer.write_str(indent)?;
259        }
260        Ok(())
261    }
262}
263
264pub fn encode_id(id: &str) -> String {
265    let mut alloc = String::with_capacity(id.len() + 1);
266    alloc.push('$');
267    make_kebab(id, &mut alloc);
268    alloc
269}
270
271pub fn encode_kebab(id: &str) -> String {
272    let mut alloc = String::with_capacity(id.len() + 2);
273    alloc.push('"');
274    make_kebab(id, &mut alloc);
275    alloc.push('"');
276    alloc
277}
278
279fn make_kebab(input: &str, buffer: &mut String) {
280    for c in input.chars() {
281        match c {
282            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | ':' | '@' | '/' => buffer.push(c),
283            _ => buffer.push('-'),
284        }
285    }
286}