Skip to main content

device_driver_mir/lowering/
gen_docs.rs

1use std::{fmt::Write, fs, num::NonZero, path::Path};
2
3use device_driver_common::{
4    identifier::IdentifierType,
5    span::{Span, SpanExt},
6    specifiers::{BaseType, VariantNames},
7};
8use device_driver_diagnostics::{DynError, ResultExt};
9use device_driver_parser::{Ident, Node, Property, Repeat, TypeSpecifier};
10use itertools::Itertools;
11
12use crate::{
13    lowering::{PropertyInfo, PropertyName, Shape},
14    model::{Block, Buffer, Command, Device, Enum, Extern, Field, FieldSet, Manifest, Register},
15};
16
17/// Generate docs for all object shapes
18pub fn gen_docs(folder: &Path) -> Result<(), DynError> {
19    gen_doc::<Manifest>(folder)?;
20    gen_doc::<Device>(folder)?;
21    gen_doc::<Block>(folder)?;
22    gen_doc::<Register>(folder)?;
23    gen_doc::<Command>(folder)?;
24    gen_doc::<Buffer>(folder)?;
25    gen_doc::<FieldSet>(folder)?;
26    gen_doc::<Enum>(folder)?;
27    gen_doc::<Extern>(folder)?;
28    gen_doc::<Field>(folder)?;
29
30    Ok(())
31}
32
33fn gen_doc<S: Shape>(folder: &Path) -> Result<(), DynError> {
34    let name = S::NODE_TYPE.to_string();
35    let mut doc = String::new();
36    let mut shape = S::default();
37
38    let short_properties = S::supported_properties()
39        .iter()
40        .filter(|p| matches!(p.name, PropertyName::Short(_)))
41        .collect::<Vec<_>>();
42    let long_properties = S::supported_properties()
43        .iter()
44        .filter(|p| matches!(p.name, PropertyName::Exact(_) | PropertyName::Any))
45        .collect::<Vec<_>>();
46
47    writeln!(doc, "## Example\n").into_dyn_result()?;
48    writeln!(doc, "```ddsl").into_dyn_result()?;
49    writeln!(doc, "{}", generate_shape_example::<S>()).into_dyn_result()?;
50    writeln!(doc, "```").into_dyn_result()?;
51
52    writeln!(doc, "## Table\n").into_dyn_result()?;
53
54    writeln!(doc, "| Property | Value |").into_dyn_result()?;
55    writeln!(doc, "| --- | --- |").into_dyn_result()?;
56    writeln!(
57        doc,
58        "| Identifier namespace | `{:?}` |",
59        S::NameIdentifierType::default().runtime_value()
60    )
61    .into_dyn_result()?;
62    writeln!(
63        doc,
64        "| Supports repeat | `{}` |",
65        bool_to_yes_no(shape.repeat().is_some())
66    )
67    .into_dyn_result()?;
68    writeln!(
69        doc,
70        "| Supports basetype | `{}` |",
71        bool_to_yes_no(shape.base_type().is_some())
72    )
73    .into_dyn_result()?;
74    writeln!(
75        doc,
76        "| Supports conversion type | `{}` |",
77        bool_to_yes_no(shape.conversion_type().is_some())
78    )
79    .into_dyn_result()?;
80    writeln!(
81        doc,
82        "| Supports short properties | {} |",
83        if !short_properties.is_empty() {
84            "`yes`, see below"
85        } else {
86            "`no`"
87        }
88    )
89    .into_dyn_result()?;
90    writeln!(
91        doc,
92        "| Supports properties | {} |",
93        if !long_properties.is_empty() {
94            "`yes`, see below"
95        } else {
96            "`no`"
97        }
98    )
99    .into_dyn_result()?;
100    writeln!(
101        doc,
102        "| Supports subnodes | {} |",
103        if S::supported_subnodes().is_some() {
104            "`yes`, see below"
105        } else {
106            "`no`"
107        }
108    )
109    .into_dyn_result()?;
110
111    if !short_properties.is_empty() {
112        writeln!(doc, "## Short properties").into_dyn_result()?;
113        writeln!(
114            doc,
115            "These properties are specified inline in the node definition and are used without name."
116        )
117        .into_dyn_result()?;
118        write_properties(&mut doc, short_properties.as_slice())?;
119    }
120    if !long_properties.is_empty() {
121        writeln!(doc, "## Long properties").into_dyn_result()?;
122        writeln!(doc, "These properties are specified in the node body.").into_dyn_result()?;
123        write_properties(&mut doc, long_properties.as_slice())?;
124    }
125    if let Some(subnodes) = S::supported_subnodes() {
126        writeln!(doc, "## Possible subnodes").into_dyn_result()?;
127        writeln!(
128            doc,
129            "Subnodes of the following types are allowed in the node body."
130        )
131        .into_dyn_result()?;
132        for subnode in subnodes {
133            writeln!(doc, "- [{subnode}]").into_dyn_result()?;
134        }
135    }
136
137    fs::write(folder.join(name).with_extension("md"), doc)
138        .with_message(|| "writing mir shape to file")
139}
140
141fn bool_to_yes_no(val: bool) -> &'static str {
142    if val { "yes" } else { "no" }
143}
144
145fn write_properties<S: Shape>(
146    doc: &mut dyn Write,
147    properties: &[&PropertyInfo<S>],
148) -> Result<(), DynError> {
149    for property in properties {
150        let name = match property.name {
151            PropertyName::Exact(name) => name,
152            PropertyName::Any => "*any name*",
153            PropertyName::Short(name) => name,
154        };
155
156        writeln!(doc, "### {name}").into_dyn_result()?;
157        for description_line in property.description.lines() {
158            writeln!(doc, "{description_line}").into_dyn_result()?;
159        }
160        writeln!(
161            doc,
162            "```ddsl\n{}\n```",
163            property
164                .allowed_expression_types
165                .iter()
166                .map(|expr| format!("// {}\n{name}: {}", expr, expr.get_human_string()))
167                .join(",\n")
168        )
169        .into_dyn_result()?;
170        writeln!(doc, "#### Info").into_dyn_result()?;
171        writeln!(doc, "- required: `{}`", bool_to_yes_no(property.required)).into_dyn_result()?;
172        writeln!(
173            doc,
174            "- multiple allowed: `{}`",
175            bool_to_yes_no(property.multiple_allowed)
176        )
177        .into_dyn_result()?;
178        writeln!(
179            doc,
180            "- supports doc comments: `{}`",
181            bool_to_yes_no(property.supports_doc_comments)
182        )
183        .into_dyn_result()?;
184    }
185
186    Ok(())
187}
188
189fn generate_shape_example<S: Shape>() -> Node<'static> {
190    let mut shape = S::default();
191
192    Node {
193        doc_comments: vec![" doc comment line".with_dummy_span()],
194        node_type: Ident::new_no_span(S::NODE_TYPE.name()),
195        name: Ident::new_no_span("Example"),
196        repeat: shape.repeat().map(|_| {
197            Repeat {
198                source: device_driver_parser::RepeatSource::Count(NonZero::new(8).unwrap())
199                    .with_dummy_span(),
200                stride: 4.with_dummy_span(),
201            }
202            .with_dummy_span()
203        }),
204        type_specifier: shape.base_type().is_some().then(|| {
205            TypeSpecifier {
206                base_type: BaseType::Uint.with_dummy_span(),
207                use_try: true,
208                conversion: shape.conversion_type().map(|_| {
209                    device_driver_parser::TypeConversion::Reference(Ident::new_no_span("Foo"))
210                }),
211            }
212            .with_dummy_span()
213        }),
214        short_properties: S::supported_properties()
215            .iter()
216            .filter_map(|p| {
217                p.name
218                    .as_short()
219                    .map(|_| p.allowed_expression_types[0].clone().with_dummy_span())
220            })
221            .collect(),
222        properties: S::supported_properties()
223            .iter()
224            .filter_map(|p| match p.name {
225                PropertyName::Exact(name) => Some(
226                    Property {
227                        doc_comments: p
228                            .supports_doc_comments
229                            .then_some(" doc comment line".with_dummy_span())
230                            .into_iter()
231                            .collect(),
232                        name: Ident::new_no_span(name),
233                        expression: p.allowed_expression_types[0].clone().with_dummy_span(),
234                    }
235                    .with_dummy_span(),
236                ),
237                PropertyName::Any => Some(
238                    Property {
239                        doc_comments: p
240                            .supports_doc_comments
241                            .then_some(" doc comment line".with_dummy_span())
242                            .into_iter()
243                            .collect(),
244                        name: Ident::new_no_span("Any"),
245                        expression: p.allowed_expression_types[0].clone().with_dummy_span(),
246                    }
247                    .with_dummy_span(),
248                ),
249                PropertyName::Short(_) => None,
250            })
251            .collect(),
252        sub_nodes: S::supported_subnodes()
253            .unwrap_or_default()
254            .iter()
255            .map(|node_type| Node {
256                doc_comments: Vec::new(),
257                node_type: Ident::new_no_span(node_type.name()),
258                name: Ident::new_no_span("node"),
259                repeat: None,
260                type_specifier: None,
261                short_properties: Vec::new(),
262                properties: Vec::new(),
263                sub_nodes: Vec::new(),
264                span: Span::empty(),
265            })
266            .collect(),
267        span: Span::empty(),
268    }
269}