1use core::ops::Range;
2
3use crate::ast::expressions::{DatexExpression, VariableAccess};
4use crate::ast::type_expressions::{
5 CallableTypeExpression, TypeExpression, TypeExpressionData,
6 TypeVariantAccess,
7};
8use crate::parser::ParserOptions;
9use crate::{
10 compiler::precompiler::precompiled_ast::RichAst,
11 compiler::{CompileOptions, parse_datex_script_to_rich_ast_simple_error},
12 fmt::options::{FormattingOptions, TypeDeclarationFormatting},
13 global::operators::{BinaryOperator, ComparisonOperator, UnaryOperator},
14 libs::core::CoreLibPointerId,
15};
16use pretty::{DocAllocator, DocBuilder, RcAllocator, RcDoc};
17
18mod bracketing;
19mod formatting;
20pub mod options;
21
22pub type Format<'a> = DocBuilder<'a, RcAllocator, ()>;
23
24pub struct Formatter<'a> {
25 ast: RichAst,
26 script: &'a str,
27 options: FormattingOptions,
28 alloc: RcAllocator,
29}
30
31#[derive(Debug)]
32pub enum Operation<'a> {
34 Binary(&'a BinaryOperator),
35 Comparison(&'a ComparisonOperator),
36 Unary(&'a UnaryOperator),
37 Statements,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Assoc {
42 Left,
43 Right,
44 None,
45}
46
47pub struct ParentContext<'a> {
48 precedence: u8,
49 associativity: Assoc,
50 operation: Operation<'a>,
51}
52
53impl<'a> Formatter<'a> {
54 pub fn new(script: &'a str, options: FormattingOptions) -> Self {
55 let ast = parse_datex_script_to_rich_ast_simple_error(
56 script,
57 &mut CompileOptions {
58 parser_options: ParserOptions {
60 preserve_scoping: true,
61 },
62 ..Default::default()
63 },
64 )
65 .expect("Failed to parse Datex script");
66 Self {
67 ast,
68 script,
69 options,
70 alloc: RcAllocator,
71 }
72 }
73
74 fn tokens_at(&self, span: &Range<usize>) -> &'a str {
75 &self.script[span.start..span.end]
76 }
77
78 pub fn render(&self) -> String {
79 self.render_expression(&self.ast.ast)
80 }
81
82 fn render_expression(&self, expr: &DatexExpression) -> String {
84 self.format_datex_expression(expr)
85 .pretty(self.options.max_width)
86 .to_string()
87 }
88
89 fn indent(&self) -> isize {
91 self.options.indent as isize
92 }
93
94 fn format_datex_expression(
96 &'a self,
97 expr: &'a DatexExpression,
98 ) -> Format<'a> {
99 self.format_datex_expression_with_parent(expr, None, false)
100 }
101
102 fn format_datex_expression_with_parent(
104 &'a self,
105 expr: &'a DatexExpression,
106 parent_ctx: Option<ParentContext<'a>>,
107 is_left_child_of_parent: bool,
108 ) -> Format<'a> {
109 self.handle_bracketing(
110 expr,
111 self.datex_expression_to_source_code(expr),
112 parent_ctx,
113 is_left_child_of_parent,
114 )
115 }
116
117 fn wrap_in_parens(&'a self, doc: Format<'a>) -> Format<'a> {
119 let a = &self.alloc;
120 (a.text("(") + a.line_() + doc + a.line_() + a.text(")")).group()
121 }
122
123 fn format_type_expression(
125 &'a self,
126 type_expr: &'a TypeExpression,
127 ) -> Format<'a> {
128 let a = &self.alloc;
129 println!("formatting type expression: {:?}", type_expr);
130 match &type_expr.data {
131 TypeExpressionData::VariantAccess(TypeVariantAccess {
132 name,
133 variant,
134 ..
135 }) => a.text(format!("{}/{}", name, variant)),
136 TypeExpressionData::Integer(ti) => a.text(ti.to_string()),
137 TypeExpressionData::Decimal(td) => a.text(td.to_string()),
138 TypeExpressionData::Boolean(b) => a.text(b.to_string()),
139 TypeExpressionData::Text(t) => a.text(format!("{:?}", t)),
140 TypeExpressionData::Endpoint(ep) => a.text(ep.to_string()),
141 TypeExpressionData::Null => a.text("null"),
142 TypeExpressionData::Unit => a.text("()"),
143
144 TypeExpressionData::Ref(inner) => {
145 a.text("&") + self.format_type_expression(inner)
146 }
147 TypeExpressionData::RefMut(inner) => {
148 a.text("&mut") + a.space() + self.format_type_expression(inner)
149 }
150 TypeExpressionData::Identifier(lit) => a.text(lit.to_string()),
151 TypeExpressionData::VariableAccess(VariableAccess {
152 name, ..
153 }) => a.text(name.clone()),
154
155 TypeExpressionData::GetReference(ptr) => {
156 if let Ok(core_lib) = CoreLibPointerId::try_from(ptr) {
157 a.text(core_lib.to_string())
158 } else {
159 a.text(ptr.to_string())
160 }
161 }
162
163 TypeExpressionData::TypedInteger(typed_integer) => {
164 a.text(typed_integer.to_string())
165 }
167 TypeExpressionData::TypedDecimal(typed_decimal) => {
168 a.text(typed_decimal.to_string())
169 }
171
172 TypeExpressionData::StructuralList(elements) => {
174 let docs =
175 elements.0.iter().map(|e| self.format_type_expression(e));
176 self.wrap_collection(docs, ("[", "]"), ",")
177 }
178
179 TypeExpressionData::FixedSizeList(list) => {
180 core::todo!("#627 Undescribed by author.")
181 }
182 TypeExpressionData::SliceList(_) => {
183 core::todo!("#628 Undescribed by author.")
184 }
185
186 TypeExpressionData::Intersection(items) => {
188 self.wrap_type_collection(&items.0, "&")
189 }
190
191 TypeExpressionData::Union(items) => {
193 self.wrap_type_collection(&items.0, "|")
194 }
195
196 TypeExpressionData::GenericAccess(access) => {
197 core::todo!("#629 Undescribed by author.")
198 }
199
200 TypeExpressionData::Callable(CallableTypeExpression {
202 kind,
203 parameter_types,
204 rest_parameter_type,
205 return_type,
206 yeet_type,
207 }) => {
208 let params = parameter_types.iter().map(|(name, ty)| {
210 a.text(name.clone().unwrap_or_else(|| "_".to_string()))
211 + self.type_declaration_colon()
212 + self.format_type_expression(ty)
213 });
214 let params_doc =
215 RcDoc::intersperse(params, a.text(",") + a.space());
216 let arrow = self.operator_with_spaces(a.text("->"));
217 todo!("#631 Undescribed by author.")
218 }
219
220 TypeExpressionData::StructuralMap(items) => {
221 let pairs = items.0.iter().map(|(k, v)| {
222 let key_doc = self.format_type_expression(k);
223 key_doc
224 + self.type_declaration_colon()
225 + self.format_type_expression(v)
226 });
227 self.wrap_collection(pairs, ("{", "}"), ",")
228 }
229
230 TypeExpressionData::Recover => a.text("/*recover*/"),
231 }
232 }
233
234 fn wrap_type_collection(
236 &'a self,
237 list: &'a [TypeExpression],
238 op: &'a str,
239 ) -> Format<'a> {
240 let a = &self.alloc;
241
242 let op_doc = if self.options.spaces_around_operators {
244 a.softline() + a.text(op) + a.softline()
245 } else {
246 a.text(op)
247 };
248
249 let docs = list.iter().map(|expr| self.format_type_expression(expr));
251
252 a.nil().append(
254 RcDoc::intersperse(docs, op_doc).group().nest(self.indent()),
255 )
256 }
257
258 fn type_declaration_colon(&'a self) -> Format<'a> {
260 let a = &self.alloc;
261 match self.options.type_declaration_formatting {
262 TypeDeclarationFormatting::Compact => a.text(":"),
263 TypeDeclarationFormatting::SpaceAroundColon => {
264 a.space() + a.text(":") + a.space()
265 }
266 TypeDeclarationFormatting::SpaceAfterColon => {
267 a.text(":") + a.space()
268 }
269 }
270 }
271
272 fn operator_with_spaces(&'a self, text: Format<'a>) -> Format<'a> {
274 let a = &self.alloc;
275 if self.options.spaces_around_operators {
276 a.space() + text + a.space()
277 } else {
278 text
279 }
280 }
281
282 fn wrap_collection(
284 &'a self,
285 list: impl Iterator<Item = DocBuilder<'a, RcAllocator, ()>> + 'a,
286 brackets: (&'a str, &'a str),
287 sep: &'a str,
288 ) -> DocBuilder<'a, RcAllocator, ()> {
289 let a = &self.alloc;
290 let sep_doc = a.text(sep);
291
292 let padding = if self.options.spaced_collections {
294 a.line()
295 } else {
296 a.line_()
297 };
298
299 let separator = if self.options.space_in_collection {
301 sep_doc + a.line()
302 } else {
303 sep_doc + a.line_()
304 };
305
306 let joined = RcDoc::intersperse(list, separator).append(
307 if self.options.trailing_comma {
308 a.text(sep)
309 } else {
310 a.nil()
311 },
312 );
313
314 a.text(brackets.0)
315 .append((padding.clone() + joined).nest(self.indent()))
316 .append(padding)
317 .append(a.text(brackets.1))
318 .group()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use crate::fmt::options::VariantFormatting;
325
326 use super::*;
327 use crate::parser::Parser;
328 use indoc::indoc;
329
330 #[test]
331 fn ensure_unchanged() {
332 let script = "const x = {a: 1000000, b: [1,2,3,4,5,\"jfdjfsjdfjfsdjfdsjf\", 42, true, {a:1,b:3}], c: 123.456}; x";
333 let ast_original = Parser::parse_with_default_options(script).unwrap();
334 let formatted = to_string(script, FormattingOptions::default());
335 let ast_new = Parser::parse_with_default_options(&formatted).unwrap();
336 assert_eq!(ast_original, ast_new);
337 }
338
339 #[test]
340 #[ignore]
341 fn demo() {
342 let expr = "const x: &mut integer/u8 | text = {a: 1000000, b: [1,2,3,4,5,\"jfdjfsjdfjfsdjfdsjf\", 42, true, {a:1,b:3}], c: 123.456}; x";
343 print(expr, FormattingOptions::default());
344 print(expr, FormattingOptions::compact());
345
346 let expr = "const x = [1,2,3,4,5,6,7]";
347 print(expr, FormattingOptions::default());
348 }
349
350 #[test]
351 fn variant_formatting() {
352 let expr = "42u8";
353 assert_eq!(
354 to_string(
355 expr,
356 FormattingOptions {
357 variant_formatting: VariantFormatting::WithoutSuffix,
358 ..Default::default()
359 }
360 ),
361 "42"
362 );
363 assert_eq!(
364 to_string(
365 expr,
366 FormattingOptions {
367 variant_formatting: VariantFormatting::WithSuffix,
368 ..Default::default()
369 }
370 ),
371 "42u8"
372 );
373 assert_eq!(
374 to_string(
375 expr,
376 FormattingOptions {
377 variant_formatting: VariantFormatting::KeepAll,
378 ..Default::default()
379 }
380 ),
381 "42u8"
382 );
383 }
384
385 #[test]
386 fn statements() {
387 let expr = "1 + 2; var x: integer/u8 = 42; x * 10;";
388 assert_eq!(
389 to_string(expr, FormattingOptions::default()),
390 indoc! {"
391 1 + 2;
392 var x: integer/u8 = 42;
393 x * 10;"
394 }
395 );
396 assert_eq!(
397 to_string(expr, FormattingOptions::compact()),
398 "1+2;var x:integer/u8=42;x*10;"
399 );
400 }
401
402 #[test]
403 fn type_declarations() {
404 let expr = "type<&mut integer/u8>";
405 assert_eq!(
406 to_string(expr, FormattingOptions::default()),
407 "type<&mut integer/u8>"
408 );
409
410 let expr = "type<text | integer/u16 | decimal/f32>";
411 assert_eq!(
412 to_string(expr, FormattingOptions::default()),
413 "type<text | integer/u16 | decimal/f32>"
414 );
415 assert_eq!(
416 to_string(expr, FormattingOptions::compact()),
417 "type<text|integer/u16|decimal/f32>"
418 );
419 }
420
421 #[test]
422 fn variable_declaration() {
423 let expr = "var x: &mut integer/u8 = 42;";
424 assert_eq!(
425 to_string(expr, FormattingOptions::default()),
426 "var x: &mut integer/u8 = 42;"
427 );
428
429 assert_eq!(
430 to_string(expr, FormattingOptions::compact()),
431 "var x:&mut integer/u8=42;"
432 );
433 }
434
435 #[test]
436 fn binary_operations() {
437 let expr = "1 + 2 * 3 - 4 / 5";
438 assert_eq!(
439 to_string(expr, FormattingOptions::default()),
440 "1 + 2 * 3 - 4 / 5"
441 );
442 assert_eq!(to_string(expr, FormattingOptions::compact()), "1+2*3-4/5");
443 }
444
445 #[test]
446 fn text() {
447 let expr = r#""Hello, \"World\"!""#;
448 assert_eq!(
449 to_string(expr, FormattingOptions::default()),
450 r#""Hello, \"World\"!""#
451 );
452 }
453
454 #[test]
455 fn lists() {
456 let expr = "[1,2,3,4,5,6,7]";
458 assert_eq!(
459 to_string(
460 expr,
461 FormattingOptions {
462 max_width: 40,
463 space_in_collection: false,
464 trailing_comma: false,
465 spaced_collections: false,
466 ..Default::default()
467 }
468 ),
469 "[1,2,3,4,5,6,7]"
470 );
471
472 assert_eq!(
474 to_string(
475 expr,
476 FormattingOptions {
477 max_width: 40,
478 space_in_collection: true,
479 trailing_comma: false,
480 spaced_collections: false,
481 ..Default::default()
482 }
483 ),
484 "[1, 2, 3, 4, 5, 6, 7]"
485 );
486
487 assert_eq!(
489 to_string(
490 expr,
491 FormattingOptions {
492 max_width: 40,
493 space_in_collection: true,
494 trailing_comma: true,
495 spaced_collections: true,
496 ..Default::default()
497 }
498 ),
499 "[ 1, 2, 3, 4, 5, 6, 7, ]"
500 );
501
502 assert_eq!(
504 to_string(
505 expr,
506 FormattingOptions {
507 indent: 4,
508 max_width: 10,
509 space_in_collection: true,
510 trailing_comma: true,
511 spaced_collections: true,
512 ..Default::default()
513 }
514 ),
515 indoc! {"
516 [
517 1,
518 2,
519 3,
520 4,
521 5,
522 6,
523 7,
524 ]"}
525 );
526 }
527
528 fn to_string(script: &str, options: FormattingOptions) -> String {
529 let formatter = Formatter::new(script, options);
530 formatter.render()
531 }
532
533 fn print(script: &str, options: FormattingOptions) {
534 println!("{}", to_string(script, options));
535 }
536}