rasn_compiler/generator/rasn/
mod.rs1use std::{
2 env,
3 error::Error,
4 io::{self, Write},
5 path::PathBuf,
6 process::{Command, Stdio},
7 str::FromStr,
8};
9
10use crate::{error::CompilerError, intermediate::*};
11use proc_macro2::TokenStream;
12use quote::{quote, ToTokens};
13
14#[cfg(target_family = "wasm")]
15use wasm_bindgen::prelude::*;
16
17use super::{
18 error::{GeneratorError, GeneratorErrorType},
19 Backend, GeneratedModule,
20};
21
22mod builder;
23mod template;
24mod utils;
25
26#[derive(Debug, Default)]
27pub struct Rasn {
30 config: Config,
31 tagging_environment: TaggingEnvironment,
32 extensibility_environment: ExtensibilityEnvironment,
33}
34
35#[cfg_attr(target_family = "wasm", wasm_bindgen(getter_with_clone))]
36#[derive(Debug)]
37pub struct Config {
39 pub opaque_open_types: bool,
48 pub default_wildcard_imports: bool,
54 pub generate_from_impls: bool,
60 pub custom_imports: Vec<String>,
64 pub type_annotations: Vec<String>,
70 pub no_std_compliant_bindings: bool,
72}
73
74#[cfg(target_family = "wasm")]
75#[wasm_bindgen]
76impl Config {
77 #[wasm_bindgen(constructor)]
78 pub fn new(
79 opaque_open_types: bool,
80 default_wildcard_imports: bool,
81 no_std_compliant_bindings: bool,
82 generate_from_impls: Option<bool>,
83 custom_imports: Option<Box<[String]>>,
84 type_annotations: Option<Box<[String]>>,
85 ) -> Self {
86 Self {
87 opaque_open_types,
88 default_wildcard_imports,
89 no_std_compliant_bindings,
90 generate_from_impls: generate_from_impls.unwrap_or(false),
91 custom_imports: custom_imports.map_or(Vec::new(), |c| c.into_vec()),
92 type_annotations: type_annotations
93 .map_or(Config::default().type_annotations, |c| c.into_vec()),
94 }
95 }
96}
97
98impl Default for Config {
99 fn default() -> Self {
100 Self {
101 opaque_open_types: true,
102 default_wildcard_imports: false,
103 generate_from_impls: false,
104 no_std_compliant_bindings: false,
105 custom_imports: Vec::default(),
106 type_annotations: vec![String::from(
107 "#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)]",
108 )],
109 }
110 }
111}
112
113impl Backend for Rasn {
114 type Config = Config;
115
116 const FILE_EXTENSION: &'static str = ".rs";
117
118 fn new(
119 config: Self::Config,
120 tagging_environment: TaggingEnvironment,
121 extensibility_environment: ExtensibilityEnvironment,
122 ) -> Self {
123 Self {
124 config,
125 extensibility_environment,
126 tagging_environment,
127 }
128 }
129
130 fn from_config(config: Self::Config) -> Self {
131 Self {
132 config,
133 ..Default::default()
134 }
135 }
136
137 fn config(&self) -> &Self::Config {
138 &self.config
139 }
140
141 fn generate_module(
142 &mut self,
143 tlds: Vec<ToplevelDefinition>,
144 ) -> Result<GeneratedModule, GeneratorError> {
145 if let Some(module_ref) = tlds.first().and_then(|tld| tld.get_module_header()) {
146 let module = module_ref.borrow();
147 self.tagging_environment = module.tagging_environment;
148 self.extensibility_environment = module.extensibility_environment;
149 let name = self.to_rust_snake_case(&module.name);
150 let custom_imports = self
151 .config
152 .custom_imports
153 .iter()
154 .map(|i| TokenStream::from_str(i.as_str()).map(|i| quote!(use #i;)))
155 .collect::<Result<Vec<_>, _>>()
156 .map_err(|e| GeneratorError {
157 details: e.to_string(),
158 ..Default::default()
159 })?;
160 let imports = module.imports.iter().map(|import| {
161 let module =
162 self.to_rust_snake_case(&import.global_module_reference.module_reference);
163 let mut usages = Some(vec![]);
164 'imports: for usage in &import.types {
165 if usage.contains("{}") || usage.chars().all(|c| c.is_uppercase() || c == '-') {
166 usages = None;
167 break 'imports;
168 } else if usage.starts_with(|c: char| c.is_lowercase()) {
169 if let Some(us) = usages.as_mut() {
170 us.push(self.to_rust_const_case(usage).to_token_stream())
171 }
172 } else if usage.starts_with(|c: char| c.is_uppercase()) {
173 if let Some(us) = usages.as_mut() {
174 us.push(self.to_rust_title_case(usage).to_token_stream())
175 }
176 }
177 }
178 let used_imports = if self.config.default_wildcard_imports {
179 vec![TokenStream::from_str("*").unwrap()]
180 } else {
181 usages.unwrap_or(vec![TokenStream::from_str("*").unwrap()])
182 };
183 quote!(use super:: #module::{ #(#used_imports),* };)
184 });
185 let (pdus, warnings): (Vec<TokenStream>, Vec<CompilerError>) =
186 tlds.into_iter().fold((vec![], vec![]), |mut acc, tld| {
187 match self.generate_tld(tld) {
188 Ok(s) => {
189 acc.0.push(s);
190 acc
191 }
192 Err(e) => {
193 acc.1.push(e.into());
194 acc
195 }
196 }
197 });
198 let lazy_const_import = if self.config.no_std_compliant_bindings {
199 quote!(lazy_static::lazy_static)
200 } else {
201 quote!(std::sync::LazyLock)
202 };
203 Ok(GeneratedModule {
204 generated: Some(quote! {
205 #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, unused,
206 clippy::too_many_arguments,)]
207 pub mod #name {
208 extern crate alloc;
209
210 use core::borrow::Borrow;
211 use #lazy_const_import;
212 use rasn::prelude::*;
213 #(#custom_imports)*
214 #(#imports)*
215
216 #(#pdus)*
217 }
218 }.to_string()), warnings})
219 } else {
220 Ok(GeneratedModule::empty())
221 }
222 }
223
224 fn format_bindings(bindings: &str) -> Result<String, CompilerError> {
225 Self::internal_fmt(bindings).map_err(|e| {
226 GeneratorError {
227 top_level_declaration: None,
228 details: e.to_string(),
229 kind: GeneratorErrorType::FormattingError,
230 }
231 .into()
232 })
233 }
234
235 fn generate(&self, tld: ToplevelDefinition) -> Result<String, GeneratorError> {
236 self.generate_tld(tld).map(|ts| ts.to_string())
237 }
238}
239
240impl Rasn {
241 fn get_rustfmt_path() -> Result<PathBuf, Box<dyn Error>> {
242 if let Ok(path) = env::var("CARGO_HOME").map(PathBuf::from).map(|mut path| {
244 path.push("bin/rustfmt");
245 path
246 }) {
247 if path.exists() {
248 return Ok(path);
249 }
250 }
251
252 if let Ok(path) = env::var("CARGO").map(PathBuf::from).map(|mut path| {
254 path.set_file_name("rustfmt");
255 path
256 }) {
257 if path.exists() {
258 return Ok(path);
259 }
260 }
261
262 Err("No rustfmt found.".into())
263 }
264
265 fn internal_fmt(bindings: &str) -> Result<String, Box<dyn Error>> {
266 let rustfmt = Self::get_rustfmt_path()?;
267 let mut cmd = Command::new(rustfmt);
268
269 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
270
271 let mut child = cmd.spawn()?;
272 let mut child_stdin = child.stdin.take().unwrap();
273 let mut child_stdout = child.stdout.take().unwrap();
274
275 let bindings = bindings.to_owned();
279 let stdin_handle = ::std::thread::spawn(move || {
280 let _ = child_stdin.write_all(bindings.as_bytes());
281 bindings
282 });
283
284 let mut output = vec![];
285 io::copy(&mut child_stdout, &mut output)?;
286
287 let status = child.wait()?;
288 let bindings = stdin_handle.join().expect(
289 "The thread writing to rustfmt's stdin doesn't do \
290 anything that could panic",
291 );
292
293 match String::from_utf8(output) {
294 Ok(bindings) => match status.code() {
295 Some(0) => Ok(bindings),
296 Some(2) => Err(Box::new(io::Error::other("Rustfmt parsing errors."))),
297 Some(3) => Ok(bindings),
298 _ => Err(Box::new(io::Error::other("Internal rustfmt error"))),
299 },
300 _ => Ok(bindings),
301 }
302 }
303}