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