diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 34f01a631f..9696aaa1fb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -173,8 +173,14 @@ jobs: use-tool-cache: true - name: Generate coverage report - run: - grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore "src/lexer/tokens.rs" --ignore "src/main.rs" --ignore "src/parser/tests/*" --ignore-not-existing --ignore "/*" -o lcov.info + run: | + grcov . --binary-path ./target/debug/ -s . -t lcov --branch \ + --ignore "/*" \ + --ignore "src/main.rs" \ + --ignore "src/*/tests.rs" \ + --ignore "src/*/tests/*" \ + --ignore "src/lexer/tokens.rs" \ + --ignore-not-existing -o lcov.info - name: Upload to codecov.io diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 29a69e599d..8f646d36a9 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -614,6 +614,13 @@ fn pre_processing_generates_inline_structs_global() { //STRUCT //THEN an implicit datatype should have been generated for the struct let new_struct_type = &ast.types[0].data_type; + + if let DataType::StructType { variables, .. } = new_struct_type { + assert_eq!(variables[0].location, SourceRange::new(54..55)); + } else { + panic!("expected struct") + } + assert_eq!( &DataType::StructType { name: Some("__global_inline_struct".to_string()), @@ -693,6 +700,12 @@ fn pre_processing_generates_inline_structs() { //STRUCT //THEN an implicit datatype should have been generated for the struct let new_struct_type = &ast.types[0].data_type; + if let DataType::StructType { variables, .. } = new_struct_type { + assert_eq!(variables[0].location, SourceRange::new(67..68)); + } else { + panic!("expected struct") + } + assert_eq!( &DataType::StructType { name: Some("__foo_inline_struct".to_string()), diff --git a/src/parser.rs b/src/parser.rs index bf819df9fd..74718b3fdc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -350,12 +350,7 @@ fn parse_data_type_definition( ) -> Option { if lexer.allow(&KeywordStruct) { // Parse struct - let mut variables = Vec::new(); - while lexer.token == Identifier { - if let Some(variable) = parse_variable(lexer) { - variables.push(variable); - } - } + let variables = parse_variable_list(lexer); Some(( DataTypeDeclaration::DataTypeDefinition { data_type: DataType::StructType { name, variables }, @@ -585,13 +580,7 @@ fn parse_variable_block( //Consume the type keyword lexer.advance(); let variables = parse_any_in_region(lexer, vec![KeywordEndVar], |lexer| { - let mut variables = vec![]; - while lexer.token == Identifier { - if let Some(variable) = parse_variable(lexer) { - variables.push(variable); - } - } - variables + parse_variable_list(lexer) }); VariableBlock { variables, @@ -599,11 +588,37 @@ fn parse_variable_block( } } -fn parse_variable(lexer: &mut ParseSession) -> Option { - let variable_location = lexer.location(); - let name = lexer.slice_and_advance(); +fn parse_variable_list(lexer: &mut ParseSession) -> Vec { + let mut variables = vec![]; + while lexer.token == Identifier { + let mut line_vars = parse_variable_line(lexer); + variables.append(&mut line_vars); + } + variables +} + +fn parse_variable_line(lexer: &mut ParseSession) -> Vec { + // read in a comma separated list of variable names + let mut var_names: Vec<(String, SourceRange)> = vec![]; + while lexer.token == Identifier { + let location = lexer.location(); + let identifier_end = location.get_end(); + var_names.push((lexer.slice_and_advance(), location)); + + if lexer.token == KeywordColon { + break; + } + + if !lexer.allow(&KeywordComma) { + let next_token_start = lexer.location().get_start(); + lexer.accept_diagnostic(Diagnostic::missing_token( + format!("{:?} or {:?}", KeywordColon, KeywordComma), + SourceRange::new(identifier_end..next_token_start), + )); + } + } - //parse or recover until the colon + // colon has to come before the data type if !lexer.allow(&KeywordColon) { lexer.accept_diagnostic(Diagnostic::missing_token( format!("{:?}", KeywordColon), @@ -611,10 +626,17 @@ fn parse_variable(lexer: &mut ParseSession) -> Option { )); } - parse_full_data_type_definition(lexer, None).map(|(data_type, initializer)| Variable { - name, - data_type, - location: variable_location, - initializer, - }) + // create variables with the same data type for each of the names + let mut variables = vec![]; + if let Some((data_type, initializer)) = parse_full_data_type_definition(lexer, None) { + for (name, location) in var_names { + variables.push(Variable { + name, + data_type: data_type.clone(), + location, + initializer: initializer.clone(), + }); + } + } + variables } 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 4d97b7e273..a033f0ee22 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -313,6 +313,8 @@ fn invalid_variable_data_type_error_recovery() { VAR a DINT : ; c : INT; + h , , : INT; + f , INT : ; END_VAR END_PROGRAM "); @@ -323,12 +325,6 @@ fn invalid_variable_data_type_error_recovery() { format!("{:#?}", pou.variable_blocks[0]), r#"VariableBlock { variables: [ - Variable { - name: "a", - data_type: DataTypeReference { - referenced_type: "DINT", - }, - }, Variable { name: "c", data_type: DataTypeReference { @@ -343,12 +339,31 @@ fn invalid_variable_data_type_error_recovery() { assert_eq!( diagnostics, vec![ - Diagnostic::missing_token("KeywordColon".into(), SourceRange::new(54..58)), + Diagnostic::missing_token( + "KeywordColon or KeywordComma".into(), + SourceRange::new(53..54) + ), Diagnostic::unexpected_token_found( + "DataTypeDefinition".into(), "KeywordSemicolon".into(), - "':'".into(), - SourceRange::new(59..60) - ) + SourceRange::new(61..62) + ), + Diagnostic::missing_token("KeywordColon".into(), SourceRange::new(108..109)), + Diagnostic::unexpected_token_found( + "DataTypeDefinition".into(), + "KeywordComma".into(), + SourceRange::new(108..109) + ), + Diagnostic::unexpected_token_found( + "KeywordSemicolon".into(), + "', : INT'".into(), + SourceRange::new(108..115) + ), + Diagnostic::unexpected_token_found( + "DataTypeDefinition".into(), + "KeywordSemicolon".into(), + SourceRange::new(143..144) + ), ] ); } diff --git a/src/parser/tests/variable_parser_tests.rs b/src/parser/tests/variable_parser_tests.rs index 5844572786..a9be0b763b 100644 --- a/src/parser/tests/variable_parser_tests.rs +++ b/src/parser/tests/variable_parser_tests.rs @@ -41,6 +41,57 @@ fn global_vars_can_be_parsed() { assert_eq!(ast_string, expected_ast) } +#[test] +fn global_single_line_vars_can_be_parsed() { + let lexer = lex("VAR_GLOBAL x, y,z : INT; f : BOOL; b, c : SINT; END_VAR"); + let result = parse(lexer).0; + + let vars = &result.global_vars[0]; //globar_vars + let ast_string = format!("{:#?}", vars); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "y", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "z", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "f", + data_type: DataTypeReference { + referenced_type: "BOOL", + }, + }, + Variable { + name: "b", + data_type: DataTypeReference { + referenced_type: "SINT", + }, + }, + Variable { + name: "c", + data_type: DataTypeReference { + referenced_type: "SINT", + }, + }, + ], + variable_block_type: Global, +}"#; + assert_eq!(ast_string, expected_ast) +} + #[test] fn two_global_vars_can_be_parsed() { let lexer = lex("VAR_GLOBAL a: INT; END_VAR VAR_GLOBAL x : INT; y : BOOL; END_VAR");