1use crate::{
2 ast::{
3 Allocation, ArgumentProperty, Assignment, AstNode, AstStatement, AutoDerefType, BinaryExpression,
4 CallStatement, CompilationUnit, ConfigVariable, DataType, DataTypeDeclaration, DefaultValue,
5 DirectAccess, EmptyStatement, HardwareAccess, Implementation, Interface, JumpStatement,
6 LabelStatement, MultipliedStatement, Pou, PouType, PropertyBlock, RangeStatement, ReferenceAccess,
7 ReferenceExpr, UnaryExpression, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType,
8 },
9 control_statements::{AstControlStatement, ReturnStatement},
10 literals::AstLiteral,
11 visitor::{AstVisitor, Walker},
12};
13
14pub struct AstSerializer<'a> {
15 result: String,
16 indent: usize,
17 unit: Option<&'a CompilationUnit>,
18 user_type_context: Option<&'a UserTypeDeclaration>,
19 is_in_paren: bool,
20}
21
22impl AstSerializer<'_> {
23 pub fn format(node: &AstNode) -> String {
24 let mut serializer = AstSerializer {
25 result: String::new(),
26 indent: 0,
27 unit: None,
28 user_type_context: None,
29 is_in_paren: false,
30 };
31 serializer.visit(node);
32
33 serializer.result
34 }
35
36 pub fn from_unit(unit: &CompilationUnit) -> String {
39 let mut serializer = AstSerializer {
40 result: String::new(),
41 indent: 0,
42 unit: Some(unit),
43 user_type_context: None,
44 is_in_paren: false,
45 };
46 serializer.visit_compilation_unit(unit);
47
48 serializer.result
49 }
50
51 pub fn format_nodes(nodes: &[AstNode]) -> String {
52 let mut serializer = AstSerializer {
53 result: String::new(),
54 indent: 0,
55 unit: None,
56 user_type_context: None,
57 is_in_paren: false,
58 };
59
60 let nodes = nodes.iter().filter(|node| !node.is_empty_statement());
61
62 for (index, node) in nodes.enumerate() {
63 if index > 0 {
64 serializer.result.push('\n');
65 }
66 serializer.visit(node);
67
68 if !node.is_expression_list() {
70 serializer.result.push(';');
71 }
72 }
73
74 serializer.result
75 }
76
77 pub fn format_variable_block(variable_block: &VariableBlock, unit: &CompilationUnit) -> String {
78 let mut serializer = AstSerializer {
79 result: String::new(),
80 indent: 0,
81 unit: Some(unit),
82 user_type_context: None,
83 is_in_paren: false,
84 };
85 serializer.visit_variable_block(variable_block);
86
87 serializer.result
88 }
89
90 fn serialize_statement_list(&mut self, stmts: &[AstNode]) {
92 self.indent += 1;
93 for stmt in stmts {
94 self.push_indent();
95 stmt.walk(self);
96 }
97 self.indent -= 1;
98 }
99
100 fn push_indent(&mut self) {
102 self.result.push('\n');
103 for _ in 0..self.indent {
104 self.result.push_str(" ");
105 }
106 }
107
108 fn set_user_type_declaration_context(&mut self, type_name: &str) {
109 let Some(unit) = self.unit else {
110 panic!("cannot retrieve user type declaration without a compilation unit")
111 };
112
113 self.user_type_context =
114 unit.user_types.iter().find(|it| it.data_type.get_name().is_some_and(|name| name == type_name));
115 }
116}
117
118fn pou_keywords(kind: &PouType) -> (&'static str, &'static str) {
119 match kind {
120 PouType::Program => ("PROGRAM", "END_PROGRAM"),
121 PouType::FunctionBlock => ("FUNCTION_BLOCK", "END_FUNCTION_BLOCK"),
122 PouType::Class => ("CLASS", "END_CLASS"),
123 PouType::Action => ("ACTION", "END_ACTION"),
124 _ => ("FUNCTION", "END_FUNCTION"),
125 }
126}
127
128impl AstVisitor for AstSerializer<'_> {
129 fn visit(&mut self, node: &AstNode) {
130 node.walk(self)
131 }
132
133 fn visit_compilation_unit(&mut self, unit: &CompilationUnit) {
134 for (index, pou) in unit.pous.iter().enumerate() {
135 if index > 0 {
136 self.result.push_str("\n\n");
137 }
138 self.visit_pou(pou);
139 if let Some(implementation) = unit.implementations.iter().find(|it| it.name == pou.name) {
140 self.visit_implementation(implementation);
141 }
142 self.result.push('\n');
143 self.result.push_str(pou_keywords(&pou.kind).1);
144 }
145 }
146
147 fn visit_implementation(&mut self, implementation: &Implementation) {
148 self.indent += 1;
149 for statement in &implementation.statements {
150 if statement.is_empty_statement() {
151 continue;
152 }
153 self.push_indent();
154 statement.walk(self);
155 if !statement.is_expression_list() && statement.get_label_name().is_none() {
157 self.result.push(';');
158 }
159 }
160 self.indent -= 1;
161 }
162
163 fn visit_variable_block(&mut self, variable_block: &VariableBlock) {
164 let var_start = match variable_block.kind {
165 VariableBlockType::InOut => "VAR_IN_OUT",
166 VariableBlockType::Input(ArgumentProperty::ByVal) => "VAR_INPUT",
167 VariableBlockType::Input(ArgumentProperty::ByRef) => "VAR_INPUT {ref}",
168 VariableBlockType::Output => "VAR_OUTPUT",
169 VariableBlockType::Temp => "VAR_TEMP",
170 VariableBlockType::Global => "VAR_GLOBAL",
171 _ => "VAR",
172 };
173 let var_end: &str = "END_VAR";
174
175 self.result.push_str(var_start);
176 self.indent += 1;
177 variable_block.variables.iter().for_each(|v| {
178 self.push_indent();
179 self.result.push_str(&format!("{} : ", v.name));
180 self.visit_variable(v);
181 self.result.push(';');
182 });
183 self.indent -= 1;
184 self.result.push_str(&format!("\n{var_end}"));
185 }
186
187 fn visit_variable(&mut self, variable: &Variable) {
188 self.visit_data_type_declaration(&variable.data_type_declaration);
189
190 if let Some(initializer) = &variable.initializer {
191 self.result.push_str(" := ");
192 initializer.walk(self);
193 }
194 }
195
196 fn visit_config_variable(&mut self, _: &ConfigVariable) {
197 unimplemented!("for now only interested in individual nodes located in a POU body")
198 }
199
200 fn visit_interface(&mut self, _: &Interface) {
201 unimplemented!("for now only interested in individual nodes located in a POU body")
202 }
203
204 fn visit_property(&mut self, _: &PropertyBlock) {
205 unimplemented!("for now only interested in individual nodes located in a POU body")
206 }
207
208 fn visit_enum_element(&mut self, element: &AstNode) {
209 element.walk(self);
210 }
211
212 fn visit_data_type_declaration(&mut self, data_type_declaration: &DataTypeDeclaration) {
213 match data_type_declaration {
214 DataTypeDeclaration::Reference { referenced_type, .. } => {
215 self.set_user_type_declaration_context(referenced_type);
216
217 if let Some(user_type_declaration) = self.user_type_context {
218 self.visit_user_type_declaration(user_type_declaration);
219 } else {
220 self.result.push_str(referenced_type);
221 }
222 }
223 DataTypeDeclaration::Definition { data_type, .. } => {
224 self.visit_data_type(data_type);
225 }
226 DataTypeDeclaration::Aggregate { referenced_type, .. } => {
227 self.result.push_str(referenced_type);
228 }
229 }
230 }
231
232 fn visit_user_type_declaration(&mut self, user_type_declaration: &UserTypeDeclaration) {
233 self.visit_data_type(&user_type_declaration.data_type);
234 }
235
236 fn visit_data_type(&mut self, data_type: &DataType) {
237 match data_type {
238 DataType::PointerType { referenced_type, auto_deref, type_safe, .. } => {
239 match auto_deref {
240 Some(AutoDerefType::Reference) => {
241 self.result.push_str("REFERENCE TO ");
242 }
243 Some(AutoDerefType::Alias) | Some(AutoDerefType::Default) => (),
245 _ => {
246 if *type_safe {
247 self.result.push_str("REF_TO ");
248 } else {
249 self.result.push_str("POINTER TO ");
250 }
251 }
252 }
253
254 self.visit_data_type_declaration(referenced_type.as_ref());
255 }
256 DataType::StructType { name: Some(name), .. } => {
257 self.result.push_str(name);
258 }
259 _ => (),
261 }
262 }
263
264 fn visit_pou(&mut self, pou: &Pou) {
265 self.result.push_str(pou_keywords(&pou.kind).0);
266 self.result.push(' ');
267 self.result.push_str(&pou.name);
268 if let Some(return_type) = &pou.return_type {
269 self.result.push_str(" : ");
270 self.visit_data_type_declaration(return_type);
271 }
272 for variable_block in &pou.variable_blocks {
273 self.result.push('\n');
274 self.visit_variable_block(variable_block);
275 }
276 }
277
278 fn visit_empty_statement(&mut self, _stmt: &EmptyStatement, _node: &AstNode) {}
279
280 fn visit_default_value(&mut self, _stmt: &DefaultValue, _node: &AstNode) {}
281
282 fn visit_literal(&mut self, stmt: &AstLiteral, _node: &AstNode) {
283 use crate::literals::AstLiteral;
284 match stmt {
285 AstLiteral::Integer(value) => self.result.push_str(&value.to_string()),
286 AstLiteral::Real(value) => self.result.push_str(value),
287 AstLiteral::Bool(value) => self.result.push_str(&value.to_string().to_uppercase()),
288 AstLiteral::String(string_value) => {
289 if string_value.is_wide {
290 self.result.push_str(&format!("\"{}\"", string_value.value));
291 } else {
292 self.result.push_str(&format!("'{}'", string_value.value));
293 }
294 }
295 AstLiteral::Null => self.result.push_str("NULL"),
296 _ => stmt.walk(self), }
298 }
299
300 fn visit_multiplied_statement(&mut self, stmt: &MultipliedStatement, _node: &AstNode) {
301 stmt.walk(self)
302 }
303
304 fn visit_reference_expr(&mut self, stmt: &ReferenceExpr, _node: &AstNode) {
305 if let Some(base) = &stmt.base {
306 base.walk(self);
307 }
308
309 match &stmt.access {
310 ReferenceAccess::Global(reference) => {
311 self.result.push('.');
312 reference.walk(self);
313 }
314 ReferenceAccess::Member(reference) => {
315 if stmt.base.is_some() {
316 self.result.push('.');
317 }
318 reference.walk(self);
319 }
320 ReferenceAccess::Index(index) => {
321 self.result.push('[');
322 self.is_in_paren = true;
323 index.walk(self);
324 self.is_in_paren = false;
325 self.result.push(']');
326 }
327 ReferenceAccess::Cast(reference) => {
328 self.result.push('#');
329 reference.walk(self);
330 }
331 ReferenceAccess::Deref => {
332 self.result.push('^');
333 }
334 ReferenceAccess::Address => {
335 self.result.insert_str(0, "ADR(");
336 self.result.push(')');
337 }
338 }
339 }
340
341 fn visit_identifier(&mut self, stmt: &str, _node: &AstNode) {
342 self.result.push_str(stmt);
343 }
344
345 fn visit_direct_access(&mut self, stmt: &DirectAccess, _node: &AstNode) {
346 stmt.walk(self)
347 }
348
349 fn visit_hardware_access(&mut self, stmt: &HardwareAccess, _node: &AstNode) {
350 stmt.walk(self)
351 }
352
353 fn visit_binary_expression(&mut self, stmt: &BinaryExpression, _node: &AstNode) {
354 stmt.left.walk(self);
355 self.result.push(' ');
356 self.result.push_str(&stmt.operator.to_string());
357 self.result.push(' ');
358 stmt.right.walk(self);
359 }
360
361 fn visit_unary_expression(&mut self, stmt: &UnaryExpression, _node: &AstNode) {
362 let op = stmt.operator.to_string();
363 self.result.push_str(&op);
364 if op.chars().next().is_some_and(|c| c.is_alphabetic()) {
366 self.result.push(' ');
367 }
368 stmt.value.walk(self);
369 }
370
371 fn visit_expression_list(&mut self, stmt: &Vec<AstNode>, _node: &AstNode) {
372 let len = stmt.iter().filter(|stmt| !stmt.is_empty_statement()).count();
373 let stmt = stmt.iter().filter(|stmt| !stmt.is_empty_statement());
374 if self.is_in_paren {
375 for (i, node) in stmt.enumerate() {
376 if i > 0 {
377 self.result.push_str(", ");
378 }
379 node.walk(self);
380 }
381 } else {
382 for (i, node) in stmt.enumerate() {
383 node.walk(self);
384 self.result.push(';');
385 if i != len - 1 {
386 self.push_indent();
387 }
388 }
389 }
390 }
391
392 fn visit_paren_expression(&mut self, inner: &AstNode, _node: &AstNode) {
393 self.result.push('(');
394 self.is_in_paren = true;
395 inner.walk(self);
396 self.is_in_paren = false;
397 self.result.push(')');
398 }
399
400 fn visit_range_statement(&mut self, stmt: &RangeStatement, _node: &AstNode) {
401 stmt.walk(self)
402 }
403
404 fn visit_vla_range_statement(&mut self, _node: &AstNode) {}
405
406 fn visit_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
407 stmt.left.walk(self);
408 self.result.push_str(" := ");
409 stmt.right.walk(self);
410 }
411
412 fn visit_output_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
413 stmt.left.walk(self);
414 self.result.push_str(" => ");
415 stmt.right.walk(self);
416 }
417
418 fn visit_ref_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
419 stmt.left.walk(self);
420 self.result.push_str(" REF= ");
421 stmt.right.walk(self);
422 }
423
424 fn visit_call_statement(&mut self, stmt: &CallStatement, _node: &AstNode) {
425 stmt.operator.walk(self);
426 self.result.push('(');
427 self.is_in_paren = true;
428 if let Some(opt) = stmt.parameters.as_ref() {
429 opt.walk(self)
430 }
431 self.is_in_paren = false;
432 self.result.push(')');
433 }
434
435 fn visit_control_statement(&mut self, stmt: &AstControlStatement, _node: &AstNode) {
436 match stmt {
437 AstControlStatement::If(if_stmt) => {
438 for (i, block) in if_stmt.blocks.iter().enumerate() {
439 if i == 0 {
440 self.result.push_str("IF ");
441 } else {
442 self.push_indent();
443 self.result.push_str("ELSIF ");
444 }
445 block.condition.walk(self);
446 self.result.push_str(" THEN");
447 self.serialize_statement_list(&block.body);
448 }
449 if !if_stmt.else_block.is_empty() {
450 self.push_indent();
451 self.result.push_str("ELSE");
452 self.serialize_statement_list(&if_stmt.else_block);
453 }
454 self.push_indent();
455 self.result.push_str("END_IF");
456 }
457 AstControlStatement::ForLoop(for_stmt) => {
458 self.result.push_str("FOR ");
459 for_stmt.counter.walk(self);
460 self.result.push_str(" := ");
461 for_stmt.start.walk(self);
462 self.result.push_str(" TO ");
463 for_stmt.end.walk(self);
464 if let Some(step) = &for_stmt.by_step {
465 self.result.push_str(" BY ");
466 step.walk(self);
467 }
468 self.result.push_str(" DO");
469 self.serialize_statement_list(&for_stmt.body);
470 self.push_indent();
471 self.result.push_str("END_FOR");
472 }
473 AstControlStatement::WhileLoop(loop_stmt) => {
474 self.result.push_str("WHILE ");
475 loop_stmt.condition.walk(self);
476 self.result.push_str(" DO");
477 self.serialize_statement_list(&loop_stmt.body);
478 self.push_indent();
479 self.result.push_str("END_WHILE");
480 }
481 AstControlStatement::RepeatLoop(loop_stmt) => {
482 self.result.push_str("REPEAT");
483 self.serialize_statement_list(&loop_stmt.body);
484 self.push_indent();
485 self.result.push_str("UNTIL ");
486 loop_stmt.condition.walk(self);
487 self.push_indent();
488 self.result.push_str("END_REPEAT");
489 }
490 AstControlStatement::Case(case_stmt) => {
491 self.result.push_str("CASE ");
492 case_stmt.selector.walk(self);
493 self.result.push_str(" OF");
494 self.indent += 1;
495 for block in &case_stmt.case_blocks {
496 self.push_indent();
497 block.condition.walk(self);
498 self.result.push(':');
499 self.serialize_statement_list(&block.body);
500 }
501 if !case_stmt.else_block.is_empty() {
502 self.push_indent();
503 self.result.push_str("ELSE");
504 self.serialize_statement_list(&case_stmt.else_block);
505 }
506 self.indent -= 1;
507 self.push_indent();
508 self.result.push_str("END_CASE");
509 }
510 }
511 }
512
513 fn visit_case_condition(&mut self, child: &AstNode, _node: &AstNode) {
514 child.walk(self)
515 }
516
517 fn visit_exit_statement(&mut self, _node: &AstNode) {
518 self.result.push_str("EXIT;");
519 }
520
521 fn visit_continue_statement(&mut self, _node: &AstNode) {
522 self.result.push_str("CONTINUE;");
523 }
524
525 fn visit_return_statement(&mut self, stmt: &ReturnStatement, _node: &AstNode) {
526 if stmt.condition.is_some() {
529 self.result.push_str("IF ");
530 stmt.walk(self);
531 self.result.push_str(" THEN RETURN; END_IF");
532 } else {
533 self.result.push_str("RETURN");
534 }
535 }
536
537 fn visit_jump_statement(&mut self, stmt: &JumpStatement, _node: &AstNode) {
538 self.result.push_str("IF ");
541 stmt.condition.walk(self);
542 self.result.push_str(" THEN GOTO ");
543 stmt.target.walk(self);
544 }
545
546 fn visit_label_statement(&mut self, stmt: &LabelStatement, _node: &AstNode) {
547 self.result.push_str("LABEL: ");
548 self.result.push_str(&stmt.name);
549 }
550
551 fn visit_allocation(&mut self, stmt: &Allocation, _node: &AstNode) {
552 self.result.push_str(&format!("alloca {}: {}", stmt.name, stmt.reference_type));
553 }
554
555 fn visit_super(&mut self, _stmt: &AstStatement, _node: &AstNode) {
556 self.result.push_str("SUPER");
557 }
558
559 fn visit_this(&mut self, _stmt: &AstStatement, _node: &AstNode) {
560 self.result.push_str("THIS");
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::ast::AstFactory;
568 use crate::literals::{AstLiteral, StringValue};
569 use plc_source::source_location::SourceLocation;
570
571 #[test]
572 fn expression_list() {
573 let function_name = AstFactory::create_identifier("foo", SourceLocation::undefined(), 0);
574 let expressions = vec![
575 AstFactory::create_literal(AstLiteral::Integer(1), SourceLocation::undefined(), 1),
576 AstFactory::create_literal(
577 AstLiteral::String(StringValue { value: "two".to_string(), is_wide: false }),
578 SourceLocation::undefined(),
579 2,
580 ),
581 AstFactory::create_literal(AstLiteral::Integer(3), SourceLocation::undefined(), 3),
582 AstFactory::create_literal(
583 AstLiteral::String(StringValue { value: "four".to_string(), is_wide: false }),
584 SourceLocation::undefined(),
585 4,
586 ),
587 ];
588 let expression_list = AstFactory::create_expression_list(expressions, SourceLocation::undefined(), 5);
589 let call = AstFactory::create_call_statement(
590 function_name,
591 Some(expression_list),
592 6,
593 SourceLocation::undefined(),
594 );
595
596 let result = AstSerializer::format(&call);
597 assert_eq!(result, "foo(1, 'two', 3, 'four')");
598 }
599
600 #[test]
601 fn variable_with_initializer() {
602 let variable = Variable {
603 name: "foo".to_string(),
604 data_type_declaration: DataTypeDeclaration::reference("DINT", SourceLocation::undefined()),
605 initializer: Some(AstFactory::create_literal(
606 AstLiteral::Integer(3),
607 SourceLocation::undefined(),
608 0,
609 )),
610 address: None,
611 location: SourceLocation::undefined(),
612 };
613 let block = VariableBlock::default().with_variables(vec![variable]);
614
615 let unit = CompilationUnit::new("<test>");
616 let result = AstSerializer::format_variable_block(&block, &unit);
617 assert_eq!(result, "VAR\n foo : DINT := 3;\nEND_VAR");
618 }
619}