diff --git a/src/ast.rs b/src/ast.rs index eb708337ff..134fc1e116 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -25,6 +25,14 @@ pub struct Pou { pub pou_type: PouType, pub return_type: Option, pub location: SourceRange, + pub poly_mode: Option, +} + +#[derive(Debug, PartialEq)] +pub enum PolymorphismMode { + None, + Abstract, + Final, } impl Debug for Pou { @@ -46,6 +54,8 @@ pub struct Implementation { pub pou_type: PouType, pub statements: Vec, pub location: SourceRange, + pub overriding: bool, + pub access: Option, } #[derive(Debug, Copy, PartialEq, Clone)] @@ -54,12 +64,22 @@ pub enum LinkageType { External, } +#[derive(Debug, PartialEq)] +pub enum AccessModifier { + Private, + Public, + Protected, // default + Internal, +} + #[derive(Debug, Copy, PartialEq, Clone)] pub enum PouType { Program, Function, FunctionBlock, Action, + Class, + Method, } #[derive(Debug, PartialEq)] @@ -99,6 +119,7 @@ impl Default for CompilationUnit { #[derive(Debug, Copy, PartialEq, Clone)] pub enum VariableBlockType { Local, + Temp, Input, Output, Global, @@ -107,6 +128,9 @@ pub enum VariableBlockType { #[derive(PartialEq)] pub struct VariableBlock { + pub access: AccessModifier, + pub constant: bool, + pub retain: bool, pub variables: Vec, pub variable_block_type: VariableBlockType, } diff --git a/src/index.rs b/src/index.rs index 01ba783481..e359ac88bd 100644 --- a/src/index.rs +++ b/src/index.rs @@ -56,6 +56,7 @@ impl VariableIndexEntry { #[derive(Copy, Clone, Debug, PartialEq)] pub enum VariableType { Local, + Temp, Input, Output, InOut, diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 8f646d36a9..c24f7420a7 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -121,6 +121,70 @@ fn actions_are_indexed() { } } +#[test] +fn fb_methods_are_indexed() { + let index = index!( + r#" + FUNCTION_BLOCK myFuncBlock + METHOD foo + VAR x : SINT; END_VAR + END_METHOD + END_FUNCTION_BLOCK + "# + ); + + let foo_impl = index.find_implementation("myFuncBlock.foo").unwrap(); + assert_eq!("myFuncBlock.foo", foo_impl.call_name); + assert_eq!("myFuncBlock", foo_impl.type_name); + let info = index + .get_type("myFuncBlock.foo") + .unwrap() + .get_type_information(); + if let crate::typesystem::DataTypeInformation::Struct { + name, + member_names, + varargs: _, + } = info + { + assert_eq!("myFuncBlock.foo_interface", name); + assert_eq!(&vec!["x"], member_names); + } else { + panic!("Wrong variant : {:#?}", info); + } +} + +#[test] +fn class_methods_are_indexed() { + let index = index!( + r#" + CLASS myClass + METHOD foo + VAR y : DINT; END_VAR + END_METHOD + END_CLASS + "# + ); + + let foo_impl = index.find_implementation("myClass.foo").unwrap(); + assert_eq!("myClass.foo", foo_impl.call_name); + assert_eq!("myClass", foo_impl.type_name); + let info = index + .get_type("myClass.foo") + .unwrap() + .get_type_information(); + if let crate::typesystem::DataTypeInformation::Struct { + name, + member_names, + varargs: _, + } = info + { + assert_eq!("myClass.foo_interface", name); + assert_eq!(&vec!["y"], member_names); + } else { + panic!("Wrong variant : {:#?}", info); + } +} + #[test] fn function_is_indexed() { let index = index!( @@ -190,11 +254,17 @@ fn pous_are_indexed() { END_PROGRAM FUNCTION myFunction : INT END_FUNCTION + FUNCTION_BLOCK myFunctionBlock : INT + END_FUNCTION_BLOCK + CLASS myClass + END_CLASS "# ); index.find_type("myFunction").unwrap(); index.find_type("myProgram").unwrap(); + index.find_type("myFunctionBlock").unwrap(); + index.find_type("myClass").unwrap(); } #[test] diff --git a/src/index/visitor.rs b/src/index/visitor.rs index c1d1623e4b..8fa0e54a9d 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -175,6 +175,7 @@ fn visit_global_var_block(index: &mut Index, block: &VariableBlock) { fn get_variable_type_from_block(block: &VariableBlock) -> VariableType { match block.variable_block_type { VariableBlockType::Local => VariableType::Local, + VariableBlockType::Temp => VariableType::Temp, VariableBlockType::Input => VariableType::Input, VariableBlockType::Output => VariableType::Output, VariableBlockType::Global => VariableType::Global, diff --git a/src/lexer.rs b/src/lexer.rs index 9d6ade4e90..a5854f7a09 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -121,7 +121,9 @@ impl<'a> ParseSession<'a> { | Token::KeywordEndActions | Token::KeywordEndIf | Token::KeywordEndFor - | Token::KeywordEndRepeat => { + | Token::KeywordEndRepeat + | Token::KeywordEndMethod + | Token::KeywordEndClass => { if !self.slice().to_string().contains('_') { self.accept_diagnostic(Diagnostic::ImprovementSuggestion { message: format!( diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 057b835824..4b115e0a4d 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -15,6 +15,13 @@ pub enum Token { #[token("PROGRAM")] KeywordProgram, + #[token("CLASS")] + KeywordClass, + + #[token("END_CLASS")] + #[token("ENDCLASS")] + KeywordEndClass, + #[token("VAR_INPUT")] #[token("VARINPUT")] KeywordVarInput, @@ -26,6 +33,46 @@ pub enum Token { #[token("VAR")] KeywordVar, + #[token("ABSTRACT")] + KeywordAbstract, + + #[token("FINAL")] + KeywordFinal, + + #[token("METHOD")] + KeywordMethod, + + #[token("CONSTANT")] + KeywordConstant, + + #[token("RETAIN")] + KeywordRetain, + + #[token("NON_RETAIN")] + KeywordNonRetain, + + #[token("VAR_TEMP")] + KeywordVarTemp, + + #[token("END_METHOD")] + #[token("ENDMETHOD")] + KeywordEndMethod, + + #[token("PUBLIC")] + KeywordAccessPublic, + + #[token("PRIVATE")] + KeywordAccessPrivate, + + #[token("INTERNAL")] + KeywordAccessInternal, + + #[token("PROTECTED")] + KeywordAccessProtected, + + #[token("OVERRIDE")] + KeywordOverride, + #[token("VAR_GLOBAL")] #[token("VARGLOBAL")] KeywordVarGlobal, diff --git a/src/parser.rs b/src/parser.rs index 116d4d41ac..49b53af5d3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -30,27 +30,19 @@ pub fn parse(mut lexer: ParseSession) -> ParsedAst { KeywordVarGlobal => unit .global_vars .push(parse_variable_block(&mut lexer, VariableBlockType::Global)), - KeywordProgram => { - let (pou, implementation) = - parse_pou(&mut lexer, PouType::Program, linkage, KeywordEndProgram); - unit.units.push(pou); - unit.implementations.push(implementation); - } - KeywordFunction => { - let (pou, implementation) = - parse_pou(&mut lexer, PouType::Function, linkage, KeywordEndFunction); - unit.units.push(pou); - unit.implementations.push(implementation); - } - KeywordFunctionBlock => { - let (pou, implementation) = parse_pou( - &mut lexer, - PouType::FunctionBlock, - linkage, - KeywordEndFunctionBlock, - ); - unit.units.push(pou); - unit.implementations.push(implementation); + KeywordProgram | KeywordClass | KeywordFunction | KeywordFunctionBlock => { + let params = match lexer.token { + KeywordProgram => (PouType::Program, KeywordEndProgram), + KeywordClass => (PouType::Class, KeywordEndClass), + KeywordFunction => (PouType::Function, KeywordEndFunction), + _ => (PouType::FunctionBlock, KeywordEndFunctionBlock), + }; + + let (mut pou, mut implementation) = + parse_pou(&mut lexer, params.0, linkage, params.1); + + unit.units.append(&mut pou); + unit.implementations.append(&mut implementation); } KeywordAction => { if let Some(implementation) = parse_action(&mut lexer, linkage, None) { @@ -129,6 +121,7 @@ fn parse_actions( /// /// * `lexer` - the lexer /// * `pou_type` - the type of the pou currently parsed +/// * `linkage` - internal, external ? /// * `expected_end_token` - the token that ends this pou /// fn parse_pou( @@ -136,7 +129,7 @@ fn parse_pou( pou_type: PouType, linkage: LinkageType, expected_end_token: lexer::Token, -) -> (Pou, Implementation) { +) -> (Vec, Vec) { let start = lexer.range().start; lexer.advance(); //Consume ProgramKeyword let closing_tokens = vec![ @@ -147,68 +140,78 @@ fn parse_pou( KeywordEndFunctionBlock, ]; let pou = parse_any_in_region(lexer, closing_tokens.clone(), |lexer| { - //Parse pou name - let name = if lexer.token == Identifier { - lexer.slice_and_advance() - } else { - //missing pou name - lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "Identifier".to_string(), - lexer.slice().to_string(), - SourceRange::new(lexer.range()), - )); - "".to_string() + let poly_mode = match pou_type { + PouType::Class | PouType::FunctionBlock | PouType::Method => { + // classes and function blocks can be ABSTRACT, FINAL or neither. + parse_polymorphism_mode(lexer, pou_type) + } + _ => None, }; - //optional return type - let start_return_type = lexer.range().start; - let return_type = if lexer.allow(&KeywordColon) { - if lexer.token == Identifier || lexer.token == KeywordString { - if pou_type != PouType::Function { - lexer.accept_diagnostic(Diagnostic::return_type_not_supported( - &pou_type, - SourceRange::new(start_return_type..lexer.range().end), - )); - } - let referenced_type = lexer.slice_and_advance(); - Some(DataTypeDeclaration::DataTypeReference { referenced_type }) - } else { - //missing return type - lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "Datatype".to_string(), - lexer.slice().to_string(), - SourceRange::new(lexer.range()), - )); - None - } + let name = parse_identifier(lexer).unwrap_or_else(|| "".to_string()); // parse POU name + + // TODO: Parse USING directives + // TODO: Parse EXTENDS specifier + // TODO: Parse IMPLEMENTS specifier + + let return_type = if pou_type != PouType::Class { + // parse an optional return type + parse_return_type(lexer, pou_type) } else { + // classes do not have a return type None }; - //Parse variable declarations + // parse variable declarations. note that var in/out/inout + // blocks are not allowed inside of class declarations. let mut variable_blocks = vec![]; - while lexer.token == KeywordVar - || lexer.token == KeywordVarInput - || lexer.token == KeywordVarOutput - || lexer.token == KeywordVarInOut - { + let allowed_var_types = match pou_type { + PouType::Class => vec![KeywordVar], + _ => vec![ + KeywordVar, + KeywordVarInput, + KeywordVarOutput, + KeywordVarInOut, + ], + }; + while allowed_var_types.contains(&lexer.token) { variable_blocks.push(parse_variable_block( lexer, parse_variable_block_type(&lexer.token), )); } - let implementation = parse_implementation(lexer, linkage, pou_type, &name, &name); + let mut impl_pous = vec![]; + let mut implementations = vec![]; + if pou_type == PouType::Class || pou_type == PouType::FunctionBlock { + // classes and function blocks can have methods. methods consist of a Pou part + // and an implementation part. That's why we get another (Pou, Implementation) + // tuple out of parse_method() that has to be added to the list of Pous and + // implementations. Note that function blocks have to start with the method + // declarations before their implementation. + while lexer.token == KeywordMethod { + if let Some((pou, implementation)) = parse_method(lexer, &name, linkage) { + impl_pous.push(pou); + implementations.push(implementation); + } + } + } + if pou_type != PouType::Class { + // a class may not contain an implementation + implementations.push(parse_implementation(lexer, linkage, pou_type, &name, &name)); + } - let pou = Pou { + let mut pous = vec![Pou { name, pou_type, variable_blocks, return_type, location: SourceRange::new(start..lexer.range().end), - }; + poly_mode, + }]; + pous.append(&mut impl_pous); - (pou, implementation) + (pous, implementations) }); //check if we ended on the right end-keyword @@ -222,6 +225,143 @@ fn parse_pou( pou } +fn parse_polymorphism_mode( + lexer: &mut ParseSession, + pou_type: PouType, +) -> Option { + match pou_type { + PouType::Class | PouType::FunctionBlock | PouType::Method => { + Some( + // See if the method/pou was declared FINAL or ABSTRACT + if lexer.allow(&KeywordFinal) { + PolymorphismMode::Final + } else if lexer.allow(&KeywordAbstract) { + PolymorphismMode::Abstract + } else { + PolymorphismMode::None + }, + ) + } + _ => None, + } +} + +fn parse_return_type(lexer: &mut ParseSession, pou_type: PouType) -> Option { + let start_return_type = lexer.range().start; + if lexer.allow(&KeywordColon) { + if lexer.token == Identifier || lexer.token == KeywordString { + if pou_type != PouType::Function && pou_type != PouType::Method { + lexer.accept_diagnostic(Diagnostic::return_type_not_supported( + &pou_type, + SourceRange::new(start_return_type..lexer.range().end), + )); + } + let referenced_type = lexer.slice_and_advance(); + Some(DataTypeDeclaration::DataTypeReference { referenced_type }) + } else { + //missing return type + lexer.accept_diagnostic(Diagnostic::unexpected_token_found( + "Datatype".to_string(), + lexer.slice().to_string(), + SourceRange::new(lexer.range()), + )); + None + } + } else { + None + } +} + +fn parse_method( + lexer: &mut ParseSession, + class_name: &str, + linkage: LinkageType, +) -> Option<(Pou, Implementation)> { + parse_any_in_region(lexer, vec![KeywordEndMethod], |lexer| { + // Method declarations look like this: + // METHOD [AccessModifier] [ABSTRACT|FINAL] [OVERRIDE] [: return_type] + // ... + // END_METHOD + + let method_start = lexer.location().get_start(); + lexer.advance(); // eat METHOD keyword + + let access = Some(parse_access_modifier(lexer)); + let poly_mode = parse_polymorphism_mode(lexer, PouType::Method); + let overriding = lexer.allow(&KeywordOverride); + let name = parse_identifier(lexer)?; + let return_type = parse_return_type(lexer, PouType::Method); + + let mut variable_blocks = vec![]; + while lexer.token == KeywordVar + || lexer.token == KeywordVarInput + || lexer.token == KeywordVarOutput + || lexer.token == KeywordVarInOut + || lexer.token == KeywordVarTemp + { + variable_blocks.push(parse_variable_block( + lexer, + parse_variable_block_type(&lexer.token), + )); + } + + let call_name = format!("{}.{}", class_name, name); + let implementation = + parse_implementation(lexer, linkage, PouType::Class, &call_name, class_name); + + // parse_implementation() will default-initialize the fields it + // doesn't know. thus, we have to complete the information. + let implementation = Implementation { + overriding, + access, + ..implementation + }; + + let method_end = lexer.location().get_end(); + Some(( + Pou { + name: call_name, + pou_type: PouType::Method, + variable_blocks, + return_type, + location: SourceRange::new(method_start..method_end), + poly_mode, + }, + implementation, + )) + }) +} + +fn parse_access_modifier(lexer: &mut ParseSession) -> AccessModifier { + if lexer.allow(&KeywordAccessPublic) { + AccessModifier::Public + } else if lexer.allow(&KeywordAccessPrivate) { + AccessModifier::Private + } else if lexer.allow(&KeywordAccessProtected) { + AccessModifier::Protected + } else if lexer.allow(&KeywordAccessInternal) { + AccessModifier::Internal + } else { + AccessModifier::Protected + } +} + +/// parse identifier and advance if successful +fn parse_identifier(lexer: &mut ParseSession) -> Option { + let pou_name = lexer.slice().to_string(); + if lexer.token == Identifier { + lexer.advance(); + Some(pou_name) + } else { + lexer.accept_diagnostic(Diagnostic::unexpected_token_found( + "Identifier".into(), + pou_name, + lexer.location(), + )); + None + } +} + fn parse_implementation( lexer: &mut ParseSession, linkage: LinkageType, @@ -238,6 +378,8 @@ fn parse_implementation( pou_type, statements, location: SourceRange::new(start..lexer.range().end), + overriding: false, + access: None, } } @@ -593,6 +735,7 @@ fn parse_control(lexer: &mut ParseSession) -> Statement { fn parse_variable_block_type(block_type: &Token) -> VariableBlockType { match block_type { KeywordVar => VariableBlockType::Local, + KeywordVarTemp => VariableBlockType::Temp, KeywordVarInput => VariableBlockType::Input, KeywordVarOutput => VariableBlockType::Output, KeywordVarGlobal => VariableBlockType::Global, @@ -607,10 +750,21 @@ fn parse_variable_block( ) -> VariableBlock { //Consume the type keyword lexer.advance(); + + let constant = lexer.allow(&KeywordConstant); + + let retain = lexer.allow(&KeywordRetain); + lexer.allow(&KeywordNonRetain); + + let access = parse_access_modifier(lexer); + let variables = parse_any_in_region(lexer, vec![KeywordEndVar], |lexer| { parse_variable_list(lexer) }); VariableBlock { + access, + constant, + retain, variables, variable_block_type, } diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 33bf85d904..1bd3684246 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -1,6 +1,7 @@ use crate::ast::{SourceRange, Statement}; // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +mod class_parser_tests; mod container_parser_tests; mod control_parser_tests; mod expressions_parser_tests; diff --git a/src/parser/tests/class_parser_tests.rs b/src/parser/tests/class_parser_tests.rs new file mode 100644 index 0000000000..6832afd260 --- /dev/null +++ b/src/parser/tests/class_parser_tests.rs @@ -0,0 +1,303 @@ +use crate::{ + ast::*, + parser::{parse, tests::lex}, +}; + +#[test] +fn simple_class_with_defaults_can_be_parsed() { + let lexer = lex("CLASS MyClass END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + + assert_eq!(class.name, "MyClass"); + assert_eq!(class.poly_mode, Some(PolymorphismMode::None)); + assert_eq!(unit.implementations.len(), 0); +} + +#[test] +fn simple_class_can_be_parsed() { + let lexer = lex("CLASS ABSTRACT MyClass END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + + assert_eq!(class.name, "MyClass"); + assert_eq!(class.poly_mode, Some(PolymorphismMode::Abstract)); + assert_eq!(unit.implementations.len(), 0); +} + +#[test] +fn simple_class2_can_be_parsed() { + let lexer = lex("CLASS FINAL MyClass2 END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + + assert_eq!(class.name, "MyClass2"); + assert_eq!(class.poly_mode, Some(PolymorphismMode::Final)); + assert_eq!(unit.implementations.len(), 0); +} + +#[test] +fn method_with_defaults_can_be_parsed() { + let lexer = lex("CLASS MyClass METHOD testMethod END_METHOD END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 1); + + let method_pou = &unit.units[1]; + assert_eq!(method_pou.pou_type, PouType::Method); + let method = &unit.implementations[0]; + + assert_eq!(method_pou.name, "MyClass.testMethod"); + assert_eq!(method.access, Some(AccessModifier::Protected)); + assert_eq!(method_pou.poly_mode, Some(PolymorphismMode::None)); + assert_eq!(method_pou.return_type, None); + assert_eq!(method.overriding, false); +} + +#[test] +fn method_can_be_parsed() { + let lexer = + lex("CLASS MyClass METHOD INTERNAL FINAL OVERRIDE testMethod2 END_METHOD END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 1); + + let method_pou = &unit.units[1]; + assert_eq!(method_pou.pou_type, PouType::Method); + let method = &unit.implementations[0]; + + assert_eq!(method_pou.name, "MyClass.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 two_methods_can_be_parsed() { + let lexer = + lex("CLASS MyClass METHOD INTERNAL testMethod2 END_METHOD METHOD PROTECTED otherMethod VAR_TEMP END_VAR END_METHOD END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 2); + + let method1 = &unit.implementations[0]; + assert_eq!(method1.name, "MyClass.testMethod2"); + assert_eq!(method1.access, Some(AccessModifier::Internal)); + + let method2 = &unit.implementations[1]; + assert_eq!(method2.name, "MyClass.otherMethod"); + assert_eq!(method2.access, Some(AccessModifier::Protected)); +} + +#[test] +fn method_with_return_type_can_be_parsed() { + let lexer = lex( + "CLASS MyClass METHOD PRIVATE ABSTRACT OVERRIDE testMethod3 : SINT END_METHOD END_CLASS", + ); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + + let method_pou = &unit.units[1]; + assert_eq!(method_pou.pou_type, PouType::Method); + let method = &unit.implementations[0]; + assert_eq!(unit.implementations.len(), 1); + + assert_eq!(method_pou.name, "MyClass.testMethod3"); + assert_eq!(method.access, Some(AccessModifier::Private)); + assert_eq!(method_pou.poly_mode, Some(PolymorphismMode::Abstract)); + assert_ne!(method_pou.return_type, None); + assert_eq!(method.overriding, true); +} + +#[test] +fn class_with_var_default_block() { + let lexer = lex("CLASS MyClass VAR END_VAR END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 0); + + let vblock = &class.variable_blocks[0]; + assert_eq!(vblock.variables.len(), 0); + + assert_eq!(vblock.retain, false); + assert_eq!(vblock.constant, false); + assert_eq!(vblock.access, AccessModifier::Protected); + assert_eq!(vblock.variable_block_type, VariableBlockType::Local); +} + +#[test] +fn class_with_var_non_retain_block() { + let lexer = lex("CLASS MyClass VAR CONSTANT NON_RETAIN PUBLIC END_VAR END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 0); + + let vblock = &class.variable_blocks[0]; + assert_eq!(vblock.variables.len(), 0); + + assert_eq!(vblock.retain, false); + assert_eq!(vblock.constant, true); + assert_eq!(vblock.access, AccessModifier::Public); + assert_eq!(vblock.variable_block_type, VariableBlockType::Local); +} + +#[test] +fn class_with_var_retain_block() { + let lexer = lex("CLASS MyClass VAR RETAIN INTERNAL END_VAR END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 0); + + let vblock = &class.variable_blocks[0]; + assert_eq!(vblock.variables.len(), 0); + + assert_eq!(vblock.retain, true); + assert_eq!(vblock.constant, false); + assert_eq!(vblock.access, AccessModifier::Internal); + assert_eq!(vblock.variable_block_type, VariableBlockType::Local); +} + +#[test] +fn method_with_var_block() { + let lexer = lex("CLASS MyClass METHOD testMethod3 VAR_TEMP END_VAR END_METHOD END_CLASS"); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + assert_eq!(unit.implementations.len(), 1); + + let method_pou = &unit.units[1]; + let vblock = &method_pou.variable_blocks[0]; + + assert_eq!(vblock.retain, false); + assert_eq!(vblock.constant, false); + assert_eq!(vblock.access, AccessModifier::Protected); + assert_eq!(vblock.variable_block_type, VariableBlockType::Temp); +} + +#[test] +fn method_with_var_inout_blocks() { + let lexer = lex(r#" + CLASS MyClass + METHOD testMethod3 + VAR_INPUT CONSTANT + x:SINT := 3; + END_VAR + VAR_IN_OUT END_VAR + VAR_OUTPUT END_VAR + END_METHOD + END_CLASS"#); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::Class); + + let method_pou = &unit.units[1]; + assert_eq!(unit.implementations.len(), 1); + + assert_eq!(method_pou.variable_blocks.len(), 3); + let vblock1 = &method_pou.variable_blocks[0]; + let vblock2 = &method_pou.variable_blocks[1]; + let vblock3 = &method_pou.variable_blocks[2]; + + assert_eq!(vblock1.constant, true); + assert_eq!(vblock1.variable_block_type, VariableBlockType::Input); + + assert_eq!(vblock2.constant, false); + assert_eq!(vblock2.variable_block_type, VariableBlockType::InOut); + + assert_eq!(vblock3.constant, false); + assert_eq!(vblock3.variable_block_type, VariableBlockType::Output); +} + +#[test] +fn fb_method_can_be_parsed() { + let lexer = lex(r#" + FUNCTION_BLOCK MyFb + METHOD INTERNAL FINAL OVERRIDE testMethod2 END_METHOD + END_FUNCTION_BLOCK + "#); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::FunctionBlock); + assert_eq!(unit.implementations.len(), 2); + + let method_pou = &unit.units[1]; + assert_eq!(method_pou.pou_type, PouType::Method); + let method = &unit.implementations[0]; + + assert_eq!(method_pou.name, "MyFb.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 fb_two_methods_can_be_parsed() { + let lexer = lex(r#" + FUNCTION_BLOCK MyNewFb + METHOD INTERNAL testMethod2 END_METHOD + METHOD otherMethod VAR_TEMP END_VAR END_METHOD + END_FUNCTION_BLOCK + "#); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::FunctionBlock); + assert_eq!(unit.implementations.len(), 3); + + let method1 = &unit.implementations[0]; + assert_eq!(method1.name, "MyNewFb.testMethod2"); + assert_eq!(method1.access, Some(AccessModifier::Internal)); + + let method2 = &unit.implementations[1]; + assert_eq!(method2.name, "MyNewFb.otherMethod"); + assert_eq!(method2.access, Some(AccessModifier::Protected)); +} + +#[test] +fn fb_method_with_return_type_can_be_parsed() { + let lexer = lex(r#" + FUNCTION_BLOCK MyShinyFb + METHOD PRIVATE ABSTRACT OVERRIDE testMethod3 : SINT END_METHOD + END_FUNCTION_BLOCK + "#); + let unit = parse(lexer).0; + + let class = &unit.units[0]; + assert_eq!(class.pou_type, PouType::FunctionBlock); + + let method_pou = &unit.units[1]; + assert_eq!(method_pou.pou_type, PouType::Method); + let method = &unit.implementations[0]; + assert_eq!(unit.implementations.len(), 2); + + assert_eq!(method_pou.name, "MyShinyFb.testMethod3"); + assert_eq!(method.access, Some(AccessModifier::Private)); + assert_eq!(method_pou.poly_mode, Some(PolymorphismMode::Abstract)); + assert_ne!(method_pou.return_type, None); + assert_eq!(method.overriding, true); +} diff --git a/src/parser/tests/function_parser_tests.rs b/src/parser/tests/function_parser_tests.rs index 42e3b566fe..90890b4a1f 100644 --- a/src/parser/tests/function_parser_tests.rs +++ b/src/parser/tests/function_parser_tests.rs @@ -121,6 +121,9 @@ fn varargs_parameters_can_be_parsed() { referenced_type: "DINT".into(), }), variable_blocks: vec![VariableBlock { + constant: false, + access: AccessModifier::Protected, + retain: false, variable_block_type: VariableBlockType::Input, variables: vec![ Variable { @@ -150,6 +153,7 @@ fn varargs_parameters_can_be_parsed() { ], }], location: SourceRange::undefined(), + poly_mode: None, }; assert_eq!(format!("{:#?}", expected), format!("{:#?}", x).as_str()); } diff --git a/src/parser/tests/parse_errors.rs b/src/parser/tests/parse_errors.rs index dc2461d85d..c5509b9547 100644 --- a/src/parser/tests/parse_errors.rs +++ b/src/parser/tests/parse_errors.rs @@ -1,3 +1,4 @@ +mod parse_error_classes_tests; mod parse_error_containers_tests; mod parse_error_literals_tests; mod parse_error_messages_test; diff --git a/src/parser/tests/parse_errors/parse_error_classes_tests.rs b/src/parser/tests/parse_errors/parse_error_classes_tests.rs new file mode 100644 index 0000000000..3399b2077e --- /dev/null +++ b/src/parser/tests/parse_errors/parse_error_classes_tests.rs @@ -0,0 +1,34 @@ +use crate::{ + parser::{parse, tests::lex}, + Diagnostic, SourceRange, +}; + +#[test] +fn simple_class_without_name() { + let lexer = lex("CLASS END_CLASS"); + let diagnostics = parse(lexer).1; + + assert_eq!( + diagnostics.first().unwrap(), + &Diagnostic::unexpected_token_found( + "Identifier".into(), + "END_CLASS".into(), + SourceRange::new(6..15) + ) + ); +} + +#[test] +fn method_with_invalid_return_type() { + let lexer = lex("CLASS TestClass METHOD foo : ABSTRACT END_METHOD END_CLASS"); + let diagnostics = parse(lexer).1; + + assert_eq!( + diagnostics.first().unwrap(), + &Diagnostic::unexpected_token_found( + "Datatype".into(), + "ABSTRACT".into(), + SourceRange::new(29..37), + ) + ); +} diff --git a/src/parser/tests/parse_errors/parse_error_containers_tests.rs b/src/parser/tests/parse_errors/parse_error_containers_tests.rs index fc2a713642..602a61e8d5 100644 --- a/src/parser/tests/parse_errors/parse_error_containers_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_containers_tests.rs @@ -1,6 +1,6 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::{ - ast::{PouType, SourceRange, Statement, Variable, VariableBlock, VariableBlockType}, + ast::*, lexer::Token, parser::{parse, tests::lex}, Diagnostic, @@ -239,6 +239,9 @@ fn unclosed_var_container() { format!( "{:#?}", VariableBlock { + constant: false, + access: AccessModifier::Protected, + retain: false, variable_block_type: VariableBlockType::Local, variables: vec![Variable { name: "a".into(), diff --git a/src/parser/tests/parse_errors/parse_error_statements_tests.rs b/src/parser/tests/parse_errors/parse_error_statements_tests.rs index a033f0ee22..47071d6f2b 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -283,6 +283,9 @@ fn invalid_variable_name_error_recovery() { format!( "{:#?}", VariableBlock { + constant: false, + access: AccessModifier::Protected, + retain: false, variables: vec![Variable { name: "c".into(), data_type: DataTypeDeclaration::DataTypeReference {