diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index e999e959962..6cbad945833 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -618,6 +618,8 @@ pub enum AstStatement { ReturnStatement(ReturnStatement), JumpStatement(JumpStatement), LabelStatement(LabelStatement), + InlineVariable(InlineVariable), + DataTypeDeclaration(Box), } impl Debug for AstNode { @@ -734,6 +736,12 @@ impl Debug for AstNode { AstStatement::LabelStatement(LabelStatement { name, .. }) => { f.debug_struct("LabelStatement").field("name", name).finish() } + AstStatement::InlineVariable(InlineVariable { name, datatype }) => { + f.debug_struct("InlineVariable").field("name", name).field("datatype", datatype).finish() + } + AstStatement::DataTypeDeclaration(decl) => { + f.debug_tuple("DataTypeDeclaration").field(decl).finish() + } } } } @@ -1499,6 +1507,27 @@ impl AstFactory { pub fn create_label_statement(name: String, location: SourceLocation, id: AstId) -> AstNode { AstNode { stmt: AstStatement::LabelStatement(LabelStatement { name }), location, id } } + + /// Creates a new inline declaration by boxing the name and datatype + pub fn create_inline_declaration( + name: AstNode, + datatype: Option, + id: AstId, + location: SourceLocation, + ) -> AstNode { + let name = Box::new(name); + let datatype = datatype.map(Box::new); + AstNode { stmt: AstStatement::InlineVariable(InlineVariable { name, datatype }), id, location } + } + + pub fn create_type_declaration( + datatype: DataTypeDeclaration, + id: AstId, + location: SourceLocation, + ) -> AstNode { + let datatype = Box::new(datatype); + AstNode { stmt: AstStatement::DataTypeDeclaration(datatype), id, location } + } } #[derive(Clone, PartialEq)] pub struct EmptyStatement {} @@ -1581,3 +1610,10 @@ pub struct JumpStatement { pub struct LabelStatement { pub name: String, } + +/// Represents a new vaiable declaration in the body +#[derive(Clone, Debug, PartialEq)] +pub struct InlineVariable { + pub name: Box, + pub datatype: Option>, +} diff --git a/src/lexer/tests/lexer_tests.rs b/src/lexer/tests/lexer_tests.rs index 7e15621d699..eb15813e1c6 100644 --- a/src/lexer/tests/lexer_tests.rs +++ b/src/lexer/tests/lexer_tests.rs @@ -58,7 +58,7 @@ fn undefined_pragmas_are_ignored_by_the_lexer() { #[test] fn registered_pragmas_parsed() { let mut lexer = lex(r" - {external}{ref}{sized}{not_registerd} + {external}{ref}{sized}{def}{not_registerd} "); assert_eq!(lexer.token, PropertyExternal, "Token : {}", lexer.slice()); lexer.advance(); @@ -66,6 +66,8 @@ fn registered_pragmas_parsed() { lexer.advance(); assert_eq!(lexer.token, PropertySized, "Token : {}", lexer.slice()); lexer.advance(); + assert_eq!(lexer.token, PropertyDef, "Token : {}", lexer.slice()); + lexer.advance(); assert_eq!(lexer.token, End); } diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 02baa2a7e16..d8892f00d07 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -22,6 +22,9 @@ pub enum Token { #[token("{sized}")] PropertySized, + #[token("{def}")] + PropertyDef, + #[token("PROGRAM", ignore(case))] KeywordProgram, diff --git a/src/parser.rs b/src/parser.rs index 874fbccf292..c7cf8c9b217 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -338,7 +338,7 @@ fn parse_super_class(lexer: &mut ParseSession) -> Option { fn parse_return_type(lexer: &mut ParseSession, pou_type: &PouType) -> Option { let start_return_type = lexer.range().start; if lexer.try_consume(&KeywordColon) { - if let Some((declaration, initializer)) = parse_data_type_definition(lexer, None) { + if let Some((declaration, initializer)) = parse_datatype_with_initializer(lexer, None) { if let Some(init) = initializer { lexer.accept_diagnostic(Diagnostic::unexpected_initializer_on_function_return( init.get_location(), @@ -587,7 +587,7 @@ fn parse_full_data_type_definition( None, )) } else { - parse_data_type_definition(lexer, name).map(|(type_def, initializer)| { + parse_datatype_with_initializer(lexer, name).map(|(type_def, initializer)| { if lexer.try_consume(&KeywordDotDotDot) { ( DataTypeDeclaration::DataTypeDefinition { @@ -605,23 +605,29 @@ fn parse_full_data_type_definition( }) } -// TYPE xxx : 'STRUCT' | '(' | IDENTIFIER -fn parse_data_type_definition( +fn parse_datatype_with_initializer( lexer: &mut ParseSession, name: Option, ) -> Option { + parse_data_type_definition(lexer, name).map(|type_def| { + let initializer = + if lexer.try_consume(&KeywordAssignment) { Some(parse_expression(lexer)) } else { None }; + + (type_def, initializer) + }) +} + +// TYPE xxx : 'STRUCT' | '(' | IDENTIFIER +fn parse_data_type_definition(lexer: &mut ParseSession, name: Option) -> Option { let start = lexer.location(); if lexer.try_consume(&KeywordStruct) { // Parse struct let variables = parse_variable_list(lexer); - Some(( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::StructType { name, variables }, - location: start.span(&lexer.location()), - scope: lexer.scope.clone(), - }, - None, - )) + Some(DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::StructType { name, variables }, + location: start.span(&lexer.location()), + scope: lexer.scope.clone(), + }) } else if lexer.try_consume(&KeywordArray) { parse_array_type_definition(lexer, name) } else if lexer.try_consume(&KeywordPointer) { @@ -661,23 +667,18 @@ fn parse_pointer_definition( lexer: &mut ParseSession, name: Option, start_pos: usize, -) -> Option<(DataTypeDeclaration, Option)> { - parse_data_type_definition(lexer, None).map(|(decl, initializer)| { - ( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::PointerType { name, referenced_type: Box::new(decl) }, - location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), - scope: lexer.scope.clone(), - }, - initializer, - ) +) -> Option { + parse_data_type_definition(lexer, None).map(|decl| DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::PointerType { name, referenced_type: Box::new(decl) }, + location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), + scope: lexer.scope.clone(), }) } fn parse_type_reference_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let start = lexer.range().start; //Subrange let referenced_type = lexer.slice_and_advance(); @@ -692,9 +693,6 @@ fn parse_type_reference_type_definition( None }; - let initial_value = - if lexer.try_consume(&KeywordAssignment) { Some(parse_expression(lexer)) } else { None }; - let end = lexer.last_range.end; if name.is_some() || bounds.is_some() { let data_type = match bounds { @@ -732,15 +730,12 @@ fn parse_type_reference_type_definition( scope: lexer.scope.clone(), }, }; - Some((data_type, initial_value)) + Some(data_type) } else { - Some(( - DataTypeDeclaration::DataTypeReference { - referenced_type, - location: lexer.source_range_factory.create_range(start..end), - }, - initial_value, - )) + Some(DataTypeDeclaration::DataTypeReference { + referenced_type, + location: lexer.source_range_factory.create_range(start..end), + }) } } @@ -778,7 +773,7 @@ fn parse_string_size_expression(lexer: &mut ParseSession) -> Option { fn parse_string_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let text = lexer.slice().to_string(); let start = lexer.range().start; let is_wide = lexer.token == KeywordWideString; @@ -805,34 +800,26 @@ fn parse_string_type_definition( }), _ => Some(DataTypeDeclaration::DataTypeReference { referenced_type: text, location }), } - .zip(Some(lexer.try_consume(&KeywordAssignment).then(|| parse_expression(lexer)))) } -fn parse_enum_type_definition( - lexer: &mut ParseSession, - name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +fn parse_enum_type_definition(lexer: &mut ParseSession, name: Option) -> Option { let start = lexer.last_location(); let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { // Parse Enum - we expect at least one element let elements = parse_expression_list(lexer); Some(elements) })?; - let initializer = lexer.try_consume(&KeywordAssignment).then(|| parse_expression(lexer)); - Some(( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::EnumType { name, elements, numeric_type: DINT_TYPE.to_string() }, - location: start.span(&lexer.last_location()), - scope: lexer.scope.clone(), - }, - initializer, - )) + Some(DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::EnumType { name, elements, numeric_type: DINT_TYPE.to_string() }, + location: start.span(&lexer.last_location()), + scope: lexer.scope.clone(), + }) } fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let start = lexer.last_range.start; let range = parse_any_in_region(lexer, vec![KeywordOf], |lexer| { // Parse Array range @@ -849,7 +836,7 @@ fn parse_array_type_definition( })?; let inner_type_defintion = parse_data_type_definition(lexer, None); - inner_type_defintion.map(|(reference, initializer)| { + inner_type_defintion.map(|reference| { let reference_end = reference.get_location().to_range().map(|it| it.end).unwrap_or(0); let location = lexer.source_range_factory.create_range(start..reference_end); @@ -876,19 +863,16 @@ fn parse_array_type_definition( } }; - ( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::ArrayType { - name, - bounds: range, - referenced_type: Box::new(reference), - is_variable_length, - }, - location, - scope: lexer.scope.clone(), + DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::ArrayType { + name, + bounds: range, + referenced_type: Box::new(reference), + is_variable_length, }, - initializer, - ) + location, + scope: lexer.scope.clone(), + } }) } diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index 2c6295af57c..599d95ac057 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -15,7 +15,7 @@ use plc_source::source_location::SourceLocation; use regex::{Captures, Regex}; use std::{ops::Range, str::FromStr}; -use super::parse_hardware_access; +use super::{parse_data_type_definition, parse_hardware_access}; macro_rules! parse_left_associative_expression { ($lexer: expr, $action : expr, @@ -277,6 +277,12 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Result parse_null_literal(lexer), KeywordSquareParensOpen => parse_array_literal(lexer), DirectAccess(access) => parse_direct_access(lexer, access), + PropertyDef => { + //Just consume the {def} and go further, if it's a variable we parse it next + lexer.advance(); + parse_atomic_leaf_expression(lexer) + } + KeywordVar => parse_inline_declaration(lexer), _ => { if lexer.closing_keywords.contains(&vec![KeywordParensClose]) && matches!(lexer.last_token, KeywordOutputAssignment | KeywordAssignment) @@ -292,6 +298,27 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Result) -> Result { + //Consume the direct access + let location = lexer.location(); + //Inline variable declaration + lexer.advance(); + //Parse the name + let name = parse_identifier(lexer); + let datatype = if lexer.try_consume(&KeywordColon) { + //Parse datatype + let type_location = lexer.location(); + parse_data_type_definition(lexer, None).map(|it| { + AstFactory::create_type_declaration(it, lexer.next_id(), type_location.span(&lexer.location())) + }) + } else { + None + }; + let location = location.span(&lexer.last_location()); + + Ok(AstFactory::create_inline_declaration(name, datatype, lexer.next_id(), location)) +} + fn parse_identifier(lexer: &mut ParseSession<'_>) -> AstNode { AstFactory::create_identifier(&lexer.slice_and_advance(), &lexer.last_location(), lexer.next_id()) } diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 8ee0e295b62..08a03df0ab4 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -11,6 +11,7 @@ mod control_parser_tests; mod expressions_parser_tests; mod function_parser_tests; mod initializer_parser_tests; +mod inline_variable_tests; mod misc_parser_tests; mod parse_errors; mod parse_generics; diff --git a/src/parser/tests/inline_variable_tests.rs b/src/parser/tests/inline_variable_tests.rs new file mode 100644 index 00000000000..a0e104a17e4 --- /dev/null +++ b/src/parser/tests/inline_variable_tests.rs @@ -0,0 +1,73 @@ +use crate::test_utils::tests::parse; + +#[test] +fn inline_variable_declaration_on_new_line_with_def_pragma() { + let (result, ..) = parse( + r#" + PROGRAM main + {def} VAR x : DINT; + VAR y := 1; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_block_still_parsed_without_pragma() { + let (result, ..) = parse( + r#" + PROGRAM main + VAR x : DINT; END_VAR; + {def} VAR y := 1; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn when_already_in_body_pragma_optional() { + let (result, ..) = parse( + r#" + PROGRAM main + VAR x : DINT; END_VAR + x := 10; + VAR y := 1; + y := 10; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_declared_in_for_loop() { + let (result, ..) = parse( + r#" + PROGRAM main + FOR VAR x := 0 TO 10 DO + END_FOR + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_declared_in_inner_scope() { + let (result, ..) = parse( + r#" + PROGRAM main + FOR VAR x := 0 TO 10 DO + VAR y := 5; + END_FOR + + IF true THEN + VAR y := 10; + END_IF + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap new file mode 100644 index 00000000000..08a804bf535 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap @@ -0,0 +1,79 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + InlineVariable { + name: Identifier { + name: "x", + }, + datatype: Some( + DataTypeDeclaration( + DataTypeReference { + referenced_type: "DINT", + }, + ), + ), + }, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 4, + column: 23, + offset: 106, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap new file mode 100644 index 00000000000..4052dd2741d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap @@ -0,0 +1,80 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Local, + }, + ], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + EmptyStatement, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 33, + offset: 60, + }..TextLocation { + line: 4, + column: 23, + offset: 115, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap new file mode 100644 index 00000000000..1e813b7b860 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap @@ -0,0 +1,72 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + ForLoopStatement { + counter: InlineVariable { + name: Identifier { + name: "x", + }, + datatype: None, + }, + start: LiteralInteger { + value: 0, + }, + end: LiteralInteger { + value: 10, + }, + by_step: None, + body: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 4, + column: 23, + offset: 106, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap new file mode 100644 index 00000000000..017d0ad171e --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap @@ -0,0 +1,107 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + ForLoopStatement { + counter: InlineVariable { + name: Identifier { + name: "x", + }, + datatype: None, + }, + start: LiteralInteger { + value: 0, + }, + end: LiteralInteger { + value: 10, + }, + by_step: None, + body: [ + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 5, + }, + }, + ], + }, + IfStatement { + blocks: [ + ConditionalBlock { + condition: LiteralBool { + value: true, + }, + body: [ + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + ], + }, + ], + else_block: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 9, + column: 23, + offset: 208, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap new file mode 100644 index 00000000000..be29738c2ef --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap @@ -0,0 +1,105 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Local, + }, + ], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 3, + column: 12, + offset: 73, + }..TextLocation { + line: 6, + column: 23, + offset: 150, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +}