|
1 | 1 | #![warn(clippy::uninlined_format_args)]
|
2 | 2 |
|
3 |
| -use anyhow::Context; |
4 | 3 | use clap::parser::ValueSource;
|
5 | 4 | use clap::Arg;
|
6 | 5 | use clap::ArgAction::Set;
|
7 |
| -use core::mem; |
8 | 6 | use fs_err as fs;
|
| 7 | +use spacetimedb_codegen::{ |
| 8 | + compile_wasm, extract_descriptions_from_module, generate, Csharp, Lang, Rust, TypeScript, AUTO_GENERATED_PREFIX, |
| 9 | +}; |
9 | 10 | use spacetimedb_lib::de::serde::DeserializeWrapper;
|
10 |
| -use spacetimedb_lib::{bsatn, RawModuleDefV8}; |
11 |
| -use spacetimedb_lib::{RawModuleDef, MODULE_ABI_MAJOR_VERSION}; |
12 |
| -use spacetimedb_primitives::errno; |
13 | 11 | use spacetimedb_schema;
|
14 |
| -use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef}; |
15 |
| -use spacetimedb_schema::identifier::Identifier; |
| 12 | +use spacetimedb_schema::def::ModuleDef; |
16 | 13 | use std::path::{Path, PathBuf};
|
17 |
| -use wasmtime::{Caller, StoreContextMut}; |
18 | 14 |
|
19 |
| -use crate::generate::util::iter_reducers; |
| 15 | +use crate::tasks::csharp::dotnet_format; |
| 16 | +use crate::tasks::rust::rustfmt; |
20 | 17 | use crate::util::y_or_n;
|
21 | 18 | use crate::Config;
|
22 | 19 | use crate::{build, common_args};
|
23 | 20 | use clap::builder::PossibleValue;
|
24 | 21 | use std::collections::BTreeSet;
|
25 | 22 | use std::io::Read;
|
26 |
| -use util::AUTO_GENERATED_PREFIX; |
27 |
| - |
28 |
| -mod code_indenter; |
29 |
| -pub mod csharp; |
30 |
| -pub mod rust; |
31 |
| -pub mod typescript; |
32 |
| -mod util; |
33 | 23 |
|
34 | 24 | pub fn cli() -> clap::Command {
|
35 | 25 | clap::Command::new("generate")
|
@@ -132,22 +122,23 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
|
132 | 122 | spinner.set_message("Extracting schema from wasm...");
|
133 | 123 | extract_descriptions_from_module(module)?
|
134 | 124 | };
|
| 125 | + let module: ModuleDef = module.try_into()?; |
135 | 126 |
|
136 | 127 | fs::create_dir_all(out_dir)?;
|
137 | 128 |
|
138 | 129 | let mut paths = BTreeSet::new();
|
139 | 130 |
|
140 | 131 | let csharp_lang;
|
141 |
| - let lang = match lang { |
| 132 | + let gen_lang = match lang { |
142 | 133 | Language::Csharp => {
|
143 |
| - csharp_lang = csharp::Csharp { namespace }; |
| 134 | + csharp_lang = Csharp { namespace }; |
144 | 135 | &csharp_lang as &dyn Lang
|
145 | 136 | }
|
146 |
| - Language::Rust => &rust::Rust, |
147 |
| - Language::TypeScript => &typescript::TypeScript, |
| 137 | + Language::Rust => &Rust, |
| 138 | + Language::TypeScript => &TypeScript, |
148 | 139 | };
|
149 | 140 |
|
150 |
| - for (fname, code) in generate(module, lang)? { |
| 141 | + for (fname, code) in generate(&module, gen_lang) { |
151 | 142 | let fname = Path::new(&fname);
|
152 | 143 | // If a generator asks for a file in a subdirectory, create the subdirectory first.
|
153 | 144 | if let Some(parent) = fname.parent().filter(|p| !p.as_os_str().is_empty()) {
|
@@ -223,156 +214,23 @@ impl clap::ValueEnum for Language {
|
223 | 214 | }
|
224 | 215 | fn to_possible_value(&self) -> Option<PossibleValue> {
|
225 | 216 | Some(match self {
|
226 |
| - Self::Csharp => csharp::Csharp::clap_value(), |
227 |
| - Self::TypeScript => typescript::TypeScript::clap_value(), |
228 |
| - Self::Rust => rust::Rust::clap_value(), |
| 217 | + Self::Csharp => clap::builder::PossibleValue::new("csharp").aliases(["c#", "cs"]), |
| 218 | + Self::TypeScript => clap::builder::PossibleValue::new("typescript").aliases(["ts", "TS"]), |
| 219 | + Self::Rust => clap::builder::PossibleValue::new("rust").aliases(["rs", "RS"]), |
229 | 220 | })
|
230 | 221 | }
|
231 | 222 | }
|
232 | 223 |
|
233 |
| -pub fn generate(module: RawModuleDef, lang: &dyn Lang) -> anyhow::Result<Vec<(String, String)>> { |
234 |
| - let module = &ModuleDef::try_from(module)?; |
235 |
| - Ok(itertools::chain!( |
236 |
| - module |
237 |
| - .tables() |
238 |
| - .map(|tbl| { (lang.table_filename(module, tbl), lang.generate_table(module, tbl),) }), |
239 |
| - module |
240 |
| - .types() |
241 |
| - .map(|typ| { (lang.type_filename(&typ.name), lang.generate_type(module, typ),) }), |
242 |
| - iter_reducers(module).map(|reducer| { |
243 |
| - ( |
244 |
| - lang.reducer_filename(&reducer.name), |
245 |
| - lang.generate_reducer(module, reducer), |
246 |
| - ) |
247 |
| - }), |
248 |
| - lang.generate_globals(module), |
249 |
| - ) |
250 |
| - .collect()) |
251 |
| -} |
252 |
| - |
253 |
| -pub trait Lang { |
254 |
| - fn table_filename(&self, module: &ModuleDef, table: &TableDef) -> String; |
255 |
| - fn type_filename(&self, type_name: &ScopedTypeName) -> String; |
256 |
| - fn reducer_filename(&self, reducer_name: &Identifier) -> String; |
257 |
| - |
258 |
| - fn generate_table(&self, module: &ModuleDef, tbl: &TableDef) -> String; |
259 |
| - fn generate_type(&self, module: &ModuleDef, typ: &TypeDef) -> String; |
260 |
| - fn generate_reducer(&self, module: &ModuleDef, reducer: &ReducerDef) -> String; |
261 |
| - fn generate_globals(&self, module: &ModuleDef) -> Vec<(String, String)>; |
262 |
| - |
263 |
| - fn format_files(&self, generated_files: BTreeSet<PathBuf>) -> anyhow::Result<()>; |
264 |
| - fn clap_value() -> PossibleValue |
265 |
| - where |
266 |
| - Self: Sized; |
267 |
| -} |
268 |
| - |
269 |
| -pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<RawModuleDef> { |
270 |
| - let module = compile_wasm(wasm_file)?; |
271 |
| - extract_descriptions_from_module(module) |
272 |
| -} |
273 |
| - |
274 |
| -fn compile_wasm(wasm_file: &Path) -> anyhow::Result<wasmtime::Module> { |
275 |
| - wasmtime::Module::from_file(&wasmtime::Engine::default(), wasm_file) |
276 |
| -} |
277 |
| - |
278 |
| -fn extract_descriptions_from_module(module: wasmtime::Module) -> anyhow::Result<RawModuleDef> { |
279 |
| - let engine = module.engine(); |
280 |
| - let ctx = WasmCtx { |
281 |
| - mem: None, |
282 |
| - sink: Vec::new(), |
283 |
| - }; |
284 |
| - let mut store = wasmtime::Store::new(engine, ctx); |
285 |
| - let mut linker = wasmtime::Linker::new(engine); |
286 |
| - linker.allow_shadowing(true).define_unknown_imports_as_traps(&module)?; |
287 |
| - let module_name = &*format!("spacetime_{MODULE_ABI_MAJOR_VERSION}.0"); |
288 |
| - linker.func_wrap( |
289 |
| - module_name, |
290 |
| - "console_log", |
291 |
| - |mut caller: Caller<'_, WasmCtx>, |
292 |
| - _level: u32, |
293 |
| - _target_ptr: u32, |
294 |
| - _target_len: u32, |
295 |
| - _filename_ptr: u32, |
296 |
| - _filename_len: u32, |
297 |
| - _line_number: u32, |
298 |
| - message_ptr: u32, |
299 |
| - message_len: u32| { |
300 |
| - let (mem, _) = WasmCtx::mem_env(&mut caller); |
301 |
| - let slice = deref_slice(mem, message_ptr, message_len).unwrap(); |
302 |
| - println!("from wasm: {}", String::from_utf8_lossy(slice)); |
303 |
| - }, |
304 |
| - )?; |
305 |
| - linker.func_wrap(module_name, "bytes_sink_write", WasmCtx::bytes_sink_write)?; |
306 |
| - let instance = linker.instantiate(&mut store, &module)?; |
307 |
| - let memory = instance.get_memory(&mut store, "memory").context("no memory export")?; |
308 |
| - store.data_mut().mem = Some(memory); |
309 |
| - |
310 |
| - let mut preinits = instance |
311 |
| - .exports(&mut store) |
312 |
| - .filter_map(|exp| Some((exp.name().strip_prefix("__preinit__")?.to_owned(), exp.into_func()?))) |
313 |
| - .collect::<Vec<_>>(); |
314 |
| - preinits.sort_by(|(a, _), (b, _)| a.cmp(b)); |
315 |
| - for (_, func) in preinits { |
316 |
| - func.typed(&store)?.call(&mut store, ())? |
317 |
| - } |
318 |
| - let module: RawModuleDef = match instance.get_func(&mut store, "__describe_module__") { |
319 |
| - Some(f) => { |
320 |
| - store.data_mut().sink = Vec::new(); |
321 |
| - f.typed::<u32, ()>(&store)?.call(&mut store, 1)?; |
322 |
| - let buf = mem::take(&mut store.data_mut().sink); |
323 |
| - bsatn::from_slice(&buf)? |
324 |
| - } |
325 |
| - // TODO: shouldn't we return an error here? |
326 |
| - None => RawModuleDef::V8BackCompat(RawModuleDefV8::default()), |
327 |
| - }; |
328 |
| - Ok(module) |
329 |
| -} |
330 |
| - |
331 |
| -struct WasmCtx { |
332 |
| - mem: Option<wasmtime::Memory>, |
333 |
| - sink: Vec<u8>, |
334 |
| -} |
335 |
| - |
336 |
| -fn deref_slice(mem: &[u8], offset: u32, len: u32) -> anyhow::Result<&[u8]> { |
337 |
| - anyhow::ensure!(offset != 0, "ptr is null"); |
338 |
| - mem.get(offset as usize..) |
339 |
| - .and_then(|s| s.get(..len as usize)) |
340 |
| - .context("pointer out of bounds") |
341 |
| -} |
342 |
| - |
343 |
| -fn read_u32(mem: &[u8], offset: u32) -> anyhow::Result<u32> { |
344 |
| - Ok(u32::from_le_bytes(deref_slice(mem, offset, 4)?.try_into().unwrap())) |
345 |
| -} |
346 |
| - |
347 |
| -impl WasmCtx { |
348 |
| - pub fn get_mem(&self) -> wasmtime::Memory { |
349 |
| - self.mem.expect("Initialized memory") |
350 |
| - } |
351 |
| - |
352 |
| - fn mem_env<'a>(ctx: impl Into<StoreContextMut<'a, Self>>) -> (&'a mut [u8], &'a mut Self) { |
353 |
| - let ctx = ctx.into(); |
354 |
| - let mem = ctx.data().get_mem(); |
355 |
| - mem.data_and_store_mut(ctx) |
356 |
| - } |
357 |
| - |
358 |
| - pub fn bytes_sink_write( |
359 |
| - mut caller: Caller<'_, Self>, |
360 |
| - sink_handle: u32, |
361 |
| - buffer_ptr: u32, |
362 |
| - buffer_len_ptr: u32, |
363 |
| - ) -> anyhow::Result<u32> { |
364 |
| - if sink_handle != 1 { |
365 |
| - return Ok(errno::NO_SUCH_BYTES.get().into()); |
| 224 | +impl Language { |
| 225 | + fn format_files(&self, generated_files: BTreeSet<PathBuf>) -> anyhow::Result<()> { |
| 226 | + match self { |
| 227 | + Language::Rust => rustfmt(generated_files)?, |
| 228 | + Language::Csharp => dotnet_format(generated_files)?, |
| 229 | + Language::TypeScript => { |
| 230 | + // TODO: implement formatting. |
| 231 | + } |
366 | 232 | }
|
367 | 233 |
|
368 |
| - let (mem, env) = Self::mem_env(&mut caller); |
369 |
| - |
370 |
| - // Read `buffer_len`, i.e., the capacity of `buffer` pointed to by `buffer_ptr`. |
371 |
| - let buffer_len = read_u32(mem, buffer_len_ptr)?; |
372 |
| - // Write `buffer` to `sink`. |
373 |
| - let buffer = deref_slice(mem, buffer_ptr, buffer_len)?; |
374 |
| - env.sink.extend(buffer); |
375 |
| - |
376 |
| - Ok(0) |
| 234 | + Ok(()) |
377 | 235 | }
|
378 | 236 | }
|
0 commit comments