device_driver_mir/lowering/
gen_docs.rs1use 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
17pub 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}