wasefire_applet_api_desc/
lib.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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        /// Pointed type (None if opaque).
185        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                        // SAFETY: See `tests::enum_values_are_valid()`.
270                        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                /// Parameters of [Sig].
334                #[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 = &params 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                // TODO: Find a solution to have this computed at compile time.
503                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    /// Returns whether the type width is the pointer width.
592    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            // TODO: Is there a way to decorate this better?
656            Type::Pointer { mutable: _, type_: _ } => write!(output, "usize"),
657            // TODO: Is there a way to decorate this better?
658            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    /// Makes sure enum values form a range starting at zero.
753    ///
754    /// This invariant is assumed by unsafe code.
755    #[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}