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