Skip to content
4 changes: 4 additions & 0 deletions compiler/plc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,10 @@ impl AstNode {
_ => None,
}
}

pub fn is_repeat(&self) -> bool {
matches!(self.stmt, AstStatement::ControlStatement(AstControlStatement::RepeatLoop(_)))
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand Down
39 changes: 37 additions & 2 deletions compiler/plc_ast/src/mut_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::ast::{
JumpStatement, MultipliedStatement, Pou, PropertyBlock, RangeStatement, ReferenceAccess, ReferenceExpr,
UnaryExpression, UserTypeDeclaration, Variable, VariableBlock,
};
use crate::control_statements::{AstControlStatement, ConditionalBlock, ReturnStatement};
use crate::control_statements::{
AstControlStatement, ConditionalBlock, ForLoopStatement, LoopStatement, ReturnStatement,
};
use crate::literals::AstLiteral;
use crate::try_from_mut;

Expand Down Expand Up @@ -180,7 +182,40 @@ pub trait AstVisitorMut: Sized {

fn visit_control_statement(&mut self, node: &mut AstNode) {
let stmt = try_from_mut!(node, AstControlStatement).expect("AstControlStatement");
stmt.walk(self)
match stmt {
AstControlStatement::ForLoop(for_stmt) => self.visit_for_loop_statement(for_stmt),
AstControlStatement::WhileLoop(loop_stmt) => self.visit_while_loop_statement(loop_stmt),
AstControlStatement::RepeatLoop(loop_stmt) => self.visit_repeat_loop_statement(loop_stmt),
_ => stmt.walk(self),
}
}

/// Visits a `ForLoop` control statement.
/// Make sure to visit the counter, bounds, optional step and body to continue the traversal.
/// # Arguments
/// * `stmt` - The unwraped, typed `ForLoopStatement` node to visit.
fn visit_for_loop_statement(&mut self, stmt: &mut ForLoopStatement) {
visit_nodes_mut!(self, &mut stmt.counter, &mut stmt.start, &mut stmt.end);
visit_all_nodes_mut!(self, &mut stmt.by_step);
self.visit_statement_list(&mut stmt.body);
}

/// Visits a `WhileLoop` control statement.
/// Make sure to visit the condition and body to continue the traversal.
/// # Arguments
/// * `stmt` - The unwraped, typed `LoopStatement` node to visit.
fn visit_while_loop_statement(&mut self, stmt: &mut LoopStatement) {
visit_nodes_mut!(self, &mut stmt.condition);
self.visit_statement_list(&mut stmt.body);
}

/// Visits a `RepeatLoop` control statement.
/// Make sure to visit the condition and body to continue the traversal.
/// # Arguments
/// * `stmt` - The unwraped, typed `LoopStatement` node to visit.
fn visit_repeat_loop_statement(&mut self, stmt: &mut LoopStatement) {
visit_nodes_mut!(self, &mut stmt.condition);
self.visit_statement_list(&mut stmt.body);
}

fn visit_case_condition(&mut self, node: &mut AstNode) {
Expand Down
43 changes: 40 additions & 3 deletions compiler/plc_ast/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::ast::{
MultipliedStatement, Pou, PropertyBlock, RangeStatement, ReferenceAccess, ReferenceExpr, UnaryExpression,
UserTypeDeclaration, Variable, VariableBlock,
};
use crate::control_statements::{AstControlStatement, ConditionalBlock, ReturnStatement};
use crate::control_statements::{
AstControlStatement, ConditionalBlock, ForLoopStatement, LoopStatement, ReturnStatement,
};
use crate::literals::AstLiteral;

/// Macro that calls the visitor's `visit` method for every AstNode in the passed iterator `iter`.
Expand Down Expand Up @@ -375,8 +377,43 @@ pub trait AstVisitor: Sized {
/// # Arguments
/// * `stmt` - The unwraped, typed `AstControlStatement` node to visit.
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
fn visit_control_statement(&mut self, stmt: &AstControlStatement, _node: &AstNode) {
stmt.walk(self)
fn visit_control_statement(&mut self, stmt: &AstControlStatement, node: &AstNode) {
match stmt {
AstControlStatement::WhileLoop(loop_stmt) => self.visit_while_loop_statement(loop_stmt, node),
AstControlStatement::RepeatLoop(loop_stmt) => self.visit_repeat_loop_statement(loop_stmt, node),
AstControlStatement::ForLoop(for_stmt) => self.visit_for_loop_statement(for_stmt, node),
_ => stmt.walk(self),
}
}

/// Visits a `ForLoop` control statement.
/// # Arguments
/// * `stmt` - The unwraped, typed `ForLoopStatement` node to visit.
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
fn visit_for_loop_statement(&mut self, stmt: &ForLoopStatement, _node: &AstNode) {
visit_nodes!(self, &stmt.counter, &stmt.start, &stmt.end);
visit_all_nodes!(self, &stmt.by_step);
self.visit_statement_list(&stmt.body);
}

/// Visits a `WhileLoop` control statement.
/// Make sure to call `walk` on the `LoopStatement` node to visit its children.
/// # Arguments
/// * `stmt` - The unwraped, typed `LoopStatement` node to visit.
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
fn visit_while_loop_statement(&mut self, stmt: &LoopStatement, _node: &AstNode) {
visit_nodes!(self, &stmt.condition);
self.visit_statement_list(&stmt.body);
}

/// Visits a `RepeatLoop` control statement.
/// Make sure to call `walk` on the `LoopStatement` node to visit its children.
/// # Arguments
/// * `stmt` - The unwraped, typed `LoopStatement` node to visit.
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
fn visit_repeat_loop_statement(&mut self, stmt: &LoopStatement, _node: &AstNode) {
visit_nodes!(self, &stmt.condition);
self.visit_statement_list(&stmt.body);
}

/// Visits a `CaseCondition` node.
Expand Down
3 changes: 2 additions & 1 deletion compiler/plc_driver/src/pipelines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use plc_header_generator::{
};
use plc_index::GlobalContext;
use plc_lowering::{
control_statement::ControlStatementParticipant, inheritance::InheritanceLowerer,
control_statement::ControlStatementParticipant, inheritance::InheritanceLowerer, loops::LoopDesugarer,
retain::RetainParticipant,
};
use project::{
Expand Down Expand Up @@ -337,6 +337,7 @@ impl<T: SourceContainer> BuildPipeline<T> {

// XXX: should we use a static array of participants?
let mut_participants: Vec<Box<dyn PipelineParticipantMut>> = vec![
Box::new(LoopDesugarer::new(self.context.provider())),
Box::new(PropertyLowerer::new(self.context.provider())),
Box::new(PolymorphismLowerer::new(
self.context.provider(),
Expand Down
21 changes: 14 additions & 7 deletions compiler/plc_driver/src/pipelines/participant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@ use plc::{
};
use plc_diagnostics::diagnostics::Diagnostic;
use plc_lowering::{
array_lowering,
retain::RetainParticipant,
{
control_statement::ControlStatementParticipant, inheritance::InheritanceLowerer,
initializer::Initializer,
},
array_lowering, control_statement::ControlStatementParticipant, inheritance::InheritanceLowerer,
initializer::Initializer, loops::LoopDesugarer, retain::RetainParticipant,
};
use project::{object::Object, project::LibraryInformation};
use source_code::SourceContainer;
Expand Down Expand Up @@ -353,7 +349,8 @@ impl PipelineParticipantMut for RetainParticipant {
fn post_index(&mut self, indexed_project: IndexedProject) -> IndexedProject {
let IndexedProject { mut project, index, .. } = indexed_project;
self.lower_retain(&mut project.units, index);
//Re-index

// Re-index
project.index(self.ids.clone())
}
}
Expand All @@ -362,6 +359,16 @@ impl PipelineParticipantMut for ControlStatementParticipant {
fn pre_index(&mut self, parsed_project: ParsedProject) -> ParsedProject {
let ParsedProject { mut units } = parsed_project;
self.lower_control_statements(&mut units);

ParsedProject { units }
}
}

impl PipelineParticipantMut for LoopDesugarer {
fn pre_index(&mut self, parsed_project: ParsedProject) -> ParsedProject {
let ParsedProject { mut units } = parsed_project;
self.desugar(&mut units);

ParsedProject { units }
}
}
136 changes: 62 additions & 74 deletions compiler/plc_lowering/src/control_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,27 @@ impl ControlStatementLowerer {
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use plc_ast::ser::AstSerializer;
use plc_driver::parse_and_annotate;
use plc_source::SourceCode;

fn serialize(source: impl Into<SourceCode>) -> String {
let (_, project) = parse_and_annotate("unit-test", vec![source.into()]).unwrap();
let unit = project.units[0].get_unit();

unit.implementations
.iter()
.find(|implementation| implementation.name == "mainProg")
.expect("mainProg implementation should exist")
.statements
.iter()
.map(|statement| statement.as_string())
.collect::<Vec<_>>()
.join("\n")
}

#[test]
fn elseif_is_lowered_to_else_with_nested_if() {
let src: SourceCode = r#"
let source = r#"
PROGRAM mainProg
VAR
val : INT;
Expand All @@ -192,20 +206,11 @@ mod tests {
cVar := 'x';
END_IF
END_PROGRAM
"#
.into();

let (_, project) = parse_and_annotate("test", vec![src]).unwrap();

let implementations = &project.units[0].get_unit().implementations;
let implementation = implementations
.iter()
.find(|i| i.name == "mainProg")
.expect("mainProg implementation should exist");
assert_eq!(implementation.name, "mainProg");
"#;

let control_statement = &implementation.statements[2];
assert_snapshot!(AstSerializer::format(control_statement), @"
assert_snapshot!(serialize(source), @"
val := 5
cVar := ''
IF val = 3 THEN
cVar := 'f'
ELSE
Expand All @@ -220,7 +225,7 @@ mod tests {

#[test]
fn elseif_is_lowered_to_else_with_nested_if_even_if_no_else_is_present() {
let src: SourceCode = r#"
let source = r#"
PROGRAM mainProg
VAR
val : INT;
Expand All @@ -238,20 +243,11 @@ mod tests {
cVar := 'b';
END_IF
END_PROGRAM
"#
.into();

let (_, project) = parse_and_annotate("test", vec![src]).unwrap();

let implementations = &project.units[0].get_unit().implementations;
let implementation = implementations
.iter()
.find(|i| i.name == "mainProg")
.expect("mainProg implementation should exist");
assert_eq!(implementation.name, "mainProg");
"#;

let control_statement = &implementation.statements[2];
assert_snapshot!(AstSerializer::format(control_statement), @"
assert_snapshot!(serialize(source), @"
val := 5
cVar := ''
IF val = 3 THEN
cVar := 'f'
ELSE
Expand All @@ -264,7 +260,7 @@ mod tests {

#[test]
fn elseif_is_lowered_to_else_with_nested_if_when_prenested_in_if() {
let src: SourceCode = r#"
let source = r#"
PROGRAM mainProg
VAR
val : INT;
Expand All @@ -286,20 +282,11 @@ mod tests {
END_IF
END_IF
END_PROGRAM
"#
.into();

let (_, project) = parse_and_annotate("test", vec![src]).unwrap();

let implementations = &project.units[0].get_unit().implementations;
let implementation = implementations
.iter()
.find(|i| i.name == "mainProg")
.expect("mainProg implementation should exist");
assert_eq!(implementation.name, "mainProg");
"#;

let control_statement = &implementation.statements[2];
assert_snapshot!(AstSerializer::format(control_statement), @"
assert_snapshot!(serialize(source), @"
val := 5
cVar := ''
IF val = 4 THEN
cVar := 'a'
ELSE
Expand All @@ -318,7 +305,7 @@ mod tests {

#[test]
fn elseif_is_lowered_to_else_with_nested_if_inside_for_loop() {
let src: SourceCode = r#"
let source = r#"
PROGRAM mainProg
VAR
i : INT;
Expand All @@ -339,21 +326,29 @@ mod tests {
END_IF
END_FOR
END_PROGRAM
"#
.into();

let (_, project) = parse_and_annotate("test", vec![src]).unwrap();

let implementations = &project.units[0].get_unit().implementations;
let implementation = implementations
.iter()
.find(|i| i.name == "mainProg")
.expect("mainProg implementation should exist");
assert_eq!(implementation.name, "mainProg");

let control_statement = &implementation.statements[2];
assert_snapshot!(AstSerializer::format(control_statement), @"
FOR i := 0 TO 10 DO
"#;

assert_snapshot!(serialize(source), @"
val := 5
cVar := ''
alloca ran_once_0: BOOL
alloca is_incrementing_0: BOOL
i := 0
is_incrementing_0 := TRUE
WHILE TRUE DO
IF ran_once_0 THEN
i := i + 1
END_IF
ran_once_0 := TRUE
IF is_incrementing_0 THEN
IF i > 10 THEN
EXIT;
END_IF
ELSE
IF i < 10 THEN
EXIT;
END_IF
END_IF
IF val = 3 THEN
cVar := 'f'
ELSE
Expand All @@ -363,13 +358,13 @@ mod tests {
cVar := 'x'
END_IF
END_IF
END_FOR
END_WHILE
");
}

#[test]
fn elseif_is_lowered_to_else_with_nested_if_inside_while_loop() {
let src: SourceCode = r#"
let source = r#"
PROGRAM mainProg
VAR
i : INT;
Expand Down Expand Up @@ -400,20 +395,13 @@ mod tests {
END_IF
END_WHILE
END_PROGRAM
"#
.into();

let (_, project) = parse_and_annotate("test", vec![src]).unwrap();

let implementations = &project.units[0].get_unit().implementations;
let implementation = implementations
.iter()
.find(|i| i.name == "mainProg")
.expect("mainProg implementation should exist");
assert_eq!(implementation.name, "mainProg");
"#;

let control_statement = &implementation.statements[4];
assert_snapshot!(AstSerializer::format(control_statement), @"
assert_snapshot!(serialize(source), @"
val := 5
cVar := ''
someCon := TRUE
breakOut := 0
WHILE TRUE DO
IF NOT someCon THEN
EXIT;
Expand Down
Loading
Loading