Skip to content

Commit

Permalink
feat: methods
Browse files Browse the repository at this point in the history
  • Loading branch information
mhasel committed Nov 13, 2024
1 parent 36769b5 commit 1b157fc
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ fn parse_pou(
// declarations before their implementation.
// all other Pous need to be checked in the validator if they can have methods.
while matches!(lexer.token, KeywordMethod | PropertyConstant) {
if !matches!(pou_type, PouType::FunctionBlock | PouType::Class | PouType::Program) {
let location = lexer.source_range_factory.create_range(lexer.last_range.clone());

lexer.accept_diagnostic(
Diagnostic::new(format!("Methods cannot be declared in a POU of type '{pou_type}'."))
.with_location(location),
);
break;
}
let const_method = lexer.try_consume(&PropertyConstant);
if let Some((pou, implementation)) = parse_method(lexer, &name, linkage, const_method) {
impl_pous.push(pou);
Expand Down
173 changes: 173 additions & 0 deletions src/parser/tests/class_parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,176 @@ fn fb_method_with_return_type_can_be_parsed() {
assert_ne!(method_pou.return_type, None);
assert_eq!(method.overriding, true);
}

#[test]
fn program_methods_can_be_parsed() {
let src = r#"
PROGRAM prog
METHOD INTERNAL FINAL OVERRIDE testMethod2 END_METHOD
END_PROG
"#;
let unit = parse(src).0;

let class = &unit.units[0];
assert_eq!(class.pou_type, PouType::Program);

// classes have implementation because they are treated as other POUs
assert_eq!(unit.implementations.len(), 2);

let method_pou = &unit.units[1];
assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "prog".into() });
let method = &unit.implementations[0];

assert_eq!(method_pou.name, "prog.testMethod2");
assert_eq!(method.access, Some(AccessModifier::Internal));
assert_eq!(method_pou.poly_mode, Some(PolymorphismMode::Final));
assert_eq!(method_pou.return_type, None);
assert_eq!(method.overriding, true);
}

#[test]
fn program_two_methods_can_be_parsed() {
let src = r#"
PROGRAM prog
METHOD INTERNAL testMethod2 END_METHOD
METHOD otherMethod VAR_TEMP END_VAR END_METHOD
END_PROGRAM
"#;
let unit = parse(src).0;

let class = &unit.units[0];
assert_eq!(class.pou_type, PouType::Program);

// classes have implementation because they are treated as other POUs
assert_eq!(unit.implementations.len(), 3);

let method1 = &unit.implementations[0];
assert_eq!(method1.name, "prog.testMethod2");
assert_eq!(method1.access, Some(AccessModifier::Internal));

let method2 = &unit.implementations[1];
assert_eq!(method2.name, "prog.otherMethod");
assert_eq!(method2.access, Some(AccessModifier::Protected));
}

#[test]
fn program_method_with_return_type_can_be_parsed() {
let src = r#"
PROGRAM prog
METHOD PRIVATE ABSTRACT OVERRIDE testMethod3 : SINT END_METHOD
END_PROGRAM
"#;
let unit = parse(src).0;

let class = &unit.units[0];
assert_eq!(class.pou_type, PouType::Program);

let method_pou = &unit.units[1];
assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "prog".into() });
let method = &unit.implementations[0];

// classes have implementation because they are treated as other POUs
assert_eq!(unit.implementations.len(), 2);

assert_eq!(method_pou.name, "prog.testMethod3");
assert_eq!(method.access, Some(AccessModifier::Private)); // TODO: default public?
assert_eq!(method_pou.poly_mode, Some(PolymorphismMode::Abstract));
assert_ne!(method_pou.return_type, None);
assert_eq!(method.overriding, true);
}

#[test]
fn method_variable_blocks_can_be_parsed() {
let src = r"
FUNCTION_BLOCK fb
METHOD mthd
VAR
x : DINT;
y : DINT := 3;
END_VAR
VAR_TEMP
xTmp : DINT;
yTmp : DINT := 3;
END_VAR
VAR_INPUT
xIn : DINT;
yIn : DINT := 3;
END_VAR
VAR_OUTPUT
xOut : DINT;
yOut : DINT := 3;
END_VAR
VAR_IN_OUT
xIO : DINT;
yIO : DINT;
END_VAR
END_METHOD
END_FUNCTION_BLOCK
PROGRAM prg
METHOD mthd
VAR
x : DINT;
y : DINT := 3;
END_VAR
VAR_TEMP
xTmp : DINT;
yTmp : DINT := 3;
END_VAR
VAR_INPUT
xIn : DINT;
yIn : DINT := 3;
END_VAR
VAR_OUTPUT
xOut : DINT;
yOut : DINT := 3;
END_VAR
VAR_IN_OUT
xIO : DINT;
yIO : DINT;
END_VAR
END_METHOD
END_PROGRAM
";

let (unit, _) = parse(src);
let fb_mthd = &unit.units[1];
assert_eq!(fb_mthd.name, "fb.mthd".to_string());
assert_eq!(fb_mthd.pou_type, PouType::Method { owner_class: "fb".into() });

let prg_mthd = &unit.units[3];
assert_eq!(prg_mthd.name, "prg.mthd".to_string());
assert_eq!(prg_mthd.pou_type, PouType::Method { owner_class: "prg".into() });

// we expect one of each of these `VariableBlockType` to be parsed
let expected_var_blocks = vec![
VariableBlockType::Local,
VariableBlockType::Temp,
VariableBlockType::Input(ArgumentProperty::ByVal),
VariableBlockType::Output,
VariableBlockType::InOut,
];
let actual =
&fb_mthd.variable_blocks.iter().map(|it| it.variable_block_type).collect::<Vec<VariableBlockType>>();
assert_eq!(&expected_var_blocks, actual);
let actual =
&prg_mthd.variable_blocks.iter().map(|it| it.variable_block_type).collect::<Vec<VariableBlockType>>();
assert_eq!(&expected_var_blocks, actual);

// we expect to have parsed 10 variables and 4 of them (all `y`s apart from the in-out) to have initializer
let variables = &fb_mthd.variable_blocks.iter().fold(vec![], |mut acc, block| {
acc.extend(block.variables.iter());
acc
});
assert_eq!(variables.len(), 10);
let with_initializer = variables.iter().filter(|it| it.initializer.is_some());
assert_eq!(with_initializer.count(), 4);

let variables = &prg_mthd.variable_blocks.iter().fold(vec![], |mut acc, block| {
acc.extend(block.variables.iter());
acc
});
assert_eq!(variables.len(), 10);
let with_initializer = variables.iter().filter(|it| it.initializer.is_some());
assert_eq!(with_initializer.count(), 4);
}
46 changes: 45 additions & 1 deletion src/parser/tests/parse_errors/parse_error_classes_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use insta::assert_snapshot;

use crate::test_utils::tests::parse_and_validate_buffered;
use crate::test_utils::tests::{parse_and_validate_buffered, parse_buffered};

#[test]
fn simple_class_without_name() {
Expand All @@ -15,3 +15,47 @@ fn method_with_invalid_return_type() {
let diagnostics = parse_and_validate_buffered(src);
assert_snapshot!(diagnostics)
}

#[test]
fn declaring_methods_in_functions_is_an_error() {
let src = r#"
FUNCTION bar
METHOD anyMethod
;
END_METHOD
;
END_FUNCTION
"#;
let (_, diagnostics) = parse_buffered(src);
assert_snapshot!(diagnostics, @r###"
error[E001]: Methods cannot be declared in a POU of type 'Function'.
┌─ <internal>:2:14
2 │ FUNCTION bar
│ ^^^ Methods cannot be declared in a POU of type 'Function'.
error[E007]: Unexpected token: expected Literal but found METHOD
┌─ <internal>:3:9
3 │ METHOD anyMethod
│ ^^^^^^ Unexpected token: expected Literal but found METHOD
error[E007]: Unexpected token: expected KeywordSemicolon but found 'METHOD anyMethod'
┌─ <internal>:3:9
3 │ METHOD anyMethod
│ ^^^^^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'METHOD anyMethod'
error[E007]: Unexpected token: expected Literal but found END_METHOD
┌─ <internal>:5:9
5 │ END_METHOD
│ ^^^^^^^^^^ Unexpected token: expected Literal but found END_METHOD
error[E007]: Unexpected token: expected KeywordSemicolon but found 'END_METHOD'
┌─ <internal>:5:9
5 │ END_METHOD
│ ^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'END_METHOD'
"###);
}

0 comments on commit 1b157fc

Please sign in to comment.