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