Skip to content

Commit 56b416b

Browse files
authored
feat: Action support in CFC (#981)
* refactor: Non peeking reader for xml Add a new parser, for now with `visit2` methods that does not require peeking * refactor: remove the Peekable reader and adjust tests The peekable reader is now fully removed, instead we have a wrapper around the original reader and auto conversion of errors * Add support to deserialize an xml with actions * Action codegen Actions can now be generated as ast and resolved. Added resolver and correctness test
1 parent ec4b34c commit 56b416b

File tree

51 files changed

+1677
-1081
lines changed

Some content is hidden

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

51 files changed

+1677
-1081
lines changed

compiler/plc_driver/src/runner.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ pub fn compile<T: Compilable>(context: &CodegenContext, source: T) -> GeneratedM
4747
pub fn compile_and_run<T, U, S: Compilable>(source: S, params: &mut T) -> U {
4848
let context: CodegenContext = CodegenContext::create();
4949
let module = compile(&context, source);
50+
module.print_to_stderr();
5051
module.run::<T, U>("main", params)
5152
}

compiler/plc_xml/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,9 @@ impl std::fmt::Debug for Error {
6060
}
6161
}
6262
}
63+
64+
impl From<quick_xml::Error> for Error {
65+
fn from(value: quick_xml::Error) -> Self {
66+
Error::ReadEvent(value)
67+
}
68+
}

compiler/plc_xml/src/model/action.rs

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,153 @@
11
use std::borrow::Cow;
22

3-
#[derive(Debug)]
3+
use quick_xml::events::Event;
4+
5+
use crate::{
6+
error::Error,
7+
reader::Reader,
8+
xml_parser::{get_attributes, Parseable},
9+
};
10+
11+
use super::body::Body;
12+
13+
#[derive(Debug, Default)]
414
pub(crate) struct Action<'xml> {
515
pub name: Cow<'xml, str>,
616
pub type_name: Cow<'xml, str>,
17+
pub body: Body<'xml>,
18+
}
19+
20+
impl Action<'_> {
21+
pub fn new(name: &str) -> Self {
22+
Action { name: name.to_string().into(), ..Default::default() }
23+
}
24+
}
25+
26+
impl Parseable for Vec<Action<'_>> {
27+
fn visit(
28+
reader: &mut Reader,
29+
_tag: Option<quick_xml::events::BytesStart>,
30+
) -> Result<Self, crate::error::Error> {
31+
let mut res = vec![];
32+
loop {
33+
match reader.read_event()? {
34+
Event::Start(tag) if tag.name().as_ref() == b"action" => {
35+
res.push(Action::visit(reader, Some(tag))?);
36+
}
37+
Event::End(tag) if tag.name().as_ref() == b"actions" => break,
38+
Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"actions"])),
39+
_ => {}
40+
}
41+
}
42+
43+
Ok(res)
44+
}
45+
}
46+
47+
impl Parseable for Action<'_> {
48+
fn visit(
49+
reader: &mut Reader,
50+
tag: Option<quick_xml::events::BytesStart>,
51+
) -> Result<Self, crate::error::Error> {
52+
let Some(tag) = tag else { unreachable!() };
53+
54+
let attributes = get_attributes(tag.attributes())?;
55+
let Some(name) = attributes.get("name") else { todo!() };
56+
let mut action = Action::new(name);
57+
loop {
58+
match reader.read_event()? {
59+
Event::Start(tag) if tag.name().as_ref() == b"body" => {
60+
action.body = Body::visit(reader, Some(tag))?;
61+
}
62+
Event::End(tag) if tag.name().as_ref() == b"action" => break,
63+
Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"actions"])),
64+
_ => {}
65+
}
66+
}
67+
68+
Ok(action)
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use insta::assert_debug_snapshot;
75+
76+
use crate::xml_parser;
77+
78+
#[test]
79+
fn list_of_actions_parsed_to_model() {
80+
let src = r###"
81+
<?xml version="1.0" encoding="UTF-8"?>
82+
<pou xmlns="http://www.plcopen.org/xml/tc6_0201" name="program_0" pouType="program">
83+
<interface>
84+
<localVars/>
85+
<addData>
86+
<data name="www.bachmann.at/plc/plcopenxml" handleUnknown="implementation">
87+
<textDeclaration>
88+
<content>
89+
PROGRAM program_0
90+
VAR
91+
a : DINT;
92+
END_VAR
93+
</content>
94+
</textDeclaration>
95+
</data>
96+
</addData>
97+
</interface>
98+
<actions>
99+
<action name="newAction">
100+
<body>
101+
<FBD>
102+
<block localId="1" width="80" height="60" typeName="foo" executionOrderId="0">
103+
<position x="420" y="110"/>
104+
<inputVariables>
105+
<variable formalParameter="IN1" negated="false">
106+
<connectionPointIn>
107+
<relPosition x="0" y="30"/>
108+
</connectionPointIn>
109+
</variable>
110+
<variable formalParameter="IN2" negated="false">
111+
<connectionPointIn>
112+
<relPosition x="0" y="50"/>
113+
</connectionPointIn>
114+
</variable>
115+
</inputVariables>
116+
<inOutVariables/>
117+
<outputVariables>
118+
<variable formalParameter="OUT" negated="false">
119+
<connectionPointOut>
120+
<relPosition x="80" y="30"/>
121+
</connectionPointOut>
122+
</variable>
123+
</outputVariables>
124+
</block>
125+
</FBD>
126+
</body>
127+
</action>
128+
<action name="newAction2">
129+
<body>
130+
<FBD>
131+
<inOutVariable localId="1" height="20" width="80" negatedIn="false" storageIn="none" negatedOut="false">
132+
<position x="200" y="70"/>
133+
<connectionPointIn>
134+
<relPosition x="0" y="10"/>
135+
</connectionPointIn>
136+
<connectionPointOut>
137+
<relPosition x="80" y="10"/>
138+
</connectionPointOut>
139+
<expression>a</expression>
140+
</inOutVariable>
141+
</FBD>
142+
</body>
143+
</action>
144+
</actions>
145+
<body>
146+
<FBD/>
147+
</body>
148+
</pou>
149+
"###;
150+
151+
assert_debug_snapshot!(xml_parser::visit(src).unwrap())
152+
}
7153
}

compiler/plc_xml/src/model/block.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use std::{borrow::Cow, collections::HashMap};
22

3-
use quick_xml::events::Event;
3+
use quick_xml::events::{BytesStart, Event};
44

5-
use crate::{error::Error, extensions::GetOrErr, reader::PeekableReader, xml_parser::Parseable};
5+
use crate::{
6+
error::Error,
7+
extensions::GetOrErr,
8+
reader::Reader,
9+
xml_parser::{get_attributes, Parseable},
10+
};
611

712
use super::variables::BlockVariable;
813

@@ -28,28 +33,26 @@ impl<'xml> Block<'xml> {
2833
}
2934

3035
impl<'xml> Parseable for Block<'xml> {
31-
type Item = Self;
32-
33-
fn visit(reader: &mut PeekableReader) -> Result<Self::Item, Error> {
34-
let attributes = reader.attributes()?;
36+
fn visit(reader: &mut Reader, tag: Option<BytesStart>) -> Result<Self, Error> {
37+
let Some(tag) = tag else { unreachable!() };
38+
let attributes = get_attributes(tag.attributes())?;
3539
let mut variables = Vec::new();
36-
3740
loop {
38-
match reader.peek()? {
41+
match reader.read_event().map_err(Error::ReadEvent)? {
3942
Event::Start(tag) => match tag.name().as_ref() {
4043
b"inputVariables" | b"outputVariables" | b"inOutVariables" => {
41-
variables.extend(BlockVariable::visit(reader)?)
44+
let new_vars: Vec<BlockVariable> = Parseable::visit(reader, Some(tag))?;
45+
variables.extend(new_vars);
4246
}
43-
_ => reader.consume()?,
47+
_ => {}
4448
},
4549

4650
Event::End(tag) if tag.name().as_ref() == b"block" => {
47-
reader.consume()?;
4851
break;
4952
}
5053

5154
Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"block"])),
52-
_ => reader.consume()?,
55+
_ => {}
5356
}
5457
}
5558

@@ -63,7 +66,7 @@ mod tests {
6366

6467
use crate::{
6568
model::block::Block,
66-
reader::PeekableReader,
69+
reader::{get_start_tag, Reader},
6770
serializer::{XBlock, XInOutVariables, XInputVariables, XOutputVariables, XVariable},
6871
xml_parser::Parseable,
6972
};
@@ -83,7 +86,8 @@ mod tests {
8386
)
8487
.serialize();
8588

86-
let mut reader = PeekableReader::new(&content);
87-
assert_debug_snapshot!(Block::visit(&mut reader).unwrap());
89+
let mut reader = Reader::new(&content);
90+
let tag = get_start_tag(reader.read_event().unwrap());
91+
assert_debug_snapshot!(Block::visit(&mut reader, tag).unwrap());
8892
}
8993
}

compiler/plc_xml/src/model/body.rs

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,38 @@
1-
use quick_xml::events::Event;
1+
use quick_xml::events::{BytesStart, Event};
22

33
use super::fbd::FunctionBlockDiagram;
4-
use crate::{error::Error, reader::PeekableReader, xml_parser::Parseable};
4+
use crate::{error::Error, reader::Reader, xml_parser::Parseable};
55

66
#[derive(Debug, Default)]
77
pub(crate) struct Body<'xml> {
8-
pub function_block_diagram: Option<FunctionBlockDiagram<'xml>>,
8+
pub function_block_diagram: FunctionBlockDiagram<'xml>,
99
}
1010

1111
impl<'xml> Body<'xml> {
12-
fn new(fbd: Option<FunctionBlockDiagram<'xml>>) -> Result<Self, Error> {
12+
fn new(fbd: FunctionBlockDiagram<'xml>) -> Result<Self, Error> {
1313
Ok(Self { function_block_diagram: fbd })
1414
}
1515

1616
fn empty() -> Result<Self, Error> {
17-
Ok(Self { function_block_diagram: None })
17+
Ok(Self { function_block_diagram: FunctionBlockDiagram::default() })
1818
}
1919
}
2020

2121
impl<'xml> Parseable for Body<'xml> {
22-
type Item = Self;
23-
24-
fn visit(reader: &mut PeekableReader) -> Result<Self::Item, Error> {
22+
fn visit(reader: &mut Reader, _tag: Option<BytesStart>) -> Result<Self, Error> {
23+
let mut body = Body::default();
2524
loop {
26-
match reader.peek()? {
27-
Event::Start(tag) => match tag.name().as_ref() {
28-
b"FBD" => {
29-
let fbd = FunctionBlockDiagram::visit(reader)?;
30-
reader.consume_until(vec![b"body"])?;
31-
32-
return Body::new(Some(fbd));
33-
}
34-
_ => reader.consume()?,
35-
},
36-
Event::Empty(tag) if tag.name().as_ref() == b"FBD" => return Body::empty(),
25+
match reader.read_event().map_err(Error::ReadEvent)? {
26+
Event::Start(tag) if tag.name().as_ref() == b"FBD" => {
27+
body.function_block_diagram = FunctionBlockDiagram::visit(reader, Some(tag))?
28+
}
29+
Event::End(tag) if tag.name().as_ref() == b"body" => break,
3730
Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"body"])),
38-
_ => reader.consume()?,
31+
_ => {}
3932
}
4033
}
34+
35+
Ok(body)
4136
}
4237
}
4338

@@ -47,7 +42,7 @@ mod tests {
4742

4843
use crate::{
4944
model::body::Body,
50-
reader::PeekableReader,
45+
reader::{get_start_tag, Reader},
5146
serializer::{XBlock, XBody, XFbd, XInOutVariables, XInputVariables, XOutputVariables, XVariable},
5247
xml_parser::Parseable,
5348
};
@@ -56,8 +51,8 @@ mod tests {
5651
fn empty() {
5752
let content = XBody::new().with_fbd(XFbd::new().close()).serialize();
5853

59-
let mut reader = PeekableReader::new(&content);
60-
assert_debug_snapshot!(Body::visit(&mut reader).unwrap());
54+
let mut reader = Reader::new(&content);
55+
assert_debug_snapshot!(Body::visit(&mut reader, None).unwrap());
6156
}
6257

6358
#[test]
@@ -84,7 +79,8 @@ mod tests {
8479
)
8580
.serialize();
8681

87-
let mut reader = PeekableReader::new(&content);
88-
assert_debug_snapshot!(Body::visit(&mut reader).unwrap());
82+
let mut reader = Reader::new(&content);
83+
let tag = get_start_tag(reader.read_event().unwrap());
84+
assert_debug_snapshot!(Body::visit(&mut reader, tag).unwrap());
8985
}
9086
}

compiler/plc_xml/src/model/connector.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use quick_xml::events::Event;
55
use crate::{
66
error::Error,
77
extensions::{GetOrErr, TryToString},
8-
reader::PeekableReader,
9-
xml_parser::Parseable,
8+
reader::Reader,
9+
xml_parser::{get_attributes, Parseable},
1010
};
1111

1212
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -37,37 +37,25 @@ pub(crate) enum ConnectorKind {
3737
}
3838

3939
impl<'xml> Parseable for Connector<'xml> {
40-
type Item = Self;
40+
fn visit(reader: &mut Reader, tag: Option<quick_xml::events::BytesStart>) -> Result<Self, Error> {
41+
let Some(tag) = tag else { unreachable!() };
4142

42-
fn visit(reader: &mut PeekableReader) -> Result<Self::Item, Error> {
43-
let next = reader.peek()?;
44-
let kind = match &next {
45-
Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() {
46-
b"connector" => ConnectorKind::Source,
47-
b"continuation" => ConnectorKind::Sink,
48-
_ => return Err(Error::UnexpectedElement(tag.name().try_to_string()?)),
49-
},
50-
51-
_ => unreachable!(),
43+
let kind = match tag.name().as_ref() {
44+
b"connector" => ConnectorKind::Source,
45+
b"continuation" => ConnectorKind::Sink,
46+
_ => return Err(Error::UnexpectedElement(tag.name().try_to_string()?)),
5247
};
5348

54-
let mut attributes = reader.attributes()?;
49+
let mut attributes = get_attributes(tag.attributes())?;
5550
loop {
56-
match reader.peek()? {
57-
Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() {
58-
b"connection" => attributes.extend(reader.attributes()?),
59-
_ => reader.consume()?,
60-
},
61-
62-
Event::End(tag) if matches!(tag.name().as_ref(), b"connector" | b"continuation") => {
63-
reader.consume()?;
64-
break;
51+
match reader.read_event().map_err(Error::ReadEvent)? {
52+
Event::Start(tag) | Event::Empty(tag) if tag.name().as_ref() == b"connection" => {
53+
attributes.extend(get_attributes(tag.attributes())?)
6554
}
66-
67-
_ => reader.consume()?,
55+
Event::End(tag) if matches!(tag.name().as_ref(), b"connector" | b"continuation") => break,
56+
_ => {}
6857
}
6958
}
70-
7159
Connector::new(attributes, kind)
7260
}
7361
}

0 commit comments

Comments
 (0)