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