1pub mod config;
4pub mod element;
5mod token;
6pub mod v1;
7
8use std::fmt::Write;
9
10pub use config::*;
11pub use token::*;
12use wdl_ast::Element;
13use wdl_ast::Node as AstNode;
14
15use crate::element::FormatElement;
16
17pub const SPACE: &str = " ";
19pub const TAB: &str = "\t";
21
22pub trait Writable {
24 fn write(&self, stream: &mut TokenStream<PreToken>, config: &Config);
26}
27
28impl Writable for &FormatElement {
29 fn write(&self, stream: &mut TokenStream<PreToken>, config: &Config) {
30 match self.element() {
31 Element::Node(node) => match node {
32 AstNode::AccessExpr(_) => v1::expr::format_access_expr(self, stream, config),
33 AstNode::AdditionExpr(_) => v1::expr::format_addition_expr(self, stream, config),
34 AstNode::ArrayType(_) => v1::decl::format_array_type(self, stream, config),
35 AstNode::Ast(_) => v1::format_ast(self, stream, config),
36 AstNode::BoundDecl(_) => v1::decl::format_bound_decl(self, stream, config),
37 AstNode::CallAfter(_) => {
38 v1::workflow::call::format_call_after(self, stream, config)
39 }
40 AstNode::CallAlias(_) => {
41 v1::workflow::call::format_call_alias(self, stream, config)
42 }
43 AstNode::CallExpr(_) => v1::expr::format_call_expr(self, stream, config),
44 AstNode::CallInputItem(_) => {
45 v1::workflow::call::format_call_input_item(self, stream, config)
46 }
47 AstNode::CallStatement(_) => {
48 v1::workflow::call::format_call_statement(self, stream, config)
49 }
50 AstNode::CallTarget(_) => {
51 v1::workflow::call::format_call_target(self, stream, config)
52 }
53 AstNode::CommandSection(_) => {
54 v1::task::format_command_section(self, stream, config)
55 }
56 AstNode::ConditionalStatement(_) => {
57 v1::workflow::format_conditional_statement(self, stream, config)
58 }
59 AstNode::ConditionalStatementClause(_) => {
60 v1::workflow::format_conditional_statement_clause(self, stream, config)
61 }
62 AstNode::DefaultOption(_) => v1::expr::format_default_option(self, stream, config),
63 AstNode::DivisionExpr(_) => v1::expr::format_division_expr(self, stream, config),
64 AstNode::EqualityExpr(_) => v1::expr::format_equality_expr(self, stream, config),
65 AstNode::EnumDefinition(_) => {
66 v1::r#enum::format_enum_definition(self, stream, config)
67 }
68 AstNode::EnumTypeParameter(_) => {
69 v1::r#enum::format_enum_type_parameter(self, stream, config)
70 }
71 AstNode::EnumVariant(_) => v1::r#enum::format_enum_variant(self, stream, config),
72 AstNode::ExponentiationExpr(_) => {
73 v1::expr::format_exponentiation_expr(self, stream, config)
74 }
75 AstNode::GreaterEqualExpr(_) => {
76 v1::expr::format_greater_equal_expr(self, stream, config)
77 }
78 AstNode::GreaterExpr(_) => v1::expr::format_greater_expr(self, stream, config),
79 AstNode::IfExpr(_) => v1::expr::format_if_expr(self, stream, config),
80 AstNode::ImportAlias(_) => v1::import::format_import_alias(self, stream, config),
81 AstNode::ImportStatement(_) => {
82 v1::import::format_import_statement(self, stream, config)
83 }
84 AstNode::ImportMembers(_) => {
85 v1::import::format_import_members(self, stream, config)
86 }
87 AstNode::ImportMember(_) => v1::import::format_import_member(self, stream, config),
88 AstNode::SymbolicModulePath(_) => {
89 v1::import::format_symbolic_module_path(self, stream, config)
90 }
91 AstNode::IndexExpr(_) => v1::expr::format_index_expr(self, stream, config),
92 AstNode::InequalityExpr(_) => {
93 v1::expr::format_inequality_expr(self, stream, config)
94 }
95 AstNode::InputSection(_) => v1::format_input_section(self, stream, config),
96 AstNode::LessEqualExpr(_) => v1::expr::format_less_equal_expr(self, stream, config),
97 AstNode::LessExpr(_) => v1::expr::format_less_expr(self, stream, config),
98 AstNode::LiteralArray(_) => v1::expr::format_literal_array(self, stream, config),
99 AstNode::LiteralBoolean(_) => {
100 v1::expr::format_literal_boolean(self, stream, config)
101 }
102 AstNode::LiteralFloat(_) => v1::expr::format_literal_float(self, stream, config),
103 AstNode::LiteralHints(_) => v1::format_literal_hints(self, stream, config),
104 AstNode::LiteralHintsItem(_) => v1::format_literal_hints_item(self, stream, config),
105 AstNode::LiteralInput(_) => v1::format_literal_input(self, stream, config),
106 AstNode::LiteralInputItem(_) => v1::format_literal_input_item(self, stream, config),
107 AstNode::LiteralInteger(_) => {
108 v1::expr::format_literal_integer(self, stream, config)
109 }
110 AstNode::LiteralMap(_) => v1::expr::format_literal_map(self, stream, config),
111 AstNode::LiteralMapItem(_) => {
112 v1::expr::format_literal_map_item(self, stream, config)
113 }
114 AstNode::LiteralNone(_) => v1::expr::format_literal_none(self, stream, config),
115 AstNode::LiteralNull(_) => v1::meta::format_literal_null(self, stream, config),
116 AstNode::LiteralObject(_) => v1::expr::format_literal_object(self, stream, config),
117 AstNode::LiteralObjectItem(_) => {
118 v1::expr::format_literal_object_item(self, stream, config)
119 }
120 AstNode::LiteralOutput(_) => v1::format_literal_output(self, stream, config),
121 AstNode::LiteralOutputItem(_) => {
122 v1::format_literal_output_item(self, stream, config)
123 }
124 AstNode::LiteralPair(_) => v1::expr::format_literal_pair(self, stream, config),
125 AstNode::LiteralString(_) => v1::expr::format_literal_string(self, stream, config),
126 AstNode::LiteralStruct(_) => {
127 v1::r#struct::format_literal_struct(self, stream, config)
128 }
129 AstNode::LiteralStructItem(_) => {
130 v1::r#struct::format_literal_struct_item(self, stream, config)
131 }
132 AstNode::LogicalAndExpr(_) => {
133 v1::expr::format_logical_and_expr(self, stream, config)
134 }
135 AstNode::LogicalNotExpr(_) => {
136 v1::expr::format_logical_not_expr(self, stream, config)
137 }
138 AstNode::LogicalOrExpr(_) => v1::expr::format_logical_or_expr(self, stream, config),
139 AstNode::MapType(_) => v1::decl::format_map_type(self, stream, config),
140 AstNode::MetadataArray(_) => v1::meta::format_metadata_array(self, stream, config),
141 AstNode::MetadataObject(_) => {
142 v1::meta::format_metadata_object(self, stream, config)
143 }
144 AstNode::MetadataObjectItem(_) => {
145 v1::meta::format_metadata_object_item(self, stream, config)
146 }
147 AstNode::MetadataSection(_) => {
148 v1::meta::format_metadata_section(self, stream, config)
149 }
150 AstNode::ModuloExpr(_) => v1::expr::format_modulo_expr(self, stream, config),
151 AstNode::MultiplicationExpr(_) => {
152 v1::expr::format_multiplication_expr(self, stream, config)
153 }
154 AstNode::NameRefExpr(_) => v1::expr::format_name_ref_expr(self, stream, config),
155 AstNode::NegationExpr(_) => v1::expr::format_negation_expr(self, stream, config),
156 AstNode::OutputSection(_) => v1::format_output_section(self, stream, config),
157 AstNode::PairType(_) => v1::decl::format_pair_type(self, stream, config),
158 AstNode::ObjectType(_) => v1::decl::format_object_type(self, stream, config),
159 AstNode::ParameterMetadataSection(_) => {
160 v1::meta::format_parameter_metadata_section(self, stream, config)
161 }
162 AstNode::ParenthesizedExpr(_) => {
163 v1::expr::format_parenthesized_expr(self, stream, config)
164 }
165 AstNode::Placeholder(_) => v1::expr::format_placeholder(self, stream, config),
166 AstNode::PrimitiveType(_) => v1::decl::format_primitive_type(self, stream, config),
167 AstNode::RequirementsItem(_) => {
168 v1::task::format_requirements_item(self, stream, config)
169 }
170 AstNode::RequirementsSection(_) => {
171 v1::task::format_requirements_section(self, stream, config)
172 }
173 AstNode::RuntimeItem(_) => v1::task::format_runtime_item(self, stream, config),
174 AstNode::RuntimeSection(_) => {
175 v1::task::format_runtime_section(self, stream, config)
176 }
177 AstNode::ScatterStatement(_) => {
178 v1::workflow::format_scatter_statement(self, stream, config)
179 }
180 AstNode::SepOption(_) => v1::expr::format_sep_option(self, stream, config),
181 AstNode::StructDefinition(_) => {
182 v1::r#struct::format_struct_definition(self, stream, config)
183 }
184 AstNode::SubtractionExpr(_) => {
185 v1::expr::format_subtraction_expr(self, stream, config)
186 }
187 AstNode::TaskDefinition(_) => {
188 v1::task::format_task_definition(self, stream, config)
189 }
190 AstNode::TaskHintsItem(_) => v1::task::format_task_hints_item(self, stream, config),
191 AstNode::TaskHintsSection(_) => {
192 v1::task::format_task_hints_section(self, stream, config)
193 }
194 AstNode::TrueFalseOption(_) => {
195 v1::expr::format_true_false_option(self, stream, config)
196 }
197 AstNode::TypeRef(_) => v1::decl::format_type_ref(self, stream, config),
198 AstNode::UnboundDecl(_) => v1::decl::format_unbound_decl(self, stream, config),
199 AstNode::VersionStatement(_) => v1::format_version_statement(self, stream, config),
200 AstNode::WorkflowDefinition(_) => {
201 v1::workflow::format_workflow_definition(self, stream, config)
202 }
203 AstNode::WorkflowHintsArray(_) => {
204 v1::workflow::format_workflow_hints_array(self, stream, config)
205 }
206 AstNode::WorkflowHintsItem(_) => {
207 v1::workflow::format_workflow_hints_item(self, stream, config)
208 }
209 AstNode::WorkflowHintsObject(_) => {
210 v1::workflow::format_workflow_hints_object(self, stream, config)
211 }
212 AstNode::WorkflowHintsObjectItem(_) => {
213 v1::workflow::format_workflow_hints_object_item(self, stream, config)
214 }
215 AstNode::WorkflowHintsSection(_) => {
216 v1::workflow::format_workflow_hints_section(self, stream, config)
217 }
218 },
219 Element::Token(token) => {
220 stream.push_ast_token(token);
221 }
222 }
223 }
224}
225
226#[derive(Debug, Default)]
228pub struct Formatter {
229 config: Config,
231}
232
233impl Formatter {
234 pub fn new(config: Config) -> Self {
236 Self { config }
237 }
238
239 pub fn config(&self) -> &Config {
241 &self.config
242 }
243
244 pub fn format<W: Writable>(&self, element: W) -> std::result::Result<String, std::fmt::Error> {
246 let mut result = String::new();
247
248 for token in self.to_stream(element) {
249 write!(result, "{token}", token = token.display(self.config()))?;
250 }
251
252 Ok(result)
253 }
254
255 fn to_stream<W: Writable>(&self, element: W) -> TokenStream<PostToken> {
257 let mut stream = TokenStream::default();
258 element.write(&mut stream, self.config());
259
260 let mut postprocessor = Postprocessor::default();
261 postprocessor.run(stream, self.config())
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use wdl_ast::Document;
268 use wdl_ast::Node;
269
270 use crate::Formatter;
271 use crate::element::node::AstNodeFormatExt as _;
272
273 #[test]
274 fn smoke() {
275 let (document, diagnostics) = Document::parse(
276 "## WDL
277version 1.2 # This is a comment attached to the version.
278
279# This is a comment attached to the task keyword.
280task foo # This is an inline comment on the task ident.
281{
282
283} # This is an inline comment on the task close brace.
284
285# This is a comment attached to the workflow keyword.
286workflow bar # This is an inline comment on the workflow ident.
287{
288 # This is attached to the call keyword.
289 call foo {}
290} # This is an inline comment on the workflow close brace.",
291 None,
292 );
293
294 assert!(diagnostics.is_empty());
295 let document = Node::Ast(document.ast().into_v1().unwrap()).into_format_element();
296 let formatter = Formatter::default();
297 let result = formatter.format(&document);
298 match result {
299 Ok(s) => {
300 print!("{s}");
301 }
302 Err(err) => {
303 panic!("failed to format document: {err}");
304 }
305 }
306 }
307}