Skip to content

Commit 3690b1a

Browse files
authored
feat(cfc): Conditional Returns (#950)
1 parent b0c9db4 commit 3690b1a

File tree

42 files changed

+1382
-125
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1382
-125
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ insta = "1.31.0"
4646
pretty_assertions = "1.3.0"
4747
driver = { path = "./compiler/plc_driver/", package = "plc_driver" }
4848
project = { path = "./compiler/plc_project/", package = "plc_project" }
49+
plc_xml = { path = "./compiler/plc_xml" }
4950
serial_test = "*"
5051
tempfile = "3"
5152
encoding_rs.workspace = true

compiler/plc_ast/src/ast.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
1010
use crate::{
1111
control_statements::{
1212
AstControlStatement, CaseStatement, ConditionalBlock, ForLoopStatement, IfStatement, LoopStatement,
13+
ReturnStatement,
1314
},
1415
literals::{AstLiteral, StringValue},
1516
pre_processor,
@@ -614,7 +615,7 @@ pub enum AstStatement {
614615
CaseCondition(Box<AstNode>),
615616
ExitStatement(()),
616617
ContinueStatement(()),
617-
ReturnStatement(()),
618+
ReturnStatement(ReturnStatement),
618619
}
619620

620621
impl Debug for AstNode {
@@ -714,7 +715,9 @@ impl Debug for AstNode {
714715
AstStatement::CaseCondition(condition) => {
715716
f.debug_struct("CaseCondition").field("condition", condition).finish()
716717
}
717-
AstStatement::ReturnStatement(..) => f.debug_struct("ReturnStatement").finish(),
718+
AstStatement::ReturnStatement(ReturnStatement { condition }) => {
719+
f.debug_struct("ReturnStatement").field("condition", condition).finish()
720+
}
718721
AstStatement::ContinueStatement(..) => f.debug_struct("ContinueStatement").finish(),
719722
AstStatement::ExitStatement(..) => f.debug_struct("ExitStatement").finish(),
720723
AstStatement::CastStatement(CastStatement { target, type_name }) => {
@@ -1058,8 +1061,13 @@ impl AstFactory {
10581061
// AstStatement::EmptyStatement ( EmptyStatement {}, location, id }
10591062
}
10601063

1061-
pub fn create_return_statement(location: SourceLocation, id: AstId) -> AstNode {
1062-
AstNode { stmt: AstStatement::ReturnStatement(()), location, id }
1064+
pub fn create_return_statement(
1065+
condition: Option<AstNode>,
1066+
location: SourceLocation,
1067+
id: AstId,
1068+
) -> AstNode {
1069+
let condition = condition.map(Box::new);
1070+
AstNode { stmt: AstStatement::ReturnStatement(ReturnStatement { condition }), location, id }
10631071
}
10641072

10651073
pub fn create_exit_statement(location: SourceLocation, id: AstId) -> AstNode {
@@ -1210,8 +1218,7 @@ impl AstFactory {
12101218
}
12111219

12121220
/// creates a not-expression
1213-
pub fn create_not_expression(operator: AstNode, location: SourceLocation) -> AstNode {
1214-
let id = operator.get_id();
1221+
pub fn create_not_expression(operator: AstNode, location: SourceLocation, id: AstId) -> AstNode {
12151222
AstNode {
12161223
stmt: AstStatement::UnaryExpression(UnaryExpression {
12171224
value: Box::new(operator),

compiler/plc_ast/src/control_statements.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ pub struct ConditionalBlock {
4646
pub body: Vec<AstNode>,
4747
}
4848

49+
#[derive(Clone, PartialEq)]
50+
pub struct ReturnStatement {
51+
/// Indicates that the given condition must evaluate to true in order for the return to take place.
52+
/// Only used in CFC where the condition may be [`Some`] and [`None`] otherwise.
53+
pub condition: Option<Box<AstNode>>,
54+
}
55+
4956
impl Debug for ConditionalBlock {
5057
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5158
f.debug_struct("ConditionalBlock")

compiler/plc_diagnostics/src/diagnostics.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,33 @@ impl Diagnostic {
719719
}
720720
}
721721

722+
// CFC related diagnostics
723+
impl Diagnostic {
724+
pub fn empty_control_statement(range: SourceLocation) -> Diagnostic {
725+
Diagnostic::SemanticError {
726+
message: "Control statement has no connection".to_string(),
727+
range: vec![range],
728+
err_no: ErrNo::cfc__empty_control_statement,
729+
}
730+
}
731+
732+
pub fn undefined_node(local_id: usize, ref_local_id: usize, range: SourceLocation) -> Diagnostic {
733+
Diagnostic::SemanticError {
734+
message: format!("Node {local_id} is referencing a non-existing element with ID {ref_local_id}"),
735+
range: vec![range],
736+
err_no: ErrNo::cfc__undefined_node,
737+
}
738+
}
739+
740+
pub fn unexpected_nodes(range: SourceLocation) -> Diagnostic {
741+
Diagnostic::SemanticError {
742+
message: "Unexpected relationship between nodes".to_string(),
743+
range: vec![range],
744+
err_no: ErrNo::cfc__unexpected_node,
745+
}
746+
}
747+
}
748+
722749
#[cfg(test)]
723750
mod tests {
724751
use codespan_reporting::files::{Location, SimpleFile};

compiler/plc_diagnostics/src/errno.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ pub enum ErrNo {
8787
case__duplicate_condition,
8888
case__case_condition_outside_case_statement,
8989
case__invalid_case_condition,
90+
91+
// CFC related
92+
cfc__empty_control_statement,
93+
cfc__undefined_node,
94+
cfc__unexpected_node,
9095
}
9196

9297
impl Display for ErrNo {

compiler/plc_xml/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ indexmap = "1.9"
1313
html-escape = "0.2"
1414
plc = { path = "../..", package = "rusty" }
1515
ast = { path = "../plc_ast/", package = "plc_ast" }
16-
plc_diagnostics = { path = "../plc_diagnostics"}
17-
plc_source = { path = "../plc_source"}
16+
plc_diagnostics = { path = "../plc_diagnostics" }
17+
plc_source = { path = "../plc_source" }
1818

1919
[features]
2020
default = []

compiler/plc_xml/src/model/control.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,21 @@ impl Parseable for Control {
6565
loop {
6666
match reader.peek()? {
6767
Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() {
68-
b"negated" => attributes.extend(reader.attributes()?),
6968
b"connection" => attributes.extend(reader.attributes()?),
69+
70+
// As opposed to e.g. variables where the negation information is directly stored in its
71+
// attributes (e.g. `<inVariable negated="false" .../>`) return elements store their
72+
// negation information in a seperate nested element called `negated` with the form of
73+
// `<negated value="..."/>`.
74+
// Hence we search for a negate element and extract its information from their attributes.
75+
b"negated" => {
76+
let value = reader.attributes()?;
77+
attributes.insert(
78+
"negated".to_string(),
79+
(value.get_or_err("value")? == "true").to_string(),
80+
);
81+
}
82+
7083
_ => reader.consume()?,
7184
},
7285

compiler/plc_xml/src/serializer.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ macro_rules! declare_type_and_extend_if_needed {
145145
self
146146
}
147147

148+
pub fn with_local_id(mut self, value: &'static str) -> Self {
149+
self.0.attributes.push(("localId", value));
150+
self
151+
}
152+
153+
pub fn with_execution_order_id(mut self, value: &'static str) -> Self {
154+
self.0.attributes.push(("executionOrderId", value));
155+
self
156+
}
157+
148158
pub fn with_data(mut self, data: &'static str) -> Self {
149159
self.0.content = Content::Data(data);
150160
self
@@ -210,7 +220,8 @@ declare_type_and_extend_if_needed! {
210220
(XInVariable, with_in_variable),
211221
(XOutVariable, with_out_variable),
212222
(XContinuation, with_continuation),
213-
(XConnector, with_connector)
223+
(XConnector, with_connector),
224+
(XReturn, with_return)
214225
),
215226
(
216227
XVariable, "variable",
@@ -253,7 +264,8 @@ declare_type_and_extend_if_needed! {
253264
),
254265
(
255266
XData, "data",
256-
(XTextDeclaration, with_text_declaration)
267+
(XTextDeclaration, with_text_declaration),
268+
(XNegated, with_negated_field)
257269
),
258270
(
259271
XTextDeclaration, "textDeclaration",
@@ -277,6 +289,14 @@ declare_type_and_extend_if_needed! {
277289
(XPosition, with_position),
278290
(XConnectionPointIn, with_connection_point_in)
279291
),
292+
(
293+
XReturn, "return",
294+
(XConnectionPointIn, with_connection_point_in),
295+
(XAddData, with_add_data)
296+
),
297+
(
298+
XNegated, "negated",
299+
),
280300
}
281301

282302
#[cfg(test)]
@@ -285,8 +305,8 @@ mod tests {
285305
use crate::serializer::{XConnection, XConnectionPointIn, XConnectionPointOut, XRelPosition};
286306

287307
use super::{
288-
XAddData, XBlock, XContent, XData, XInVariable, XInterface, XOutVariable, XPou, XTextDeclaration,
289-
XVariable,
308+
XAddData, XBlock, XContent, XData, XInVariable, XInterface, XNegated, XOutVariable, XPou,
309+
XTextDeclaration, XVariable,
290310
};
291311

292312
// convenience methods to reduce amount of boiler-plate-code
@@ -376,4 +396,12 @@ mod tests {
376396
))
377397
}
378398
}
399+
400+
impl XAddData {
401+
pub(crate) fn negated(is_negated: bool) -> Self {
402+
XAddData::new().with_data_data(XData::new().with_negated_field(
403+
XNegated::new().with_attribute("value", if is_negated { "true" } else { "false" }),
404+
))
405+
}
406+
}
379407
}

compiler/plc_xml/src/xml_parser.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919

2020
mod action;
2121
mod block;
22+
mod control;
2223
mod fbd;
2324
mod pou;
2425
#[cfg(test)]
@@ -60,24 +61,28 @@ fn parse(
6061
linkage: LinkageType,
6162
id_provider: IdProvider,
6263
) -> (CompilationUnit, Vec<Diagnostic>) {
63-
// transform the xml file to a data model.
64+
// Transform the xml file to a data model.
6465
// XXX: consecutive call-statements are nested in a single ast-statement. this will be broken up with temporary variables in the future
65-
let Ok(project) = visit(&source.source) else {
66-
todo!("cfc errors need to be transformed into diagnostics")
66+
let project = match visit(&source.source) {
67+
Ok(project) => project,
68+
Err(why) => todo!("cfc errors need to be transformed into diagnostics; {why:?}"),
6769
};
6870

69-
// create a new parse session
71+
// Create a new parse session
7072
let source_location_factory = SourceLocationFactory::for_source(source);
7173
let parser =
7274
ParseSession::new(&project, source.get_location_str(), id_provider, linkage, source_location_factory);
7375

74-
// try to parse a declaration data field
75-
let Some((unit, diagnostics)) = parser.try_parse_declaration() else {
76+
// Parse the declaration data field
77+
let Some((unit, mut diagnostics)) = parser.try_parse_declaration() else {
7678
unimplemented!("XML schemas without text declarations are not yet supported")
7779
};
7880

79-
// transform the data model into rusty AST statements and add them to the compilation unit
80-
(unit.with_implementations(parser.parse_model()), diagnostics)
81+
// Transform the data-model into an AST
82+
let (implementations, parser_diagnostics) = parser.parse_model();
83+
diagnostics.extend(parser_diagnostics);
84+
85+
(unit.with_implementations(implementations), diagnostics)
8186
}
8287

8388
pub(crate) struct ParseSession<'parse> {
@@ -86,6 +91,7 @@ pub(crate) struct ParseSession<'parse> {
8691
linkage: LinkageType,
8792
file_name: &'static str,
8893
range_factory: SourceLocationFactory,
94+
diagnostics: Vec<Diagnostic>,
8995
}
9096

9197
impl<'parse> ParseSession<'parse> {
@@ -96,7 +102,7 @@ impl<'parse> ParseSession<'parse> {
96102
linkage: LinkageType,
97103
range_factory: SourceLocationFactory,
98104
) -> Self {
99-
ParseSession { project, id_provider, linkage, file_name, range_factory }
105+
ParseSession { project, id_provider, linkage, file_name, range_factory, diagnostics: Vec::new() }
100106
}
101107

102108
/// parse the compilation unit from the addData field
@@ -130,15 +136,16 @@ impl<'parse> ParseSession<'parse> {
130136
exp
131137
}
132138

133-
fn parse_model(&self) -> Vec<Implementation> {
139+
fn parse_model(mut self) -> (Vec<Implementation>, Vec<Diagnostic>) {
134140
let mut implementations = vec![];
135141
for pou in &self.project.pous {
136142
// transform body
137-
implementations.push(pou.build_implementation(self));
143+
implementations.push(pou.build_implementation(&mut self));
138144
// transform actions
139-
pou.actions.iter().for_each(|action| implementations.push(action.build_implementation(self)));
145+
pou.actions.iter().for_each(|action| implementations.push(action.build_implementation(&self)));
140146
}
141-
implementations
147+
148+
(implementations, self.diagnostics)
142149
}
143150

144151
fn next_id(&self) -> AstId {

0 commit comments

Comments
 (0)