1use std::ffi::CString;
16use std::io::Write;
17use std::ops;
18
19use proc_macro2::{Ident, Span, TokenStream};
20use quote::{format_ident, quote};
21
22#[cfg(feature = "api-button")]
23mod button;
24#[cfg(feature = "api-clock")]
25mod clock;
26#[cfg(feature = "internal-api-crypto")]
27mod crypto;
28mod debug;
29#[cfg(feature = "internal-api-fingerprint")]
30mod fingerprint;
31#[cfg(feature = "api-gpio")]
32mod gpio;
33mod id;
34#[cfg(feature = "api-led")]
35mod led;
36mod macros;
37#[cfg(feature = "internal-api-platform")]
38mod platform;
39#[cfg(feature = "api-rng")]
40mod rng;
41mod scheduling;
42#[cfg(feature = "internal-api-store")]
43mod store;
44#[cfg(feature = "api-timer")]
45mod timer;
46#[cfg(feature = "api-uart")]
47mod uart;
48#[cfg(feature = "internal-api-usb")]
49mod usb;
50#[cfg(feature = "api-vendor")]
51mod vendor;
52
53pub use id::{Id, Name};
54
55#[derive(Debug, Clone)]
56pub struct Api(Vec<Item>);
57
58impl Default for Api {
59 fn default() -> Self {
60 Api(vec![
61 #[cfg(feature = "api-button")]
62 button::new(),
63 #[cfg(feature = "api-clock")]
64 clock::new(),
65 #[cfg(feature = "internal-api-crypto")]
66 crypto::new(),
67 debug::new(),
68 #[cfg(feature = "internal-api-fingerprint")]
69 fingerprint::new(),
70 #[cfg(feature = "api-gpio")]
71 gpio::new(),
72 #[cfg(feature = "api-led")]
73 led::new(),
74 #[cfg(feature = "internal-api-platform")]
75 platform::new(),
76 #[cfg(feature = "api-rng")]
77 rng::new(),
78 scheduling::new(),
79 #[cfg(feature = "internal-api-store")]
80 store::new(),
81 #[cfg(feature = "api-timer")]
82 timer::new(),
83 #[cfg(feature = "api-uart")]
84 uart::new(),
85 #[cfg(feature = "internal-api-usb")]
86 usb::new(),
87 #[cfg(feature = "api-vendor")]
88 vendor::new(),
89 ])
90 }
91}
92
93#[derive(Copy, Clone)]
94pub enum Lang {
95 C,
96 Assemblyscript,
97}
98
99impl Api {
100 pub fn host(&self) -> TokenStream {
101 Mod::body(None, &self.0)
102 }
103
104 pub fn wasm(&self, output: &mut dyn Write, lang: Lang) -> std::io::Result<()> {
105 match lang {
106 Lang::C => unimplemented!(),
107 Lang::Assemblyscript => self.wasm_assemblyscript(output),
108 }
109 }
110
111 pub fn wasm_markdown(&self) -> &'static str {
112 include_str!("api.md")
113 }
114
115 pub fn wasm_rust(&self) -> TokenStream {
116 let items: Vec<_> = self.0.iter().map(|x| x.wasm_rust()).collect();
117 quote!(#(#items)*)
118 }
119
120 pub fn wasm_assemblyscript(&self, output: &mut dyn Write) -> std::io::Result<()> {
121 write_items(output, &self.0, |output, item| item.wasm_assemblyscript(output, &Path::Empty))
122 }
123}
124
125#[derive(Debug, Clone)]
126enum Item {
127 #[cfg_attr(not(feature = "api-button"), allow(dead_code))]
128 Enum(Enum),
129 Struct(Struct),
130 Fn(Fn),
131 Mod(Mod),
132}
133
134#[derive(Debug, Clone)]
135struct Enum {
136 docs: Vec<String>,
137 name: String,
138 variants: Vec<Variant>,
139}
140
141#[derive(Debug, Clone)]
142struct Struct {
143 docs: Vec<String>,
144 name: String,
145 fields: Vec<Field>,
146}
147
148#[derive(Debug, Clone)]
149struct Variant {
150 docs: Vec<String>,
151 name: String,
152 value: u32,
153}
154
155#[derive(Debug, Clone)]
156struct Fn {
157 docs: Vec<String>,
158 name: String,
159 link: String,
160 params: Vec<Field>,
161 result: Type,
162}
163
164#[derive(Debug, Clone)]
165struct Field {
166 docs: Vec<String>,
167 name: String,
168 type_: Type,
169}
170
171#[derive(Debug, Clone)]
172enum Type {
173 Never,
174 Unit,
175 #[cfg_attr(not(feature = "api-store"), allow(dead_code))]
176 Bool,
177 Integer {
178 signed: bool,
179 bits: Option<usize>,
180 },
181 Pointer {
182 mutable: bool,
183
184 type_: Option<Box<Type>>,
186 },
187 #[cfg_attr(not(feature = "api-button"), allow(dead_code))]
188 Function {
189 params: Vec<Field>,
190 },
191}
192
193#[derive(Debug, Clone)]
194struct Mod {
195 docs: Vec<String>,
196 name: String,
197 items: Vec<Item>,
198}
199
200impl Item {
201 fn host(&self) -> TokenStream {
202 match self {
203 Item::Enum(x) => x.host(),
204 Item::Struct(x) => x.host(),
205 Item::Fn(x) => x.host(),
206 Item::Mod(x) => x.host(),
207 }
208 }
209
210 fn wasm_rust(&self) -> TokenStream {
211 match self {
212 Item::Enum(x) => x.wasm_rust(),
213 Item::Struct(x) => x.wasm_rust(),
214 Item::Fn(x) => x.wasm_rust(),
215 Item::Mod(x) => x.wasm_rust(),
216 }
217 }
218
219 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
220 match self {
221 Item::Enum(x) => x.wasm_assemblyscript(output, path),
222 Item::Struct(x) => x.wasm_assemblyscript(output, path),
223 Item::Fn(x) => x.wasm_assemblyscript(output, path),
224 Item::Mod(x) => x.wasm_assemblyscript(output, path),
225 }
226 }
227}
228
229impl Enum {
230 fn host(&self) -> TokenStream {
231 let definition = self.definition();
232 let name = format_ident!("{}", self.name);
233 quote! {
234 #definition
235 impl From<#name> for crate::U32<usize> {
236 fn from(x: #name) -> Self { (x as u32).into() }
237 }
238 }
239 }
240
241 fn wasm_rust(&self) -> TokenStream {
242 let definition = self.definition();
243 let name = format_ident!("{}", self.name);
244 quote! {
245 #definition
246 impl From<usize> for #name {
247 fn from(x: usize) -> Self {
248 (x as u32).try_into().unwrap()
249 }
250 }
251 }
252 }
253
254 fn definition(&self) -> TokenStream {
255 let Enum { docs, name, variants } = self;
256 let name = format_ident!("{}", name);
257 let variants: Vec<_> = variants.iter().map(|x| x.wasm_rust()).collect();
258 let num_variants = variants.len();
259 quote! {
260 #(#[doc = #docs])*
261 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
262 #[repr(u32)]
263 pub enum #name { #(#variants),* }
264 impl TryFrom<u32> for #name {
265 type Error = wasefire_error::Error;
266
267 fn try_from(x: u32) -> Result<Self, Self::Error> {
268 if x < #num_variants as u32 {
269 Ok(unsafe { core::mem::transmute(x) })
271 } else {
272 Err(wasefire_error::Error::user(wasefire_error::Code::OutOfBounds))
273 }
274 }
275 }
276 }
277 }
278
279 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
280 let Enum { docs, name, variants } = self;
281 write_docs(output, docs, path)?;
282 writeln!(output, "{path:#}enum {path}{name} {{")?;
283 write_items(output, variants, |output, variant| variant.wasm_assemblyscript(output, path))?;
284 writeln!(output, "{path:#}}}")
285 }
286}
287
288impl Struct {
289 fn host(&self) -> TokenStream {
290 self.definition(|x| x.host())
291 }
292
293 fn wasm_rust(&self) -> TokenStream {
294 self.definition(|x| x.wasm_rust())
295 }
296
297 fn definition(&self, field: impl ops::Fn(&Field) -> TokenStream) -> TokenStream {
298 let Struct { docs, name, fields } = self;
299 let name = format_ident!("{}", name);
300 let fields: Vec<_> = fields.iter().map(field).collect();
301 quote! {
302 #(#[doc = #docs])*
303 #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
304 #[repr(C)]
305 pub struct #name { #(#fields),* }
306 }
307 }
308
309 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
310 let Struct { docs, name, fields } = self;
311 write_docs(output, docs, path)?;
312 writeln!(output, "{path:#}class {path}{name} {{")?;
313 write_items(output, fields, |output, field| field.wasm_assemblyscript(output, path, ";"))?;
314 writeln!(output, "{path:#}}}")
315 }
316}
317
318impl Fn {
319 fn host(&self) -> TokenStream {
320 let Fn { docs, name, link, params, result } = self;
321 let name = format_ident!("{}", name);
322 let doc = format!("Module of [`{name}`]({name}::Sig).");
323 let params: Vec<_> = params.iter().map(|x| x.host()).collect();
324 let result = result.host();
325 quote! {
326 #[doc = #doc]
327 pub mod #name {
328 #(#[doc = #docs])*
329 #[derive(Debug, Default, Copy, Clone)]
330 #[repr(C)]
331 pub struct Sig;
332
333 #[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
335 #[repr(C)]
336 pub struct Params { #(#params,)* }
337
338 #[sealed::sealed] impl crate::ArrayU32 for Params {}
339 #[sealed::sealed]
340 impl crate::Signature for Sig {
341 const NAME: &'static str = #link;
342 type Params = Params;
343 type Result = #result;
344 }
345 }
346 }
347 }
348
349 fn wasm_rust(&self) -> TokenStream {
350 let Fn { docs, name, link, params, result: _ } = self;
351 let name = format_ident!("{name}");
352 let name_wasm = format_ident!("{name}_wasm");
353 let env_link = format!("env_{link}");
354 let ffi_link = syn::LitCStr::new(&CString::new(link.clone()).unwrap(), Span::call_site());
355 let doc = format!("Module of [`{name}`]({name}()).");
356 let params_doc = format!("Parameters of [`{name}`](super::{name}())");
357 let params: Vec<_> = params.iter().map(|x| x.wasm_rust()).collect();
358 let names_wasm: Vec<_> = self.params.iter().map(|x| format_ident!("{}", x.name)).collect();
359 let params_wasm: Vec<_> = self.params.iter().map(|x| x.param()).collect();
360 let (fn_params, let_params, let_params_wasm) = if params.is_empty() {
361 (None, quote!(let ffi_params = core::ptr::null();), None)
362 } else {
363 (
364 Some(quote!(params: #name::Params)),
365 quote!(let ffi_params = ¶ms as *const _ as *const u32;),
366 Some(quote!(let #name::Params { #(#names_wasm,)* } = params;)),
367 )
368 };
369 quote! {
370 #[doc = #doc]
371 pub mod #name {
372 #[doc = #params_doc]
373 #[derive(Debug, Copy, Clone)]
374 #[repr(C)]
375 pub struct Params { #(#params,)* }
376 }
377 #[cfg(not(feature = "native"))]
378 unsafe extern "C" {
379 #[link_name = #link]
380 unsafe fn #name_wasm(#(#params_wasm,)*) -> isize;
381 }
382 #[cfg(not(feature = "native"))]
383 #(#[doc = #docs])*
384 pub unsafe fn #name(#fn_params) -> isize {
385 #let_params_wasm
386 unsafe { #name_wasm(#(#names_wasm,)*) }
387 }
388 #[cfg(feature = "native")]
389 #[unsafe(export_name = #env_link)]
390 #[linkage = "weak"]
391 pub unsafe extern "C" fn #name(#fn_params) -> isize {
392 #let_params
393 crate::wasm::native::env_dispatch(#ffi_link.as_ptr(), ffi_params)
394 }
395 }
396 }
397
398 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
399 let Fn { docs, name, link, params, result: _ } = self;
400 write_docs(output, docs, path)?;
401 writeln!(output, r#"{path:#}@external("env", "{link}")"#)?;
402 writeln!(output, "{path:#}export declare function {path}{name}(")?;
403 write_items(output, params, |output, param| param.wasm_assemblyscript(output, path, ","))?;
404 writeln!(output, "{path:#}): i32")
405 }
406}
407
408impl Mod {
409 fn host(&self) -> TokenStream {
410 let Mod { docs, name, items } = self;
411 let ident = format_ident!("{}", name);
412 let body = Mod::body(Some(name), items);
413 quote! {
414 #(#[doc = #docs])*
415 pub mod #ident {
416 #body
417 }
418 }
419 }
420
421 fn wasm_rust(&self) -> TokenStream {
422 let Mod { docs, name, items } = self;
423 let name = format_ident!("{}", name);
424 let items: Vec<_> = items.iter().map(|x| x.wasm_rust()).collect();
425 quote! {
426 #(#[doc = #docs])*
427 pub mod #name {
428 #(#items)*
429 }
430 }
431 }
432
433 fn body(name: Option<&str>, items: &[Item]) -> TokenStream {
434 let mut api = Vec::new();
435 let mut ident_camels = Vec::new();
436 let mut inner = Vec::new();
437 let mut merge = Vec::new();
438 let mut descriptor = Vec::new();
439 let mut iter = Vec::new();
440 let mut id = Vec::new();
441 let mut erase = Vec::new();
442 for item in items {
443 match item {
444 Item::Enum(_) => (),
445 Item::Struct(_) => (),
446 Item::Fn(Fn { name, .. }) => {
447 let doc = format!("Selector for [`{name}`]({name}::Sig).");
448 let name_camel = camel(name);
449 let name = format_ident!("{}", name);
450 let pat = quote!(Api::#name_camel);
451 api.push(quote!(#[doc = #doc] #name_camel(T::Merged<#name::Sig>),));
452 ident_camels.push(name_camel.clone());
453 inner.push(quote!(T::Merged<#name::Sig>));
454 merge.push(quote!(#pat(_) => Api::#name_camel(T::merge(erased)),));
455 descriptor
456 .push(quote!(#pat(_) => <#name::Sig as crate::Signature>::descriptor(),));
457 iter.push(quote!(output.push(wrap(Api::#name_camel(#name::Sig)));));
458 id.push(quote!(#pat(_) => Api::#name_camel(#name::Sig),));
459 erase.push(quote!(#pat(x) => <T as crate::Dispatch>::erase(x),));
460 }
461 Item::Mod(Mod { name, .. }) => {
462 let doc = format!("Selector for [`{name}`]({name}).");
463 let name_camel = camel(name);
464 let name = format_ident!("{}", name);
465 let pat = quote!(Api::#name_camel(x) =>);
466 api.push(quote!(#[doc = #doc] #name_camel(#name::Api<T>),));
467 ident_camels.push(name_camel.clone());
468 inner.push(quote!(#name::Api<T>));
469 merge.push(quote!(#pat Api::#name_camel(x.merge(erased)),));
470 descriptor.push(quote!(#pat x.descriptor(),));
471 iter.push(quote! {
472 #name::Api::<crate::Id>::iter(output, |x| wrap(Api::#name_camel(x)));
473 });
474 id.push(quote!(#pat Api::#name_camel(x.id()),));
475 erase.push(quote!(#pat x.erase(),));
476 }
477 }
478 }
479 let items: Vec<_> = items.iter().map(|x| x.host()).collect();
480 let doc = match name {
481 Some(x) => format!(" API for [`{x}`](self)"),
482 None => " Applet API".to_string(),
483 };
484 quote! {
485 #(#items)*
486 #[doc = #doc]
487 #[derive(Debug)]
488 pub enum Api<T: crate::Dispatch> { #(#api)* }
489 impl<T: crate::Dispatch> Copy for Api<T> where #(#inner: Copy,)* {}
490 impl<T: crate::Dispatch> Clone for Api<T> where #(#inner: Clone,)* {
491 fn clone(&self) -> Self {
492 match self { #(Api::#ident_camels(x) => Api::#ident_camels(x.clone()),)* }
493 }
494 }
495 impl Api<crate::Id> {
496 pub fn merge<T: crate::Dispatch>(&self, erased: T::Erased) -> Api<T> {
497 match self { #(#merge)* }
498 }
499 pub fn descriptor(&self) -> crate::Descriptor {
500 match self { #(#descriptor)* }
501 }
502 pub fn iter<T>(output: &mut alloc::vec::Vec<T>, wrap: impl Fn(Self) -> T) {
504 #(#iter)*
505 }
506 }
507 impl<T: crate::Dispatch> Api<T> {
508 pub fn id(&self) -> Api<crate::Id> {
509 match self { #(#id)* }
510 }
511 pub fn erase(self) -> T::Erased {
512 match self { #(#erase)* }
513 }
514 }
515 }
516 }
517
518 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
519 let Mod { docs, name, items } = self;
520 writeln!(output, "{path:#}// START OF MODULE {path}{name}")?;
521 write_docs(output, docs, path)?;
522 let inner_path = Path::Mod { name, prev: path };
523 write_items(output, items, |output, item| item.wasm_assemblyscript(output, &inner_path))?;
524 writeln!(output, "{path:#}// END OF MODULE {path}{name}")
525 }
526}
527
528impl Field {
529 fn host(&self) -> TokenStream {
530 self.definition(|x| x.host())
531 }
532
533 fn wasm_rust(&self) -> TokenStream {
534 self.definition(|x| x.wasm_rust())
535 }
536
537 fn definition(&self, quote_type: impl ops::Fn(&Type) -> TokenStream) -> TokenStream {
538 let Field { docs, name, type_ } = self;
539 let name = format_ident!("{}", name);
540 let type_ = quote_type(type_);
541 quote!(#(#[doc = #docs])* pub #name: #type_)
542 }
543
544 fn param(&self) -> TokenStream {
545 let Field { docs: _, name, type_ } = self;
546 let name = format_ident!("{}", name);
547 let type_ = type_.wasm_rust();
548 quote!(#name: #type_)
549 }
550
551 fn wasm_assemblyscript(
552 &self, output: &mut dyn Write, path: &Path, separator: &str,
553 ) -> std::io::Result<()> {
554 let Field { docs, name, type_ } = self;
555 let name = match name.as_str() {
556 "private" => "private_",
557 "public" => "public_",
558 x => x,
559 };
560 let path = Path::Mod { name: "", prev: path };
561 write_docs(output, docs, &path)?;
562 write!(output, "{path:#}{name}: ")?;
563 type_.wasm_assemblyscript(output)?;
564 writeln!(output, "{separator}")
565 }
566}
567
568impl Type {
569 #[cfg(test)]
570 fn is_param(&self) -> bool {
571 match self {
572 Type::Never | Type::Unit | Type::Bool => false,
573 Type::Integer { bits: None, .. } => true,
574 Type::Integer { bits: Some(32), .. } => true,
575 Type::Integer { bits: Some(_), .. } => false,
576 Type::Pointer { .. } => true,
577 Type::Function { .. } => true,
578 }
579 }
580
581 #[cfg(test)]
582 fn is_result(&self) -> bool {
583 #[allow(clippy::match_like_matches_macro)]
584 match self {
585 Type::Never | Type::Unit | Type::Bool => true,
586 Type::Integer { signed: false, bits: None } => true,
587 _ => false,
588 }
589 }
590
591 fn needs_u32(&self) -> bool {
593 match self {
594 Type::Never | Type::Unit | Type::Bool => false,
595 Type::Integer { bits: None, .. } => true,
596 Type::Integer { bits: Some(_), .. } => false,
597 Type::Pointer { .. } => true,
598 Type::Function { .. } => true,
599 }
600 }
601
602 fn host(&self) -> TokenStream {
603 let mut type_ = self.wasm_rust();
604 if self.needs_u32() {
605 type_ = quote!(crate::U32<#type_>);
606 }
607 type_
608 }
609
610 fn wasm_rust(&self) -> TokenStream {
611 match self {
612 Type::Never => quote!(!),
613 Type::Unit => quote!(()),
614 Type::Bool => quote!(bool),
615 Type::Integer { signed: true, bits: None } => quote!(isize),
616 Type::Integer { signed: false, bits: None } => quote!(usize),
617 Type::Integer { signed: true, bits: Some(8) } => quote!(i8),
618 Type::Integer { signed: false, bits: Some(8) } => quote!(u8),
619 Type::Integer { signed: true, bits: Some(16) } => quote!(i16),
620 Type::Integer { signed: false, bits: Some(16) } => quote!(u16),
621 Type::Integer { signed: true, bits: Some(32) } => quote!(i32),
622 Type::Integer { signed: false, bits: Some(32) } => quote!(u32),
623 Type::Integer { signed: true, bits: Some(64) } => quote!(i64),
624 Type::Integer { signed: false, bits: Some(64) } => quote!(u64),
625 Type::Integer { .. } => unimplemented!(),
626 Type::Pointer { mutable, type_ } => {
627 let mutable = if *mutable { quote!(mut) } else { quote!(const) };
628 let type_ = match type_ {
629 None => quote!(u8),
630 Some(x) => x.wasm_rust(),
631 };
632 quote!(*#mutable #type_)
633 }
634 Type::Function { params } => {
635 let params: Vec<_> = params.iter().map(|x| x.param()).collect();
636 quote!(extern "C" fn(#(#params),*))
637 }
638 }
639 }
640
641 fn wasm_assemblyscript(&self, output: &mut dyn Write) -> std::io::Result<()> {
642 match self {
643 Type::Never | Type::Unit | Type::Bool => unreachable!(),
644 Type::Integer { signed: true, bits: None } => write!(output, "isize"),
645 Type::Integer { signed: false, bits: None } => write!(output, "usize"),
646 Type::Integer { signed: true, bits: Some(8) } => write!(output, "i8"),
647 Type::Integer { signed: false, bits: Some(8) } => write!(output, "u8"),
648 Type::Integer { signed: true, bits: Some(16) } => write!(output, "i16"),
649 Type::Integer { signed: false, bits: Some(16) } => write!(output, "u16"),
650 Type::Integer { signed: true, bits: Some(32) } => write!(output, "i32"),
651 Type::Integer { signed: false, bits: Some(32) } => write!(output, "u32"),
652 Type::Integer { signed: true, bits: Some(64) } => write!(output, "i64"),
653 Type::Integer { signed: false, bits: Some(64) } => write!(output, "u64"),
654 Type::Integer { .. } => unimplemented!(),
655 Type::Pointer { mutable: _, type_: _ } => write!(output, "usize"),
657 Type::Function { params: _ } => write!(output, "usize"),
659 }
660 }
661}
662
663impl Variant {
664 fn wasm_rust(&self) -> TokenStream {
665 let Variant { docs, name, value } = self;
666 let name = format_ident!("{}", name);
667 quote!(#(#[doc = #docs])* #name = #value)
668 }
669
670 fn wasm_assemblyscript(&self, output: &mut dyn Write, path: &Path) -> std::io::Result<()> {
671 let Variant { docs, name, value } = self;
672 let path = Path::Mod { name: "", prev: path };
673 write_docs(output, docs, &path)?;
674 writeln!(output, "{path:#}{name} = {value},")
675 }
676}
677
678fn camel(input: &str) -> Ident {
679 let mut output = String::new();
680 let mut begin = true;
681 for c in input.chars() {
682 if c == '_' {
683 begin = true;
684 continue;
685 }
686 if begin {
687 begin = false;
688 output.extend(c.to_uppercase());
689 } else {
690 output.push(c);
691 }
692 }
693 Ident::new(&output, Span::call_site())
694}
695
696enum Path<'a> {
697 Empty,
698 Mod { name: &'a str, prev: &'a Path<'a> },
699}
700
701impl std::fmt::Display for Path<'_> {
702 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
703 let (name, prev) = match self {
704 Path::Empty => return Ok(()),
705 Path::Mod { name, prev } => (name, prev),
706 };
707 if f.alternate() { write!(f, "{prev:#} ") } else { write!(f, "{prev}{name}_") }
708 }
709}
710
711fn write_docs(output: &mut dyn Write, docs: &[String], path: &Path) -> std::io::Result<()> {
712 for doc in docs {
713 writeln!(output, "{path:#}//{doc}")?;
714 }
715 Ok(())
716}
717
718fn write_items<T>(
719 output: &mut dyn Write, items: &[T],
720 mut write: impl FnMut(&mut dyn Write, &T) -> std::io::Result<()>,
721) -> std::io::Result<()> {
722 let mut first = true;
723 for item in items {
724 if !std::mem::replace(&mut first, false) {
725 writeln!(output)?;
726 }
727 write(output, item)?;
728 }
729 Ok(())
730}
731
732#[cfg(test)]
733mod tests {
734 use std::collections::{HashMap, HashSet};
735
736 use super::*;
737
738 #[test]
739 fn link_names_are_unique() {
740 let mut seen = HashSet::new();
741 let mut todo = Api::default().0;
742 while let Some(item) = todo.pop() {
743 match item {
744 Item::Enum(_) => (),
745 Item::Struct(_) => (),
746 Item::Fn(Fn { link, .. }) => assert_eq!(seen.replace(link), None),
747 Item::Mod(Mod { items, .. }) => todo.extend(items),
748 }
749 }
750 }
751
752 #[test]
756 fn enum_values_are_valid() {
757 let mut todo = Api::default().0;
758 while let Some(item) = todo.pop() {
759 match item {
760 Item::Enum(Enum { variants, .. }) => {
761 let mut seen = HashMap::new();
762 for Variant { name, value, .. } in variants {
763 if let Some(other) = seen.insert(value, name.clone()) {
764 panic!("duplicate enum value {value} between {name} and {other}");
765 }
766 }
767 for value in 0 .. seen.len() as u32 {
768 assert!(seen.contains_key(&value), "skipped enum value {value}");
769 }
770 }
771 Item::Struct(_) => (),
772 Item::Fn(_) => (),
773 Item::Mod(Mod { items, .. }) => todo.extend(items),
774 }
775 }
776 }
777
778 #[test]
779 fn params_are_u32() {
780 fn test(link: &str, field: &Field) {
781 let name = &field.name;
782 assert!(field.type_.is_param(), "Param {name} of {link:?} is not U32");
783 }
784 let mut todo = Api::default().0;
785 while let Some(item) = todo.pop() {
786 match item {
787 Item::Enum(_) => (),
788 Item::Struct(_) => (),
789 Item::Fn(Fn { link, params, .. }) => params.iter().for_each(|x| test(&link, x)),
790 Item::Mod(Mod { items, .. }) => todo.extend(items),
791 }
792 }
793 }
794
795 #[test]
796 fn results_are_valid() {
797 let mut todo = Api::default().0;
798 while let Some(item) = todo.pop() {
799 match item {
800 Item::Enum(_) => (),
801 Item::Struct(_) => (),
802 Item::Fn(Fn { link, result, .. }) => {
803 assert!(result.is_result(), "Result of {link:?} is invalid");
804 }
805 Item::Mod(Mod { items, .. }) => todo.extend(items),
806 }
807 }
808 }
809}