|
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