Skip to content

Commit

Permalink
tests: Add and update parser / lowering tests
Browse files Browse the repository at this point in the history
  • Loading branch information
volsa committed Jan 27, 2025
1 parent 5f12e96 commit 3ab053d
Show file tree
Hide file tree
Showing 10 changed files with 525 additions and 517 deletions.
605 changes: 179 additions & 426 deletions src/lowering/property.rs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/lowering/validator.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{ops::Deref, sync::RwLock};

use plc_ast::ast::{Property, PropertyImplementation, PropertyKind};
use plc_ast::ast::{Property, PropertyKind};
use plc_diagnostics::{diagnostician::Diagnostician, diagnostics::Diagnostic};

#[derive(Default)]
pub struct ParticipantValidator {
pub diagnostics: Vec<Diagnostic>,
}
Expand Down Expand Up @@ -45,7 +46,6 @@ impl ParticipantValidator {
}
}
}
if implementation.kind == PropertyKind::Get {}
match implementation.kind {
PropertyKind::Get => {
get_blocks.push(implementation.location.clone());
Expand Down Expand Up @@ -87,6 +87,7 @@ impl ParticipantValidator {
}
// -------

#[derive(Default)]
pub struct ParticipantDiagnostician {
inner: RwLock<Diagnostician>,
}
Expand Down
30 changes: 24 additions & 6 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ fn parse_pou(
}

if lexer.token == KeywordProperty {
properties.push(parse_property(lexer, &name, &name_location, &kind));
if let Some(property) = parse_property(lexer, &name, &name_location, &kind) {
properties.push(property);
}
} else {
let is_const = lexer.try_consume(PropertyConstant);
if let Some((pou, implementation)) = parse_method(lexer, &name, linkage, is_const) {
Expand Down Expand Up @@ -632,13 +634,25 @@ fn parse_property(
parent_name: &str,
parent_location: &SourceLocation,
kind: &PouType,
) -> Property {
) -> Option<Property> {
let _location = lexer.location();
lexer.advance(); // Move past `PROPERTY` keyword

// TODO: Error handling
let (name, name_location) = parse_identifier(lexer).expect("property missing a name");
let datatype = parse_return_type(lexer, &PouType::Function).expect("property missing a datatype"); // XXX: The POU type is not correct
let identifier = parse_identifier(lexer);
let datatype = parse_return_type(lexer, &PouType::Function); // XXX: The POU type is not correct

// This is kind of common, hence we parse invalid variable blocks to have useful error messages
while lexer.token.is_var() {
let block = parse_variable_block(lexer, LinkageType::Internal);
lexer.accept_diagnostic(
Diagnostic::new(
"Variable blocks may only be defined within a GET or SET block in the context of properties",
)
.with_location(&block.location)
.with_error_code("E007"),
);
}

let mut implementations = Vec::new();
while matches!(lexer.token, KeywordGet | KeywordSet) {
Expand All @@ -655,16 +669,20 @@ fn parse_property(
implementations.push(PropertyImplementation { kind, variables, statements, location });
}

let (name, name_location) = identifier?;

let datatype = datatype?;

lexer.try_consume_or_report(Token::KeywordEndProperty); // Move past `END_PROPERTY` keyword
Property {
Some(Property {
name,
name_location,
name_parent: parent_name.to_string(),
kind_parent: kind.clone(),
name_parent_location: parent_location.clone(),
datatype,
implementations,
}
})
}

fn parse_access_modifier(lexer: &mut ParseSession) -> AccessModifier {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/expressions_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Option<AstNode>
}

fn parse_identifier(lexer: &mut ParseSession<'_>) -> AstNode {
AstFactory::create_identifier(&lexer.slice_and_advance(), lexer.last_location(), lexer.next_id())
AstFactory::create_identifier(lexer.slice_and_advance(), lexer.last_location(), lexer.next_id())
}

fn parse_vla_range(lexer: &mut ParseSession) -> Option<AstNode> {
Expand Down
1 change: 1 addition & 0 deletions src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod misc_parser_tests;
mod parse_errors;
mod parse_generics;
mod program_parser_tests;
mod property_parser_tests;
mod statement_parser_tests;
mod type_parser_tests;
mod variable_parser_tests;
Expand Down
279 changes: 279 additions & 0 deletions src/parser/tests/property_parser_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
use crate::test_utils::tests::{parse, parse_buffered};

#[test]
fn properties_can_be_parsed() {
let source = r"
FUNCTION_BLOCK foo
PROPERTY bar : INT
GET
VAR
getLocalVariable : DINT;
END_VAR
bar := 5;
END_GET
SET
VAR
setLocalVariable : DINT;
END_VAR
localNonExistingVariable := bar;
END_SET
END_PROPERTY
END_FUNCTION_BLOCK
";

let (unit, diagnostics) = parse(source);

assert_eq!(diagnostics, vec![]);

assert_eq!(unit.units.len(), 1);
assert_eq!(unit.units[0].name, "foo");

assert_eq!(unit.properties.len(), 1);
assert_eq!(unit.properties[0].name, "bar");
assert_eq!(unit.properties[0].implementations.len(), 2);

insta::assert_debug_snapshot!(unit.properties, @r#"
[
Property {
name: "bar",
name_location: SourceLocation {
span: Range(
TextLocation {
line: 2,
column: 21,
offset: 49,
}..TextLocation {
line: 2,
column: 24,
offset: 52,
},
),
},
kind_parent: FunctionBlock,
name_parent: "foo",
name_parent_location: SourceLocation {
span: Range(
TextLocation {
line: 1,
column: 23,
offset: 24,
}..TextLocation {
line: 1,
column: 26,
offset: 27,
},
),
},
datatype: DataTypeReference {
referenced_type: "INT",
},
implementations: [
PropertyImplementation {
kind: Get,
variables: [
VariableBlock {
variables: [
Variable {
name: "getLocalVariable",
data_type: DataTypeReference {
referenced_type: "DINT",
},
},
],
variable_block_type: Local,
},
],
statements: [
Assignment {
left: ReferenceExpr {
kind: Member(
Identifier {
name: "bar",
},
),
base: None,
},
right: LiteralInteger {
value: 5,
},
},
],
location: SourceLocation {
span: Range(
TextLocation {
line: 3,
column: 16,
offset: 75,
}..TextLocation {
line: 3,
column: 19,
offset: 78,
},
),
},
},
PropertyImplementation {
kind: Set,
variables: [
VariableBlock {
variables: [
Variable {
name: "setLocalVariable",
data_type: DataTypeReference {
referenced_type: "DINT",
},
},
],
variable_block_type: Local,
},
],
statements: [
Assignment {
left: ReferenceExpr {
kind: Member(
Identifier {
name: "localNonExistingVariable",
},
),
base: None,
},
right: ReferenceExpr {
kind: Member(
Identifier {
name: "bar",
},
),
base: None,
},
},
],
location: SourceLocation {
span: Range(
TextLocation {
line: 10,
column: 16,
offset: 251,
}..TextLocation {
line: 10,
column: 19,
offset: 254,
},
),
},
},
],
},
]
"#);
}

#[test]
fn property_with_missing_name() {
let source = r"
FUNCTION_BLOCK foo
PROPERTY : INT // <- Missing name
END_PROPERTY
END_FUNCTION_BLOCK
";

// TODO: This error message is pretty bad, however this is more of a parser issue than something property related...
let (_, diagnostics) = parse_buffered(source);
insta::assert_snapshot!(diagnostics, @r"
error[E007]: Unexpected token: expected Identifier but found :
┌─ <internal>:3:22
3 │ PROPERTY : INT // <- Missing name
│ ^ Unexpected token: expected Identifier but found :
error[E007]: Unexpected token: expected Literal but found END_PROPERTY
┌─ <internal>:4:13
4 │ END_PROPERTY
│ ^^^^^^^^^^^^ Unexpected token: expected Literal but found END_PROPERTY
error[E007]: Unexpected token: expected KeywordSemicolon but found 'END_PROPERTY'
┌─ <internal>:4:13
4 │ END_PROPERTY
│ ^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'END_PROPERTY'
error[E006]: Missing expected Token [KeywordSemicolon, KeywordColon]
┌─ <internal>:5:9
5 │ END_FUNCTION_BLOCK
│ ^^^^^^^^^^^^^^^^^^ Missing expected Token [KeywordSemicolon, KeywordColon]
error[E007]: Unexpected token: expected KeywordSemicolon but found 'END_FUNCTION_BLOCK'
┌─ <internal>:5:9
5 │ END_FUNCTION_BLOCK
│ ^^^^^^^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'END_FUNCTION_BLOCK'
");
}

#[test]
fn property_with_missing_datatype() {
let source = r"
FUNCTION_BLOCK foo
PROPERTY bar // <- Missing datatype
END_PROPERTY
END_FUNCTION_BLOCK
";

// TODO: This error message is pretty bad, however this is more of a parser issue than something property related...
let (_, diagnostics) = parse_buffered(source);
insta::assert_snapshot!(diagnostics, @r"
error[E007]: Unexpected token: expected Literal but found END_PROPERTY
┌─ <internal>:4:13
4 │ END_PROPERTY
│ ^^^^^^^^^^^^ Unexpected token: expected Literal but found END_PROPERTY
error[E007]: Unexpected token: expected KeywordSemicolon but found 'END_PROPERTY'
┌─ <internal>:4:13
4 │ END_PROPERTY
│ ^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'END_PROPERTY'
error[E006]: Missing expected Token [KeywordSemicolon, KeywordColon]
┌─ <internal>:5:9
5 │ END_FUNCTION_BLOCK
│ ^^^^^^^^^^^^^^^^^^ Missing expected Token [KeywordSemicolon, KeywordColon]
error[E007]: Unexpected token: expected KeywordSemicolon but found 'END_FUNCTION_BLOCK'
┌─ <internal>:5:9
5 │ END_FUNCTION_BLOCK
│ ^^^^^^^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'END_FUNCTION_BLOCK'
");
}

#[test]
fn property_with_variable_block() {
let source = r"
FUNCTION_BLOCK foo
PROPERTY bar : DINT
VAR
// Invalid variable block, should be in a getter or setter
END_VAR
GET
// ...
END_GET
END_PROPERTY
END_FUNCTION_BLOCK
";

// TODO: Update location
let (_, diagnostics) = parse_buffered(source);
insta::assert_snapshot!(diagnostics, @r"
error[E007]: Variable blocks may only be defined within a GET or SET block in the context of properties
┌─ <internal>:4:17
4 │ VAR
│ ^^^ Variable blocks may only be defined within a GET or SET block in the context of properties
");
}
Loading

0 comments on commit 3ab053d

Please sign in to comment.