From 3c36dd763ffd370adbcb0e48d7825c5a8367c074 Mon Sep 17 00:00:00 2001 From: Mathias Rieder Date: Wed, 30 Aug 2023 09:01:35 +0200 Subject: [PATCH] refactor ast representation of qualified/array/ptr references (#951) * first implementation of member and access * improved reference parsing * fix control statements and tests * fix statement_parser_tests * fix type-definition tests (enums) * fix misc_parser tests * fix parse-error tests * working on parenthesized expressions * parsing works * we can parse everything again * improved parsing and resolving * improved handling of cast-expressions * improve codegen * fixed R-value generation of direct-access * assigning to direct access fixed * string literals * all tests working - yay * cleanup and simplifications * ADR * renamed AstStatement::Reference to AstStatement::Identifier * added validation test * review improvements --- compiler/plc_ast/src/ast.rs | 261 ++- compiler/plc_ast/src/pre_processor.rs | 44 +- .../plc_driver/src/tests/external_files.rs | 2 +- compiler/plc_xml/src/xml_parser/block.rs | 42 +- src/builtins.rs | 6 +- .../generators/expression_generator.rs | 608 +++---- src/codegen/generators/statement_generator.rs | 173 +- src/codegen/tests/code_gen_tests.rs | 31 + src/codegen/tests/function_tests.rs | 23 + ...__code_gen_tests__class_method_in_pou.snap | 18 +- ...sts__code_gen_tests__fb_method_in_pou.snap | 18 +- ..._program_with_casted_chars_assignment.snap | 2 + ...e_gen_tests__reference_qualified_name.snap | 12 +- ..._tests__typed_enums_are_used_properly.snap | 31 + ...tests__return_variable_in_nested_call.snap | 32 + ...ts__function_defined_in_external_file.snap | 2 +- ...program_with_casted_string_assignment.snap | 6 +- src/index.rs | 7 +- src/index/tests/index_tests.rs | 1 - ...pre_processing_generates_inline_enums.snap | 70 +- ...cessing_generates_inline_enums_global.snap | 72 +- src/index/visitor.rs | 2 +- src/lexer/tests/lexer_tests.rs | 6 +- src/parser.rs | 8 +- src/parser/expressions_parser.rs | 354 ++-- src/parser/tests.rs | 13 +- src/parser/tests/control_parser_tests.rs | 432 +---- src/parser/tests/expressions_parser_tests.rs | 1449 ++--------------- src/parser/tests/initializer_parser_tests.rs | 32 +- src/parser/tests/misc_parser_tests.rs | 236 +-- .../parse_error_containers_tests.rs | 64 +- .../parse_error_literals_tests.rs | 3 +- .../parse_error_statements_tests.rs | 502 ++++-- ...function_return_type_with_initializer.snap | 14 + ...h_illegal_return_variable_declaration.snap | 14 + ...ainers_tests__illegal_end_pou_keyword.snap | 14 + ...or_containers_tests__missing_pou_name.snap | 14 + ..._containers_tests__missing_pou_name_2.snap | 15 + ...h_illegal_return_variable_declaration.snap | 14 + ...s_tests__missing_semicolon_after_call.snap | 17 + ..._statement_with_else_and_no_condition.snap | 16 + ...se_statement_with_multiple_conditions.snap | 78 + ...th_multiple_expressions_per_condition.snap | 69 + ...ts__case_statement_with_no_conditions.snap | 16 + ...ts__case_statement_with_one_condition.snap | 32 + ...tement_with_one_condition_and_an_else.snap | 41 + ...th_one_condition_with_trailling_comma.snap | 32 + ..._with_one_empty_condition_and_an_else.snap | 32 + ...parser_tests__for_with_body_statement.snap | 49 + ...er_tests__for_with_literals_statement.snap | 27 + ...r_tests__for_with_reference_statement.snap | 32 + ...parser_tests__for_with_step_statement.snap | 26 + ...s__if_else_statement_with_expressions.snap | 33 + ...elsif_else_statement_with_expressions.snap | 73 + ...ser_tests__repeat_with_body_statement.snap | 27 + ..._parser_tests__repeat_with_expression.snap | 21 + ...rser_tests__while_with_body_statement.snap | 27 + ...l_parser_tests__while_with_expression.snap | 21 + ...sions_parser_tests__addition_ast_test.snap | 13 + ...ts__addition_compare_or_priority_test.snap | 35 + ...ts__additon_of_three_variables_parsed.snap | 34 + ...ests__additon_of_two_variables_parsed.snap | 141 ++ ...essions_parser_tests__amp_as_and_test.snap | 23 + ...r_tests__amp_as_and_with_address_test.snap | 28 + ...s__expressions_parser_tests__and_test.snap | 23 + ..._parser_tests__arrays_can_be_parsed-2.snap | 57 + ...ests__arrays_in_structs_can_be_parsed.snap | 30 + ...ests__arrays_of_structs_can_be_parsed.snap | 30 + ...essions_parser_tests__assignment_test.snap | 38 + ...ions_parser_tests__assignment_to_null.snap | 15 + ...ber_reference_with_explicit_plus_sign.snap | 40 + ..._with_implicit_and_explicit_plus_sign.snap | 32 + ..._binary_stmts_of_two_variables_parsed.snap | 141 ++ ...s_parser_tests__bitwise_access_parsed.snap | 181 ++ ...er_tests__boolean_expression_ast_test.snap | 48 + ...ts__boolean_expression_param_ast_test.snap | 48 + ...tests__boolean_literals_can_be_parsed.snap | 13 + ...s_parser_tests__boolean_priority_test.snap | 45 + ...ser_tests__comparison_expression_test.snap | 93 ++ ...arser_tests__comparison_priority_test.snap | 35 + ...s__direct_access_as_expression_parsed.snap | 127 +- ...arser_tests__equality_expression_test.snap | 46 + ...s_parser_tests__exp_mul_priority_test.snap | 36 +- ...ser_tests__exponent_expressions_parse.snap | 9 +- ...essions_parser_tests__expression_list.snap | 17 + ...er_tests__expression_list_assignments.snap | 47 + ...er_tests__function_call_formal_params.snap | 64 + ...parser_tests__function_call_no_params.snap | 15 + ...ns_parser_tests__function_call_params.snap | 29 + ...tion_call_params_with_trailling_comma.snap | 29 + ...er_tests__function_call_return_params.snap | 39 + ..._with_underscore_number_can_be_parsed.snap | 7 + ...s_parser_tests__literal_can_be_parsed.snap | 7 + ...number_with_underscores_can_be_parsed.snap | 7 + ...parser_tests__literal_enum_parse_test.snap | 57 + ...sts__literal_hex_number_can_be_parsed.snap | 7 + ...number_with_underscores_can_be_parsed.snap | 7 + ..._number_with_underscore_can_be_parsed.snap | 7 + ...number_with_underscores_can_be_parsed.snap | 7 + ..._parser_tests__module_expression_test.snap | 13 + ...ests__multidim_arrays_can_be_parsed-2.snap | 71 + ...parser_tests__multiplication_ast_test.snap | 19 + ...sts__multiplication_expressions_parse.snap | 19 + ..._tests__nested_arrays_can_be_parsed-2.snap | 75 + ..._or_compare_expressions_priority_test.snap | 29 + ...expressions_should_not_change_the_ast.snap | 23 + ...er_tests__parenthesized_term_ast_test.snap | 25 + ...ns_parser_tests__pointer_address_test.snap | 17 + ...arser_tests__pointer_dereference_test.snap | 17 + ...ests__pointer_dereference_test_nested.snap | 64 + ..._qualified_reference_statement_parsed.snap | 21 + ...ssions_parser_tests__range_expression.snap | 66 + ...gned_literal_expression_reversed_test.snap | 13 + ...tests__signed_literal_expression_test.snap | 21 + ...rser_tests__signed_literal_minus_test.snap | 7 + ...parser_tests__single_statement_parsed.snap | 12 + ..._parser_tests__string_can_be_parsed-2.snap | 34 + ...pressions_parser_tests__term_ast_test.snap | 25 + ...er_tests__wide_string_can_be_parsed-2.snap | 34 + ...sts__struct_initializer_can_be_parsed.snap | 42 + ...rameter_assignments_in_call_statement.snap | 36 +- ...ct_and_enum_declaration_can_be_parsed.snap | 28 +- ...tests__simple_enum_type_can_be_parsed.snap | 30 +- ..._enum_with_numeric_type_can_be_parsed.snap | 30 +- ...ne_element_numeric_type_can_be_parsed.snap | 12 +- ...num_with_initial_values_can_be_parsed.snap | 30 +- ...num_with_initial_values_can_be_parsed.snap | 28 +- src/parser/tests/statement_parser_tests.rs | 19 +- src/resolver.rs | 629 ++++--- src/resolver/const_evaluator.rs | 51 +- src/resolver/tests/const_resolver_tests.rs | 3 - .../tests/resolve_expressions_tests.rs | 563 +++++-- src/resolver/tests/resolve_generic_calls.rs | 4 +- src/resolver/tests/resolve_literals_tests.rs | 60 +- ...omparison_resolves_to_function_call-2.snap | 39 +- ...omparison_resolves_to_function_call-3.snap | 74 +- ...omparison_resolves_to_function_call-4.snap | 39 +- ...omparison_resolves_to_function_call-5.snap | 74 +- ..._comparison_resolves_to_function_call.snap | 39 +- ...string_compare_should_resolve_to_bool.snap | 27 +- src/tests/adr.rs | 5 +- ...{annotated_ast.rs => annotated_ast_adr.rs} | 28 +- src/tests/adr/reference_expressions_adr.rs | 269 +++ src/tests/adr/structs_adr.rs | 4 +- src/tests/adr/util_macros.rs | 11 - src/typesystem.rs | 16 + src/validation.rs | 9 +- src/validation/statement.rs | 243 ++- .../tests/literals_validation_tests.rs | 4 +- ...on_tests__array_assignment_validation.snap | 2 +- ...tion_tests__bit_assignment_validation.snap | 4 +- ...ion_tests__char_assignment_validation.snap | 10 +- ...ion_tests__date_assignment_validation.snap | 6 +- ...tests__duration_assignment_validation.snap | 6 +- ...tion_tests__int_assignment_validation.snap | 8 +- ..._tests__pointer_assignment_validation.snap | 2 +- ...ion_tests__real_assignment_validation.snap | 4 +- ...n_tests__string_assignment_validation.snap | 6 +- ...ve_tests__resole_struct_member_access.snap | 6 - ...ith_incorrect_operator_causes_warning.snap | 2 +- ...__invalid_cast_statement_causes_error.snap | 6 + ...__switch_case_invalid_case_conditions.snap | 4 +- .../tests/statement_validation_tests.rs | 25 + 163 files changed, 6733 insertions(+), 3432 deletions(-) create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__typed_enums_are_used_properly.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__return_variable_in_nested_call.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_return_type_with_initializer.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_with_illegal_return_variable_declaration.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__illegal_end_pou_keyword.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name_2.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__program_with_illegal_return_variable_declaration.snap create mode 100644 src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_statements_tests__missing_semicolon_after_call.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_else_and_no_condition.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_conditions.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_expressions_per_condition.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_no_conditions.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_and_an_else.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_with_trailling_comma.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_empty_condition_and_an_else.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_body_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_literals_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_reference_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_step_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_else_statement_with_expressions.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_elsif_elsif_else_statement_with_expressions.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_body_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_expression.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_body_statement.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_expression.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_compare_or_priority_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_three_variables_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_two_variables_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_with_address_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__and_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_can_be_parsed-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_in_structs_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_of_structs_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_null.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_reference_with_explicit_plus_sign.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_with_implicit_and_explicit_plus_sign.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__binary_stmts_of_two_variables_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__bitwise_access_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_param_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_literals_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_priority_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_expression_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_priority_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__equality_expression_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list_assignments.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_formal_params.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_no_params.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params_with_trailling_comma.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_return_params.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_binary_with_underscore_number_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_dec_number_with_underscores_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_enum_parse_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_with_underscores_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscore_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscores_can_be_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__module_expression_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multidim_arrays_can_be_parsed-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_expressions_parse.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__nested_arrays_can_be_parsed-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__or_compare_expressions_priority_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesis_expressions_should_not_change_the_ast.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesized_term_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_address_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test_nested.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__qualified_reference_statement_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__range_expression.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_reversed_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_minus_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__single_statement_parsed.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__string_can_be_parsed-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__term_ast_test.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__wide_string_can_be_parsed-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__initializer_parser_tests__struct_initializer_can_be_parsed.snap rename src/tests/adr/{annotated_ast.rs => annotated_ast_adr.rs} (93%) create mode 100644 src/tests/adr/reference_expressions_adr.rs create mode 100644 src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__invalid_cast_statement_causes_error.snap diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 9718e07760..2d8e15e771 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -15,7 +15,6 @@ use crate::{ pre_processor, provider::IdProvider, }; - pub type AstId = usize; #[derive(Clone, Debug, PartialEq, Eq)] @@ -693,6 +692,30 @@ fn replace_reference( Some(*old_data_type) } +#[derive(Clone, PartialEq, Debug)] +pub enum ReferenceAccess { + /** + * a, a.b + */ + Member(Box), + /** + * a[3] + */ + Index(Box), + /** + * Color#Red + */ + Cast(Box), + /** + * a^ + */ + Deref, + /** + * &a + */ + Address, +} + #[derive(Clone, PartialEq)] pub enum AstStatement { EmptyStatement { @@ -724,24 +747,17 @@ pub enum AstStatement { id: AstId, }, // Expressions - QualifiedReference { - elements: Vec, + ReferenceExpr { + access: ReferenceAccess, + base: Option>, id: AstId, + location: SourceRange, }, - Reference { + Identifier { name: String, location: SourceRange, id: AstId, }, - ArrayAccess { - reference: Box, - access: Box, - id: AstId, - }, - PointerAccess { - reference: Box, - id: AstId, - }, DirectAccess { access: DirectAccessType, index: Box, @@ -829,9 +845,8 @@ impl Debug for AstStatement { AstStatement::EmptyStatement { .. } => f.debug_struct("EmptyStatement").finish(), AstStatement::DefaultValue { .. } => f.debug_struct("DefaultValue").finish(), AstStatement::Literal { kind, .. } => kind.fmt(f), - AstStatement::Reference { name, .. } => f.debug_struct("Reference").field("name", name).finish(), - AstStatement::QualifiedReference { elements, .. } => { - f.debug_struct("QualifiedReference").field("elements", elements).finish() + AstStatement::Identifier { name, .. } => { + f.debug_struct("Identifier").field("name", name).finish() } AstStatement::BinaryExpression { operator, left, right, .. } => f .debug_struct("BinaryExpression") @@ -903,12 +918,6 @@ impl Debug for AstStatement { .field("case_blocks", case_blocks) .field("else_block", else_block) .finish(), - AstStatement::ArrayAccess { reference, access, .. } => { - f.debug_struct("ArrayAccess").field("reference", reference).field("access", access).finish() - } - AstStatement::PointerAccess { reference, .. } => { - f.debug_struct("PointerAccess").field("reference", reference).finish() - } AstStatement::DirectAccess { access, index, .. } => { f.debug_struct("DirectAccess").field("access", access).field("index", index).finish() } @@ -933,6 +942,9 @@ impl Debug for AstStatement { AstStatement::CastStatement { target, type_name, .. } => { f.debug_struct("CastStatement").field("type_name", type_name).field("target", target).finish() } + AstStatement::ReferenceExpr { access, base, .. } => { + f.debug_struct("ReferenceExpr").field("kind", access).field("base", base).finish() + } } } } @@ -951,12 +963,7 @@ impl AstStatement { AstStatement::EmptyStatement { location, .. } => location.clone(), AstStatement::DefaultValue { location, .. } => location.clone(), AstStatement::Literal { location, .. } => location.clone(), - AstStatement::Reference { location, .. } => location.clone(), - AstStatement::QualifiedReference { elements, .. } => { - let first = elements.first().map_or_else(SourceRange::undefined, |it| it.get_location()); - let last = elements.last().map_or_else(SourceRange::undefined, |it| it.get_location()); - first.span(&last) - } + AstStatement::Identifier { location, .. } => location.clone(), AstStatement::BinaryExpression { left, right, .. } => { let left_loc = left.get_location(); let right_loc = right.get_location(); @@ -986,12 +993,6 @@ impl AstStatement { } AstStatement::CallStatement { location, .. } => location.clone(), AstStatement::ControlStatement { location, .. } => location.clone(), - AstStatement::ArrayAccess { reference, access, .. } => { - let reference_loc = reference.get_location(); - let access_loc = access.get_location(); - reference_loc.span(&access_loc) - } - AstStatement::PointerAccess { reference, .. } => reference.get_location(), AstStatement::DirectAccess { location, .. } => location.clone(), AstStatement::HardwareAccess { location, .. } => location.clone(), AstStatement::MultipliedStatement { location, .. } => location.clone(), @@ -1000,6 +1001,7 @@ impl AstStatement { AstStatement::ContinueStatement { location, .. } => location.clone(), AstStatement::ExitStatement { location, .. } => location.clone(), AstStatement::CastStatement { location, .. } => location.clone(), + AstStatement::ReferenceExpr { location, .. } => location.clone(), } } @@ -1009,10 +1011,7 @@ impl AstStatement { AstStatement::DefaultValue { id, .. } => *id, AstStatement::Literal { id, .. } => *id, AstStatement::MultipliedStatement { id, .. } => *id, - AstStatement::QualifiedReference { id, .. } => *id, - AstStatement::Reference { id, .. } => *id, - AstStatement::ArrayAccess { id, .. } => *id, - AstStatement::PointerAccess { id, .. } => *id, + AstStatement::Identifier { id, .. } => *id, AstStatement::DirectAccess { id, .. } => *id, AstStatement::HardwareAccess { id, .. } => *id, AstStatement::BinaryExpression { id, .. } => *id, @@ -1029,15 +1028,20 @@ impl AstStatement { AstStatement::ContinueStatement { id, .. } => *id, AstStatement::ExitStatement { id, .. } => *id, AstStatement::CastStatement { id, .. } => *id, + AstStatement::ReferenceExpr { id, .. } => *id, } } /// Returns true if the current statement has a direct access. pub fn has_direct_access(&self) -> bool { - if let AstStatement::QualifiedReference { elements, .. } = self { - matches!(elements.last(), Some(AstStatement::DirectAccess { .. })) - } else { - false + match self { + AstStatement::ReferenceExpr { access: ReferenceAccess::Member(reference), base, .. } + | AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(reference), base, .. } => { + reference.has_direct_access() + || base.as_ref().map(|it| it.has_direct_access()).unwrap_or(false) + } + AstStatement::DirectAccess { .. } => true, + _ => false, } } @@ -1047,18 +1051,44 @@ impl AstStatement { // TODO: figure out a better name for this... match self { AstStatement::Literal { kind, .. } => kind.is_cast_prefix_eligible(), - AstStatement::Reference { .. } => true, + AstStatement::Identifier { .. } => true, _ => false, } } - /// Returns true if the current statement is a reference - pub fn is_reference(&self) -> bool { - matches!(self, AstStatement::Reference { .. }) + /// Returns true if the current statement is a flat reference (e.g. `a`) + pub fn is_flat_reference(&self) -> bool { + matches!(self, AstStatement::Identifier { .. }) || { + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Member(reference), + base: None, + .. + } = self + { + matches!(reference.as_ref(), AstStatement::Identifier { .. }) + } else { + false + } + } + } + + /// Returns the reference-name if this is a flat reference like `a`, or None if this is no flat reference + pub fn get_flat_reference_name(&self) -> Option<&str> { + match self { + AstStatement::ReferenceExpr { access: ReferenceAccess::Member(reference), .. } => { + if let AstStatement::Identifier { name, .. } = reference.as_ref() { + Some(name) + } else { + None + } + } + AstStatement::Identifier { name, .. } => Some(name), + _ => None, + } } - pub fn is_qualified_reference(&self) -> bool { - matches!(self, AstStatement::QualifiedReference { .. }) + pub fn is_reference(&self) -> bool { + matches!(self, AstStatement::ReferenceExpr { .. }) } pub fn is_hardware_access(&self) -> bool { @@ -1066,25 +1096,17 @@ impl AstStatement { } pub fn is_array_access(&self) -> bool { - if let AstStatement::QualifiedReference { elements, .. } = self { - matches!(elements.last(), Some(AstStatement::ArrayAccess { .. })) - } else { - matches!(self, AstStatement::ArrayAccess { .. }) - } + matches!(self, AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), .. }) } pub fn is_pointer_access(&self) -> bool { - if let AstStatement::QualifiedReference { elements, .. } = self { - matches!(elements.last(), Some(AstStatement::PointerAccess { .. })) - } else { - matches!(self, AstStatement::PointerAccess { .. }) - } + matches!(self, AstStatement::ReferenceExpr { access: ReferenceAccess::Deref, .. }) } pub fn can_be_assigned_to(&self) -> bool { self.has_direct_access() + || self.is_flat_reference() || self.is_reference() - || self.is_qualified_reference() || self.is_array_access() || self.is_pointer_access() || self.is_hardware_access() @@ -1130,6 +1152,10 @@ impl AstStatement { pub fn is_literal_array(&self) -> bool { matches!(self, AstStatement::Literal { kind: AstLiteral::Array(..), .. }) } + + pub fn is_literal(&self) -> bool { + matches!(self, AstStatement::Literal { .. }) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -1150,7 +1176,6 @@ pub enum Operator { And, Or, Xor, - Address, } impl Display for Operator { @@ -1174,7 +1199,7 @@ impl Display for Operator { pub fn get_enum_element_names(enum_elements: &AstStatement) -> Vec { flatten_expression_list(enum_elements) .into_iter() - .filter(|it| matches!(it, AstStatement::Reference { .. } | AstStatement::Assignment { .. })) + .filter(|it| matches!(it, AstStatement::Identifier { .. } | AstStatement::Assignment { .. })) .map(get_enum_element_name) .collect() } @@ -1182,14 +1207,11 @@ pub fn get_enum_element_names(enum_elements: &AstStatement) -> Vec { /// expects a Reference or an Assignment pub fn get_enum_element_name(enum_element: &AstStatement) -> String { match enum_element { - AstStatement::Reference { name, .. } => name.to_string(), - AstStatement::Assignment { left, .. } => { - if let AstStatement::Reference { name, .. } = left.as_ref() { - name.to_string() - } else { - unreachable!("left of assignment not a reference") - } - } + AstStatement::Identifier { name, .. } => name.to_string(), + AstStatement::Assignment { left, .. } => left + .get_flat_reference_name() + .map(|it| it.to_string()) + .expect("left of assignment not a reference"), _ => { unreachable!("expected {:?} to be a Reference or Assignment", enum_element); } @@ -1271,6 +1293,10 @@ mod tests { pub struct AstFactory {} impl AstFactory { + pub fn empty_statement(location: SourceRange, id: AstId) -> AstStatement { + AstStatement::EmptyStatement { location, id } + } + /// creates a new if-statement pub fn create_if_statement( blocks: Vec, @@ -1375,9 +1401,67 @@ impl AstFactory { } } - /// creates a new reference - pub fn create_reference(name: &str, location: &SourceRange, id: AstId) -> AstStatement { - AstStatement::Reference { id, location: location.clone(), name: name.to_string() } + /// creates a new Identifier + pub fn create_identifier(name: &str, location: &SourceRange, id: AstId) -> AstStatement { + AstStatement::Identifier { id, location: location.clone(), name: name.to_string() } + } + + pub fn create_member_reference( + member: AstStatement, + base: Option, + id: AstId, + ) -> AstStatement { + let location = base + .as_ref() + .map(|it| it.get_location().span(&member.get_location())) + .unwrap_or_else(|| member.get_location()); + AstStatement::ReferenceExpr { + access: ReferenceAccess::Member(Box::new(member)), + base: base.map(Box::new), + id, + location, + } + } + + pub fn create_index_reference( + index: AstStatement, + base: Option, + id: AstId, + location: SourceRange, + ) -> AstStatement { + AstStatement::ReferenceExpr { + access: ReferenceAccess::Index(Box::new(index)), + base: base.map(Box::new), + id, + location, + } + } + + pub fn create_address_of_reference(base: AstStatement, id: AstId, location: SourceRange) -> AstStatement { + AstStatement::ReferenceExpr { + access: ReferenceAccess::Address, + base: Some(Box::new(base)), + id, + location, + } + } + + pub fn create_deref_reference(base: AstStatement, id: AstId, location: SourceRange) -> AstStatement { + AstStatement::ReferenceExpr { + access: ReferenceAccess::Deref, + base: Some(Box::new(base)), + id, + location, + } + } + + pub fn create_direct_access( + access: DirectAccessType, + index: AstStatement, + id: AstId, + location: SourceRange, + ) -> AstStatement { + AstStatement::DirectAccess { access, index: Box::new(index), location, id } } /// creates a new binary statement @@ -1392,16 +1476,17 @@ impl AstFactory { /// creates a new cast statement pub fn create_cast_statement( - type_name: &str, + type_name: AstStatement, stmt: AstStatement, location: &SourceRange, id: AstId, ) -> AstStatement { - AstStatement::CastStatement { + let new_location = (location.get_start()..stmt.get_location().get_end()).into(); + AstStatement::ReferenceExpr { + access: ReferenceAccess::Cast(Box::new(stmt)), + base: Some(Box::new(type_name)), id, - location: location.clone(), - type_name: type_name.to_string(), - target: Box::new(stmt), + location: new_location, } } @@ -1414,11 +1499,11 @@ impl AstFactory { location: &SourceRange, ) -> AstStatement { AstStatement::CallStatement { - operator: Box::new(AstStatement::Reference { - name: function_name, - location: location.clone(), + operator: Box::new(AstFactory::create_member_reference( + AstFactory::create_identifier(&function_name, location, id), + None, id, - }), + )), parameters: Box::new(Some(AstStatement::ExpressionList { expressions: parameters, id: parameter_list_id, @@ -1429,17 +1514,17 @@ impl AstFactory { } pub fn create_call_to_with_ids( - function_name: String, + function_name: &str, parameters: Vec, location: &SourceRange, mut id_provider: IdProvider, ) -> AstStatement { AstStatement::CallStatement { - operator: Box::new(AstStatement::Reference { - name: function_name, - location: location.clone(), - id: id_provider.next_id(), - }), + operator: Box::new(AstFactory::create_member_reference( + AstFactory::create_identifier(function_name, location, id_provider.next_id()), + None, + id_provider.next_id(), + )), parameters: Box::new(Some(AstStatement::ExpressionList { expressions: parameters, id: id_provider.next_id(), @@ -1450,7 +1535,7 @@ impl AstFactory { } pub fn create_call_to_check_function_ast( - check_function_name: String, + check_function_name: &str, parameter: AstStatement, sub_range: Range, location: &SourceRange, diff --git a/compiler/plc_ast/src/pre_processor.rs b/compiler/plc_ast/src/pre_processor.rs index b250fe2c41..93143ab934 100644 --- a/compiler/plc_ast/src/pre_processor.rs +++ b/compiler/plc_ast/src/pre_processor.rs @@ -91,33 +91,40 @@ pub fn pre_process(unit: &mut CompilationUnit, mut id_provider: IdProvider) { if !matches!(original_elements, AstStatement::EmptyStatement { .. }) => { let mut last_name: Option = None; + + fn extract_flat_ref_name(statement: &AstStatement) -> &str { + statement.get_flat_reference_name().expect("expected assignment") + } + let initialized_enum_elements = flatten_expression_list(original_elements) .iter() .map(|it| match it { - AstStatement::Reference { name, .. } => (name.clone(), None, it.get_location()), AstStatement::Assignment { left, right, .. } => { - let name = if let AstStatement::Reference { name, .. } = left.as_ref() { - name.clone() - } else { - unreachable!("expected reference, got {:?}", left.as_ref()) - }; // - (name, Some(*right.clone()), it.get_location()) + ( + extract_flat_ref_name(left.as_ref()), + Some(*right.clone()), + it.get_location(), + ) } - _ => unreachable!("expected assignment, got {:?}", it), + _ => (extract_flat_ref_name(it), None, it.get_location()), }) .map(|(element_name, initializer, location)| { let enum_literal = initializer.unwrap_or_else(|| { build_enum_initializer(&last_name, &location, &mut id_provider, enum_name) }); - last_name = Some(element_name.clone()); + last_name = Some(element_name.to_string()); AstStatement::Assignment { id: id_provider.next_id(), - left: Box::new(AstStatement::Reference { - id: id_provider.next_id(), - name: element_name, - location, - }), + left: Box::new(AstFactory::create_member_reference( + AstFactory::create_identifier( + element_name, + &location, + id_provider.next_id(), + ), + None, + id_provider.next_id(), + )), right: Box::new(enum_literal), } }) @@ -147,9 +154,14 @@ fn build_enum_initializer( ) -> AstStatement { if let Some(last_element) = last_name.as_ref() { // generate a `enum#last + 1` statement - let enum_ref = AstFactory::create_reference(last_element, location, id_provider.next_id()); + let enum_ref = AstFactory::create_identifier(last_element, location, id_provider.next_id()); + let type_element = AstFactory::create_member_reference( + AstFactory::create_identifier(enum_name, location, id_provider.next_id()), + None, + id_provider.next_id(), + ); AstFactory::create_binary_expression( - AstFactory::create_cast_statement(enum_name, enum_ref, location, id_provider.next_id()), + AstFactory::create_cast_statement(type_element, enum_ref, location, id_provider.next_id()), Operator::Plus, AstStatement::new_literal(AstLiteral::new_integer(1), id_provider.next_id(), location.clone()), id_provider.next_id(), diff --git a/compiler/plc_driver/src/tests/external_files.rs b/compiler/plc_driver/src/tests/external_files.rs index 3f9fbe98e7..6dae410494 100644 --- a/compiler/plc_driver/src/tests/external_files.rs +++ b/compiler/plc_driver/src/tests/external_files.rs @@ -80,7 +80,7 @@ fn calling_external_file_function_without_including_file_results_in_error() { if let Err(msg) = res { assert_eq!( Diagnostic::codegen_error( - r#"cannot generate call statement for "Reference { name: \"external\" }""#, + r#"cannot generate call statement for "ReferenceExpr { kind: Member(Identifier { name: \"external\" }), base: None }""#, SourceRange::in_file(30..38, "external_file.st") ), msg diff --git a/compiler/plc_xml/src/xml_parser/block.rs b/compiler/plc_xml/src/xml_parser/block.rs index 3c544fb5f0..546394636b 100644 --- a/compiler/plc_xml/src/xml_parser/block.rs +++ b/compiler/plc_xml/src/xml_parser/block.rs @@ -1,4 +1,4 @@ -use ast::ast::{AstStatement, SourceRange}; +use ast::ast::{AstFactory, AstStatement, SourceRange}; use crate::model::{block::Block, fbd::NodeIndex}; @@ -6,33 +6,21 @@ use super::ParseSession; impl Block { pub(crate) fn transform(&self, session: &ParseSession, index: &NodeIndex) -> AstStatement { - let operator = Box::new(AstStatement::Reference { - name: self.type_name.clone(), - location: SourceRange::undefined(), - id: session.next_id(), - }); + let parameters = self + .variables + .iter() + .filter_map(|var| { + // try to transform the element this block variable points to + var.transform(session, index) + }) + .collect(); - let parameters = if !self.variables.is_empty() { - Box::new(Some(AstStatement::ExpressionList { - expressions: self - .variables - .iter() - .filter_map(|var| { - // try to transform the element this block variable points to - var.transform(session, index) - }) - .collect(), - id: session.next_id(), - })) - } else { - Box::new(None) - }; - - AstStatement::CallStatement { - operator, + AstFactory::create_call_to( + self.type_name.clone(), parameters, - location: SourceRange::undefined(), - id: session.next_id(), - } + session.next_id(), + session.next_id(), + &SourceRange::undefined(), + ) } } diff --git a/src/builtins.rs b/src/builtins.rs index d82a96fa16..2cbeeb0178 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -47,7 +47,7 @@ lazy_static! { code: |generator, params, location| { if let [reference] = params { generator - .generate_element_pointer(reference) + .generate_lvalue(reference) .map(|it| ExpressionValue::RValue(generator.ptr_as_value(it))) } else { Err(Diagnostic::codegen_error( @@ -106,7 +106,7 @@ lazy_static! { code: |generator, params, location| { if let [reference] = params { generator - .generate_element_pointer(reference) + .generate_lvalue(reference) .map(|it| ExpressionValue::RValue(it.as_basic_value_enum())) } else { Err(Diagnostic::codegen_error( @@ -431,7 +431,7 @@ fn generate_variable_length_array_bound_function<'ink>( )); }; - let vla = generator.generate_element_pointer(params[0]).unwrap(); + let vla = generator.generate_lvalue(params[0]).unwrap(); let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); let accessor = match params[1] { diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 0766d55d01..4a64d9654c 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -11,8 +11,8 @@ use crate::{ }, resolver::{AnnotationMap, AstAnnotations, StatementAnnotation}, typesystem::{ - is_same_type_class, DataType, DataTypeInformation, Dimension, StringEncoding, VarArgs, DINT_TYPE, - INT_SIZE, INT_TYPE, LINT_TYPE, + is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension, + StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE, }, }; use inkwell::{ @@ -25,7 +25,10 @@ use inkwell::{ AddressSpace, FloatPredicate, IntPredicate, }; use plc_ast::{ - ast::{flatten_expression_list, AstStatement, DirectAccessType, Operator, SourceRange}, + ast::{ + flatten_expression_list, AstFactory, AstStatement, DirectAccessType, Operator, ReferenceAccess, + SourceRange, + }, literals::AstLiteral, }; use plc_diagnostics::diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR}; @@ -33,7 +36,6 @@ use plc_util::convention::qualified_name; use std::{collections::HashSet, vec}; use super::{llvm::Llvm, statement_generator::FunctionContext, ADDRESS_SPACE_CONST, ADDRESS_SPACE_GENERIC}; - /// the generator for expressions pub struct ExpressionCodeGenerator<'a, 'b> { pub llvm: &'b Llvm<'a>, @@ -191,47 +193,34 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { expression: &AstStatement, ) -> Result, Diagnostic> { //see if this is a constant - maybe we can short curcuit this codegen - if let Some(StatementAnnotation::Variable { qualified_name, .. }) = self.annotations.get(expression) { - if let Some(basic_value_enum) = self.llvm_index.find_constant_value(qualified_name) { - //this is a constant and we have a value for it - return Ok(ExpressionValue::RValue(basic_value_enum)); + if let Some(StatementAnnotation::Variable { + qualified_name, constant: true, resulting_type, .. + }) = self.annotations.get(expression) + { + if !self.index.get_type_information_or_void(resulting_type).is_aggregate() { + // constant propagation + return self.generate_constant_expression(qualified_name, expression); } } // generate the expression match expression { - AstStatement::Reference { .. } => { - if let Some(StatementAnnotation::Variable { - qualified_name, - resulting_type, - constant: true, - .. - }) = self.annotations.get(expression) - { - if self.index.get_type_information_or_void(resulting_type).is_aggregate() { - self.generate_element_pointer(expression).map(ExpressionValue::LValue) - } else { - // constant propagation - self.generate_constant_expression(qualified_name, expression) + AstStatement::ReferenceExpr { access, base, .. } => { + let res = self.generate_reference_expression(access, base.as_deref(), expression)?; + let val = match res { + ExpressionValue::LValue(val) => { + ExpressionValue::LValue(self.auto_deref_if_necessary(val, expression)) } - } else { - // general reference generation - self.generate_element_pointer(expression).map(ExpressionValue::LValue) - } - } - AstStatement::QualifiedReference { elements, .. } => { - //If direct access, don't load pointers - if expression.has_direct_access() { - //Split the qualified reference at the last element - self.generate_directaccess(elements).map(ExpressionValue::RValue) - } else { - self.generate_element_pointer(expression).map(ExpressionValue::LValue) - } - } - AstStatement::ArrayAccess { .. } => { - self.generate_element_pointer(expression).map(ExpressionValue::LValue) - } - AstStatement::PointerAccess { .. } => { - self.generate_element_pointer(expression).map(ExpressionValue::LValue) + ExpressionValue::RValue(val) => { + let val = if val.is_pointer_value() { + self.auto_deref_if_necessary(val.into_pointer_value(), expression) + .as_basic_value_enum() + } else { + val + }; + ExpressionValue::RValue(val) + } + }; + Ok(val) } AstStatement::BinaryExpression { left, right, operator, .. } => self .generate_binary_expression(left, right, operator, expression) @@ -324,55 +313,6 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } } - fn generate_directaccess(&self, elements: &[AstStatement]) -> Result, Diagnostic> { - let (expression, last) = match elements { - [qualifier, last] => { - // a.%w1 - (qualifier.clone(), last) - } - [qualifier @ .., last_qualifier, last] => { - // a.b.c.%w1 - let id = last_qualifier.get_id(); - ( - AstStatement::QualifiedReference { - elements: [qualifier, &[last_qualifier.clone()]].concat().to_vec(), - id, - }, - last, - ) - } - _ => { - return Err(Diagnostic::codegen_error( - &format!("Invalid direct-access: {elements:?}"), - SourceRange::undefined(), - )); - } - }; - - //Generate a load for the qualifer - // a.%b1.%x1 - let value = self.generate_expression(&expression)?; - let expression_type = self.get_type_hint_for(&expression)?; - if let AstStatement::DirectAccess { access, index, .. } = last { - //Generate and load the index value - let datatype = self.get_type_hint_info_for(last)?; - let rhs = self.generate_direct_access_index(access, index, datatype, expression_type)?; - //Shift the qualifer value right by the index value - let shift = self.llvm.builder.build_right_shift( - value.into_int_value(), - rhs, - expression_type.get_type_information().is_signed_int(), - "shift", - ); - //Trunc the result to the get only the target size - let llvm_target_type = self.llvm_index.get_associated_type(datatype.get_name())?.into_int_type(); - let result = self.llvm.builder.build_int_truncate_or_bit_cast(shift, llvm_target_type, ""); - Ok(result.as_basic_value_enum()) - } else { - unreachable!() - } - } - pub fn generate_direct_access_index( &self, access: &DirectAccessType, @@ -447,11 +387,6 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { )) } } - Operator::Address => { - //datatype is a pointer to the address - //value is the address - self.generate_element_pointer(expression).map(|result| result.as_basic_value_enum()) - } _ => unimplemented!(), }; value @@ -474,7 +409,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .or_else(|| self.index.find_pou(qualified_name))) .or_else(|| // some rare situations have a callstatement that's not properly annotated (e.g. checkRange-call of ranged datatypes) - if let AstStatement::Reference { name, .. } = operator { + if let Some(name) = operator.get_flat_reference_name() { self.index.find_pou(name) } else { None @@ -558,7 +493,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { if !pou.is_function() { let parameter_struct = match arguments_list.first() { Some(v) => v.into_pointer_value(), - None => self.generate_element_pointer(operator)?, + None => self.generate_lvalue(operator)?, }; self.assign_output_values(parameter_struct, implementation_name, parameters_list)? } @@ -611,7 +546,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { && !matches!(expression, AstStatement::EmptyStatement { .. }) { { - let assigned_output = self.generate_element_pointer(expression)?; + let assigned_output = self.generate_lvalue(expression)?; let assigned_output_type = self.annotations.get_type_or_void(expression, self.index).get_type_information(); @@ -656,11 +591,11 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { left: &AstStatement, right: &AstStatement, ) -> Result<(), Diagnostic> { - if let AstStatement::Reference { name, .. } = left { + if let Some(StatementAnnotation::Variable { qualified_name, .. }) = self.annotations.get(left) { let parameter = self .index - .find_member(function_name, name) - .ok_or_else(|| Diagnostic::unresolved_reference(name, left.get_location()))?; + .find_fully_qualified_variable(qualified_name) + .ok_or_else(|| Diagnostic::unresolved_reference(qualified_name, left.get_location()))?; let index = parameter.get_location_in_parent(); self.assign_output_value(&CallParameterAssignment { assignment_statement: right, @@ -691,12 +626,15 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // no function let (class_ptr, call_ptr) = match pou { PouIndexEntry::Method { .. } => { - let class_ptr = self.generate_element_pointer(operator)?; + let class_ptr = self.generate_lvalue(operator)?; let call_ptr = self.allocate_function_struct_instance(implementation.get_call_name(), operator)?; (Some(class_ptr), call_ptr) } - PouIndexEntry::Action { .. } if matches!(operator, AstStatement::Reference { .. }) => { + // TODO: find a more reliable way to make sure if this is a call into a local action!! + PouIndexEntry::Action { .. } + if matches!(operator, AstStatement::ReferenceExpr { base: None, .. }) => + { // special handling for local actions, get the parameter from the function context function_context .function @@ -705,7 +643,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .ok_or_else(|| Diagnostic::cannot_generate_call_statement(operator))? } _ => { - let call_ptr = self.generate_element_pointer(operator)?; + let call_ptr = self.generate_lvalue(operator)?; (None, call_ptr) } }; @@ -877,15 +815,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } // Generate the element pointer, then... - let value = self.generate_element_pointer(argument).or_else::(|_| { - // Passed a literal to a byref parameter? - // TODO: find more defensive solution - check early - let value = self.generate_expression(argument)?; - let argument = self.llvm.builder.build_alloca(value.get_type(), ""); - self.llvm.builder.build_store(argument, value); - - Ok(argument) - })?; + let value = { + let value = self.generate_expression_value(argument)?; + match value { + ExpressionValue::LValue(v) => v, + ExpressionValue::RValue(_v) => { + // Passed a literal to a byref parameter? + let value = self.generate_expression(argument)?; + let argument = self.llvm.builder.build_alloca(value.get_type(), ""); + self.llvm.builder.build_store(argument, value); + argument + } + } + }; // ...check if we can bitcast a reference to their hinted type if let Some(hint) = self.annotations.get_type_hint(argument, self.index) { @@ -1194,7 +1136,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { })?; builder.build_alloca(temp_type, "empty_varinout").as_basic_value_enum() } else { - self.generate_element_pointer(expression)?.as_basic_value_enum() + self.generate_lvalue(expression)?.as_basic_value_enum() }; builder.build_store(pointer_to_param, generated_exp); } else { @@ -1218,11 +1160,12 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { ) -> Result<(), Diagnostic> { let function_name = param_context.function_name; let parameter_struct = param_context.parameter_struct; - if let AstStatement::Reference { name, .. } = left { + + if let Some(StatementAnnotation::Variable { qualified_name, .. }) = self.annotations.get(left) { let parameter = self .index - .find_member(function_name, name) - .ok_or_else(|| Diagnostic::unresolved_reference(name, left.get_location()))?; + .find_fully_qualified_variable(qualified_name) + .ok_or_else(|| Diagnostic::unresolved_reference(qualified_name, left.get_location()))?; let index = parameter.get_location_in_parent(); // don't generate param assignments for empty statements, with the exception @@ -1242,80 +1185,25 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { parameter_struct, })?; }; - }; + } Ok(()) } - /// generates an gep-statement and returns the resulting pointer and DataTypeInfo + /// generates an gep-statement and returns the resulting pointer /// - /// - `reference_statement` - the statement to load (either a reference, an arrayAccess or a qualifiedReference) - pub fn generate_element_pointer( + /// - `reference_statement` - the statement to get an lvalue from + pub fn generate_lvalue( &self, reference_statement: &AstStatement, ) -> Result, Diagnostic> { - if let AstStatement::QualifiedReference { elements, .. } = reference_statement { - self.generate_element_pointer_from_elements(elements, reference_statement.get_location()) - } else { - self.do_generate_element_pointer(None, reference_statement) - } - } - - pub fn generate_element_pointer_from_elements( - &self, - elements: &[AstStatement], - location: SourceRange, - ) -> Result, Diagnostic> { - let mut qualifier: Option = None; - for e in elements { - qualifier = Some(self.do_generate_element_pointer(qualifier, e)?); - } - qualifier.ok_or_else(|| { - Diagnostic::codegen_error(&format!("Cannot generate a LValue for {elements:?}"), location) + self.generate_expression_value(reference_statement).and_then(|it| { + let v: Result = it.get_basic_value_enum().try_into(); + v.map_err(|err| { + Diagnostic::codegen_error(format!("{err:?}").as_str(), reference_statement.get_location()) + }) }) } - fn do_generate_element_pointer( - &self, - qualifier: Option>, - reference_statement: &AstStatement, - ) -> Result, Diagnostic> { - match reference_statement { - AstStatement::Reference { name, .. } => self.create_llvm_pointer_value_for_reference( - qualifier.as_ref(), - name.as_str(), - reference_statement, - ), - AstStatement::ArrayAccess { reference, access, .. } => { - let Some(dt) = self.annotations.get_type(reference, self.index) else { - // XXX: will be reachable until we abort codegen on critical errors (e.g. unresolved references) - unreachable!("unresolved reference") - }; - - if dt.get_type_information().is_vla() { - self.generate_element_pointer_for_vla(reference, access) - .map_err(|_| unreachable!("invalid access statement")) - } else { - self.generate_element_pointer_for_array(qualifier.as_ref(), reference, access) - } - } - AstStatement::PointerAccess { reference, .. } => { - self.do_generate_element_pointer(qualifier, reference).map(|it| self.deref(it)) - } - AstStatement::Literal { kind: AstLiteral::String(sv), .. } => if sv.is_wide() { - self.llvm_index.find_utf16_literal_string(sv.value()) - } else { - self.llvm_index.find_utf08_literal_string(sv.value()) - } - .map(|it| it.as_pointer_value()) - .ok_or_else(|| unreachable!("All string literals have to be constants")), - _ => Err(Diagnostic::codegen_error( - &format!("Cannot generate a LValue for {reference_statement:?}"), - reference_statement.get_location(), - )), - } - .map(|it| self.auto_deref_if_necessary(it, reference_statement)) - } - /// geneartes a gep for the given reference with an optional qualifier /// /// - `qualifier` an optional qualifier for a reference (e.g. myStruct.x where myStruct is the qualifier for x) @@ -1344,7 +1232,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .find_fully_qualified_variable(qualified_name) .map(VariableIndexEntry::get_location_in_parent) .ok_or_else(|| Diagnostic::unresolved_reference(qualified_name, offset.clone()))?; - let gep = self.llvm.get_member_pointer_from_struct( + let gep: PointerValue<'_> = self.llvm.get_member_pointer_from_struct( *qualifier, member_location, name, @@ -1461,111 +1349,114 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { /// - `access` the accessor expression (the expression between the brackets: reference[access]) fn generate_element_pointer_for_array( &self, - qualifier: Option<&PointerValue<'ink>>, reference: &AstStatement, access: &AstStatement, ) -> Result, Diagnostic> { //Load the reference - self.do_generate_element_pointer(qualifier.cloned(), reference).and_then(|lvalue| { - if let DataTypeInformation::Array { dimensions, .. } = self.get_type_hint_info_for(reference)? { - // make sure dimensions match statement list - let statements = access.get_as_list(); - if statements.is_empty() || statements.len() != dimensions.len() { - return Err(Diagnostic::codegen_error("Invalid array access", access.get_location())); - } - - // e.g. an array like `ARRAY[0..3, 0..2, 0..1] OF ...` has the lengths [ 4 , 3 , 2 ] - let lengths = dimensions - .iter() - .map(|d| d.get_length(self.index)) - .collect::, _>>() - .map_err(|msg| { - Diagnostic::codegen_error( - format!("Invalid array dimensions access: {msg}").as_str(), - access.get_location(), - ) - })?; + self.generate_expression_value(reference) + .map(|it| it.get_basic_value_enum().into_pointer_value()) + .and_then(|lvalue| { + if let DataTypeInformation::Array { dimensions, .. } = + self.get_type_hint_info_for(reference)? + { + // make sure dimensions match statement list + let statements = access.get_as_list(); + if statements.is_empty() || statements.len() != dimensions.len() { + return Err(Diagnostic::codegen_error("Invalid array access", access.get_location())); + } - // the portion indicates how many elements are represented by the corresponding dimension - // the first dimensions corresponds to the number of elements of the rest of the dimensions - // - portion = 6 (size to the right = 3 x 2 = 6) - // / - portion = 2 (because the size to the right of this dimension = 2) - // / / - the last dimension is directly translated into array-coordinates (skip-size = 1) - // / / - // [ 4 , 3 , 2 ] - let dimension_portions = (0..lengths.len()) - .map(|index| { - if index == lengths.len() - 1 { - 1 - } else { - lengths[index + 1..lengths.len()].iter().product() - } - }) - .collect::>(); + // e.g. an array like `ARRAY[0..3, 0..2, 0..1] OF ...` has the lengths [ 4 , 3 , 2 ] + let lengths = dimensions + .iter() + .map(|d| d.get_length(self.index)) + .collect::, _>>() + .map_err(|msg| { + Diagnostic::codegen_error( + format!("Invalid array dimensions access: {msg}").as_str(), + access.get_location(), + ) + })?; - let accessors_and_portions = statements - .iter() - .zip(dimensions) - .map(|(statement, dimension)| + // the portion indicates how many elements are represented by the corresponding dimension + // the first dimensions corresponds to the number of elements of the rest of the dimensions + // - portion = 6 (size to the right = 3 x 2 = 6) + // / - portion = 2 (because the size to the right of this dimension = 2) + // / / - the last dimension is directly translated into array-coordinates (skip-size = 1) + // / / + // [ 4 , 3 , 2 ] + let dimension_portions = (0..lengths.len()) + .map(|index| { + if index == lengths.len() - 1 { + 1 + } else { + lengths[index + 1..lengths.len()].iter().product() + } + }) + .collect::>(); + + let accessors_and_portions = statements + .iter() + .zip(dimensions) + .map(|(statement, dimension)| // generate array-accessors self.generate_access_for_dimension(dimension, statement)) - .zip(dimension_portions); - - // accessing [ 1, 2, 2] means to access [ 1*6 + 2*2 + 2*1 ] = 12 - let (index_access, _) = accessors_and_portions.fold( - (Ok(self.llvm.i32_type().const_zero().as_basic_value_enum()), 1), - |(accumulated_value, _), (current_v, current_portion)| { - let result = accumulated_value.and_then(|last_v| { - current_v.map(|v| { - let current_portion_value = self - .llvm - .i32_type() - .const_int(current_portion as u64, false) - .as_basic_value_enum(); - // multiply the accessor with the dimension's portion - let m_v = self.create_llvm_int_binary_expression( - &Operator::Multiplication, - current_portion_value, - v, - ); - // take the sum of the mulitlication and the previous accumulated_value - // this now becomes the new accumulated value - self.create_llvm_int_binary_expression(&Operator::Plus, m_v, last_v) - }) - }); - (result, 0 /* the 0 will be ignored */) - }, - ); - - // make sure we got an int-value - let index_access: IntValue = index_access.and_then(|it| { - it.try_into().map_err(|_| { - Diagnostic::codegen_error("non-numeric index-access", access.get_location()) - }) - })?; + .zip(dimension_portions); + + // accessing [ 1, 2, 2] means to access [ 1*6 + 2*2 + 2*1 ] = 12 + let (index_access, _) = accessors_and_portions.fold( + (Ok(self.llvm.i32_type().const_zero().as_basic_value_enum()), 1), + |(accumulated_value, _), (current_v, current_portion)| { + let result = accumulated_value.and_then(|last_v| { + current_v.map(|v| { + let current_portion_value = self + .llvm + .i32_type() + .const_int(current_portion as u64, false) + .as_basic_value_enum(); + // multiply the accessor with the dimension's portion + let m_v = self.create_llvm_int_binary_expression( + &Operator::Multiplication, + current_portion_value, + v, + ); + // take the sum of the mulitlication and the previous accumulated_value + // this now becomes the new accumulated value + self.create_llvm_int_binary_expression(&Operator::Plus, m_v, last_v) + }) + }); + (result, 0 /* the 0 will be ignored */) + }, + ); + + // make sure we got an int-value + let index_access: IntValue = index_access.and_then(|it| { + it.try_into().map_err(|_| { + Diagnostic::codegen_error("non-numeric index-access", access.get_location()) + }) + })?; - let accessor_sequence = if lvalue.get_type().get_element_type().is_array_type() { - // e.g.: [81 x i32]* - // the first index (0) will point to the array -> [81 x i32] - // the second index (index_access) will point to the element in the array - vec![self.llvm.i32_type().const_zero(), index_access] - } else { - // lvalue is a pointer to type -> e.g.: i32* - // only one index (index_access) is needed to access the element + let accessor_sequence = if lvalue.get_type().get_element_type().is_array_type() { + // e.g.: [81 x i32]* + // the first index (0) will point to the array -> [81 x i32] + // the second index (index_access) will point to the element in the array + vec![self.llvm.i32_type().const_zero(), index_access] + } else { + // lvalue is a pointer to type -> e.g.: i32* + // only one index (index_access) is needed to access the element - // IGNORE the additional first index (0) - // it would point to -> i32 - // we can't access any element of i32 - vec![index_access] - }; + // IGNORE the additional first index (0) + // it would point to -> i32 + // we can't access any element of i32 + vec![index_access] + }; - // load the access from that array - let pointer = self.llvm.load_array_element(lvalue, &accessor_sequence, "tmpVar")?; + // load the access from that array + let pointer = self.llvm.load_array_element(lvalue, &accessor_sequence, "tmpVar")?; - return Ok(pointer); - } - Err(Diagnostic::codegen_error("Invalid array access", access.get_location())) - }) + return Ok(pointer); + } + Err(Diagnostic::codegen_error("Invalid array access", access.get_location())) + }) } /// generates the result of an pointer binary-expression @@ -2016,13 +1907,12 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let mut member_values: Vec<(u32, BasicValueEnum<'ink>)> = Vec::new(); for assignment in flatten_expression_list(assignments) { if let AstStatement::Assignment { left, right, .. } = assignment { - if let AstStatement::Reference { name: variable_name, location, .. } = &**left { + if let Some(StatementAnnotation::Variable { qualified_name, .. }) = + self.annotations.get(left.as_ref()) + { let member: &VariableIndexEntry = - self.index.find_member(struct_name, variable_name).ok_or_else(|| { - Diagnostic::unresolved_reference( - &qualified_name(struct_name, variable_name), - location.clone(), - ) + self.index.find_fully_qualified_variable(qualified_name).ok_or_else(|| { + Diagnostic::unresolved_reference(qualified_name, left.get_location()) })?; let index_in_parent = member.get_location_in_parent(); @@ -2419,13 +2309,15 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { /// returns an optional name used for a temporary variable when loading a pointer represented by `expression` fn get_load_name(&self, expression: &AstStatement) -> Option { match expression { - AstStatement::Reference { name, .. } => { - Some(format!("{}{}{}", self.temp_variable_prefix, name, self.temp_variable_suffix)) - } - AstStatement::QualifiedReference { .. } => Some(self.temp_variable_prefix.clone()), - AstStatement::PointerAccess { .. } | AstStatement::ArrayAccess { .. } => { + AstStatement::ReferenceExpr { access: ReferenceAccess::Deref, .. } + | AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), .. } => { Some("load_tmpVar".to_string()) } + AstStatement::ReferenceExpr { .. } => expression + .get_flat_reference_name() + .map(|name| format!("{}{}{}", self.temp_variable_prefix, name, self.temp_variable_suffix)) + .or_else(|| Some(self.temp_variable_prefix.clone())), + AstStatement::Identifier { name, .. } => Some(format!("{}{}", name, self.temp_variable_suffix)), _ => None, } } @@ -2433,27 +2325,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { /// Generate a GEP instruction, accessing the array pointed to within the VLA struct at runtime fn generate_element_pointer_for_vla( &self, - reference: &AstStatement, + reference: ExpressionValue<'ink>, + reference_annotation: &StatementAnnotation, access: &AstStatement, ) -> Result, ()> { let builder = &self.llvm.builder; // array access is either directly on a reference or on another array access (ARRAY OF ARRAY) - let annotation = match reference { - AstStatement::Reference { .. } => self.annotations.get(reference), - AstStatement::ArrayAccess { reference, .. } => self.annotations.get(reference.as_ref()), - _ => unreachable!(), - }; - let Some(StatementAnnotation::Variable { qualified_name, .. }) = annotation else { - unreachable!() + let StatementAnnotation::Variable { resulting_type: reference_type, .. } = reference_annotation else { + unreachable!(); }; - // if the vla parameter is by-ref, we need to dereference the pointer - let struct_ptr = self.auto_deref_if_necessary( - self.llvm_index.find_loaded_associated_variable_value(qualified_name).ok_or(())?, - reference, - ); + let struct_ptr = reference.get_basic_value_enum().into_pointer_value(); // GEPs into the VLA struct, getting an LValue for the array pointer and the dimension array and // dereferences the former @@ -2463,9 +2347,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let dim_arr_gep = builder.build_struct_gep(struct_ptr, 1, "dim_arr").unwrap(); // get lengths of dimensions - let Some(type_) = self.annotations.get_type(reference, self.index) else { - unreachable!() - }; + let type_ = self.index.get_type_information_or_void(reference_type); let Some(ndims) = type_.get_type_information().get_dimensions() else { unreachable!() }; @@ -2522,6 +2404,128 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { Ok(unsafe { builder.build_in_bounds_gep(vla_arr_ptr, &[accessor], "arr_val") }) } + + /// generates a reference expression (member, index, deref, etc.) + /// + /// - `access` the ReferenceAccess of the reference to generate + /// - `base` the "previous" segment of an optional qualified reference-access + /// - `original_expression` the original ast-statement used to report Diagnostics + fn generate_reference_expression( + &self, + access: &ReferenceAccess, + base: Option<&AstStatement>, + original_expression: &AstStatement, + ) -> Result, Diagnostic> { + match (access, base) { + + // expressions like `base.member`, or just `member` + (ReferenceAccess::Member(member), base) => { + let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?; + + if let AstStatement::DirectAccess { access, index, .. } = member.as_ref() { + let (Some(base), Some(base_value)) = (base, base_value) else { + return Err(Diagnostic::codegen_error("Cannot generate DirectAccess without base value.", original_expression.get_location())); + }; + self.generate_direct_access_expression(base, &base_value, member, access, index) + } else { + let member_name = member.get_flat_reference_name().unwrap_or("unknown"); + self.create_llvm_pointer_value_for_reference( + base_value.map(|it| it.get_basic_value_enum().into_pointer_value()).as_ref(), + self.get_load_name(member).as_deref().unwrap_or(member_name), + original_expression, + ) + .map(ExpressionValue::LValue) + } + } + + // expressions like: base[idx] + (ReferenceAccess::Index(array_idx), Some(base)) => { + if self.annotations.get_type_or_void(base, self.index).is_vla() { + // vla array needs special handling + self.generate_element_pointer_for_vla( + self.generate_expression_value(base)?, + self.annotations.get(base).expect(""), + array_idx.as_ref(), + ) + .map_err(|_| unreachable!("invalid access statement")) + .map(ExpressionValue::LValue) + } else { + // normal array expression + self.generate_element_pointer_for_array(base, array_idx).map(ExpressionValue::LValue) + } + } + + // INT#target (INT = base) + (ReferenceAccess::Cast(target), Some(_base)) => { + if matches!(target.as_ref(), AstStatement::Identifier { .. }) { + let mr = + AstFactory::create_member_reference(target.as_ref().clone(), None, target.get_id()); + self.generate_expression_value(&mr) + } else { + self.generate_expression_value(target.as_ref()) + } + } + + // base^ + (ReferenceAccess::Deref, Some(base)) => { + let ptr = self.generate_expression_value(base)?; + Ok(ExpressionValue::LValue( + self.llvm + .load_pointer(&ptr.get_basic_value_enum().into_pointer_value(), "deref") + .into_pointer_value(), + )) + } + + // &base + (ReferenceAccess::Address, Some(base)) => { + let lvalue = self.generate_expression_value(base)?; + Ok(ExpressionValue::RValue(lvalue.get_basic_value_enum())) + } + + (ReferenceAccess::Index(_), None) // [idx]; + | (ReferenceAccess::Cast(_), None) // INT#; + | (ReferenceAccess::Deref, None) // ^; + | (ReferenceAccess::Address, None) // &; + => Err(Diagnostic::codegen_error( + "Expected a base-expressions, but found none.", + original_expression.get_location(), + )), + } + } + + /// generates a direct-access expression like `x.%B4` + /// - `qualifier` the qualifier statement (see `x` above) + /// - `qualifier_value` the generated value of the qualifier + /// - `member` the member AstStatement (see `%B4`above) + /// - `access` the type of access (see `B` above) + fn generate_direct_access_expression( + &self, + qualifier: &AstStatement, + qualifier_value: &ExpressionValue<'ink>, + member: &AstStatement, + access: &DirectAccessType, + index: &AstStatement, + ) -> Result, Diagnostic> { + let loaded_base_value = qualifier_value.as_r_value(self.llvm, self.get_load_name(qualifier)); + let datatype = self.get_type_hint_info_for(member)?; + let base_type = self.get_type_hint_for(qualifier)?; + //Generate and load the index value + let rhs = self.generate_direct_access_index(access, index, datatype, base_type)?; + //Shift the qualifer value right by the index value + let shift = self.llvm.builder.build_right_shift( + loaded_base_value.into_int_value(), + rhs, + base_type.get_type_information().is_signed_int(), + "shift", + ); + //Trunc the result to the get only the target size + let result = self.llvm.builder.build_int_truncate_or_bit_cast( + shift, + self.llvm_index.get_associated_type(datatype.get_name())?.into_int_type(), + "", + ); + Ok(ExpressionValue::RValue(result.as_basic_value_enum())) + } } /// Returns the information required to call a parameter implicitly in a function @@ -2538,7 +2542,7 @@ pub fn get_implicit_call_parameter<'a>( let (location, param_statement, is_implicit) = match param_statement { AstStatement::Assignment { left, right, .. } | AstStatement::OutputAssignment { left, right, .. } => { //explicit - let AstStatement::Reference { name: left_name, .. } = left.as_ref() else { + let Some(left_name) = left.as_ref().get_flat_reference_name() else { return Err(Diagnostic::reference_expected(param_statement.get_location())); }; let loc = declared_parameters diff --git a/src/codegen/generators/statement_generator.rs b/src/codegen/generators/statement_generator.rs index 507ad2d1d5..4634ff5d0b 100644 --- a/src/codegen/generators/statement_generator.rs +++ b/src/codegen/generators/statement_generator.rs @@ -15,10 +15,12 @@ use inkwell::{ basic_block::BasicBlock, builder::Builder, context::Context, - values::{BasicValueEnum, FunctionValue}, + values::{BasicValueEnum, FunctionValue, PointerValue}, }; use plc_ast::{ - ast::{flatten_expression_list, AstFactory, AstStatement, NewLines, Operator, SourceRange}, + ast::{ + flatten_expression_list, AstFactory, AstStatement, NewLines, Operator, ReferenceAccess, SourceRange, + }, control_statements::{AstControlStatement, ConditionalBlock}, }; use plc_diagnostics::diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR}; @@ -205,7 +207,12 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { return Ok(()); } let exp_gen = self.create_expr_generator(); - let left = exp_gen.generate_element_pointer(left_statement)?; + let left: PointerValue = exp_gen.generate_expression_value(left_statement).and_then(|it| { + it.get_basic_value_enum().try_into().map_err(|err| { + Diagnostic::codegen_error(format!("{err:?}").as_str(), left_statement.get_location()) + }) + })?; + let left_type = exp_gen.get_type_hint_info_for(left_statement)?; // if the lhs-type is a subrange type we may need to generate a check-call // e.g. x := y, ==> x := CheckSignedInt(y); @@ -235,38 +242,45 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { ) -> Result<(), Diagnostic> { //TODO : Validation let exp_gen = self.create_expr_generator(); - if let AstStatement::QualifiedReference { elements, .. } = left_statement { - //Target - let target: Vec = elements - .iter() - .take_while(|it| !matches!(*it, &AstStatement::DirectAccess { .. })) - .cloned() - .collect(); - let id = target.last().unwrap().get_id(); - let target = AstStatement::QualifiedReference { elements: target.to_vec(), id }; - - //Access - let direct_access: Vec<&AstStatement> = - elements.iter().skip_while(|it| !matches!(*it, &AstStatement::DirectAccess { .. })).collect(); - let left_type = exp_gen.get_type_hint_for(&target)?; - let right_type = exp_gen.get_type_hint_for(right_statement)?; - - //special case if we deal with a single bit, then we need to switch to a faked u1 type - let right_type = - if let DataTypeInformation::Integer { semantic_size: Some(typesystem::U1_SIZE), .. } = - *right_type.get_type_information() - { - self.index.get_type_or_panic(typesystem::U1_TYPE) - } else { - right_type - }; - - //Left pointer - let left = exp_gen.generate_element_pointer(&target)?; - let left_value = self.llvm.load_pointer(&left, "").into_int_value(); - //Build index - if let Some((element, direct_access)) = direct_access.split_first() { - let mut rhs = if let AstStatement::DirectAccess { access, index, .. } = element { + + // given a complex direct-access assignemnt: a.b.c.%W3,%X1 + // we want to deconstruct the targe-part (a.b.c) and the direct-access sequence (%W3.%X1) + let Some((target, access_sequence)) = collect_base_and_direct_access_for_assignment(left_statement) else {unreachable!("Invalid direct-access expression: {left_statement:#?}")}; + + let left_type = exp_gen.get_type_hint_for(target)?; + let right_type = exp_gen.get_type_hint_for(right_statement)?; + + //special case if we deal with a single bit, then we need to switch to a faked u1 type + let right_type = + if let DataTypeInformation::Integer { semantic_size: Some(typesystem::U1_SIZE), .. } = + *right_type.get_type_information() + { + self.index.get_type_or_panic(typesystem::U1_TYPE) + } else { + right_type + }; + + //Left pointer + let left_expression_value = exp_gen.generate_expression_value(target)?; + let left_value = left_expression_value.as_r_value(self.llvm, None).into_int_value(); + let left = left_expression_value.get_basic_value_enum().into_pointer_value(); + //Build index + if let Some((element, direct_access)) = access_sequence.split_first() { + let mut rhs = if let AstStatement::DirectAccess { access, index, .. } = element { + exp_gen.generate_direct_access_index( + access, + index, + right_type.get_type_information(), + left_type, + ) + } else { + Err(Diagnostic::syntax_error( + &format!("{element:?} not a direct access"), + element.get_location(), + )) + }?; + for element in direct_access { + let rhs_next = if let AstStatement::DirectAccess { access, index, .. } = element { exp_gen.generate_direct_access_index( access, index, @@ -279,52 +293,34 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { element.get_location(), )) }?; - for element in direct_access { - let rhs_next = if let AstStatement::DirectAccess { access, index, .. } = element { - exp_gen.generate_direct_access_index( - access, - index, - right_type.get_type_information(), - left_type, - ) - } else { - Err(Diagnostic::syntax_error( - &format!("{element:?} not a direct access"), - element.get_location(), - )) - }?; - rhs = self.llvm.builder.build_int_add(rhs, rhs_next, ""); - } - //Build mask for the index - //Get the target bit type as all ones - let rhs_type = self.llvm_index.get_associated_type(right_type.get_name())?.into_int_type(); - let ones = rhs_type.const_all_ones(); - //Extend the mask to the target type - let extended_mask = self.llvm.builder.build_int_z_extend(ones, left_value.get_type(), "ext"); - //Position the ones in their correct locations - let shifted_mask = self.llvm.builder.build_left_shift(extended_mask, rhs, "shift"); - //Invert the mask - let mask = self.llvm.builder.build_not(shifted_mask, "invert"); - //And the result with the mask to erase the set bits at the target location - let and_value = self.llvm.builder.build_and(left_value, mask, "erase"); - - //Generate an expression for the right size - let right = exp_gen.generate_expression(right_statement)?; - //Cast the right side to the left side type - let lhs = cast_if_needed!(self, left_type, right_type, right, None).into_int_value(); - //Shift left by the direct access - let value = self.llvm.builder.build_left_shift(lhs, rhs, "value"); - - //OR the result and store it in the left side - let or_value = self.llvm.builder.build_or(and_value, value, "or"); - self.llvm.builder.build_store(left, or_value); - } else { - unreachable!(); + rhs = self.llvm.builder.build_int_add(rhs, rhs_next, ""); } + //Build mask for the index + //Get the target bit type as all ones + let rhs_type = self.llvm_index.get_associated_type(right_type.get_name())?.into_int_type(); + let ones = rhs_type.const_all_ones(); + //Extend the mask to the target type + let extended_mask = self.llvm.builder.build_int_z_extend(ones, left_value.get_type(), "ext"); + //Position the ones in their correct locations + let shifted_mask = self.llvm.builder.build_left_shift(extended_mask, rhs, "shift"); + //Invert the mask + let mask = self.llvm.builder.build_not(shifted_mask, "invert"); + //And the result with the mask to erase the set bits at the target location + let and_value = self.llvm.builder.build_and(left_value, mask, "erase"); + + //Generate an expression for the right size + let right = exp_gen.generate_expression(right_statement)?; + //Cast the right side to the left side type + let lhs = cast_if_needed!(self, left_type, right_type, right, None).into_int_value(); + //Shift left by the direct access + let value = self.llvm.builder.build_left_shift(lhs, rhs, "value"); + + //OR the result and store it in the left side + let or_value = self.llvm.builder.build_or(and_value, value, "or"); + self.llvm.builder.build_store(left, or_value); } else { - unreachable!() + unreachable!(); } - Ok(()) } @@ -396,7 +392,7 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { "tmpVar", ); - let ptr = expression_generator.generate_element_pointer(counter)?; + let ptr = expression_generator.generate_lvalue(counter)?; builder.build_store(ptr, next); //Loop back @@ -742,3 +738,22 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { (&self.llvm.builder, self.function_context.function, self.llvm.context) } } + +/// when generating an assignment to a direct-access (e.g. a.b.c.%W3.%X2 := 2;) +/// we want to deconstruct the sequence into the base-statement (a.b.c) and the sequence +/// of direct-access commands (vec![%W3, %X2]) +fn collect_base_and_direct_access_for_assignment( + left_statement: &AstStatement, +) -> Option<(&AstStatement, Vec<&AstStatement>)> { + let mut current = Some(left_statement); + let mut access_sequence = Vec::new(); + while let Some(AstStatement::ReferenceExpr { access: ReferenceAccess::Member(m), base, .. }) = current { + if matches!(m.as_ref(), AstStatement::DirectAccess { .. }) { + access_sequence.insert(0, m.as_ref()); + current = base.as_deref(); + } else { + break; + } + } + current.zip(Some(access_sequence)) +} diff --git a/src/codegen/tests/code_gen_tests.rs b/src/codegen/tests/code_gen_tests.rs index 5548dc9d31..8bf019c3af 100644 --- a/src/codegen/tests/code_gen_tests.rs +++ b/src/codegen/tests/code_gen_tests.rs @@ -2173,6 +2173,37 @@ fn typed_enums_are_generated() { insta::assert_snapshot!(result); } +#[test] +fn typed_enums_are_used_properly() { + let result = codegen( + " + TYPE MyEnum: BYTE(red := 5, yellow, green); + END_TYPE + + TYPE MyEnum2: UINT(red := 15, yellow, green); + END_TYPE + + TYPE MyEnum3: DINT(red := 25, yellow, green); + END_TYPE + + PROGRAM prg + VAR + x: BYTE; + y: UINT; + z: DINT; + END_VAR + + x := MyEnum#yellow; + y := MyEnum2#yellow; + z := MyEnum3#yellow; + + END_PROGRAM + ", + ); + + insta::assert_snapshot!(result); +} + #[test] fn typed_enums_with_initializers_are_generated() { let result = codegen( diff --git a/src/codegen/tests/function_tests.rs b/src/codegen/tests/function_tests.rs index 792c701bd2..32831c5add 100644 --- a/src/codegen/tests/function_tests.rs +++ b/src/codegen/tests/function_tests.rs @@ -347,3 +347,26 @@ fn function_with_ref_sized_string_varargs_called_in_program() { // Function call with 3 as first parameter (size) and the arguments array as pointer insta::assert_snapshot!(result); } + +#[test] +fn return_variable_in_nested_call() { + // GIVEN a call statement where we take the adr of the return-variable + let src = " + FUNCTION main : DINT + VAR + x1, x2 : DINT; + END_VAR + x1 := SMC_Read( + ValAddr := ADR(main)); + END_FUNCTION + + FUNCTION SMC_Read : DINT + VAR_INPUT + ValAddr : LWORD; + END_VAR + END_FUNCTION + "; + + // we want a call passing the return-variable as apointer (actually the adress as a LWORD) + insta::assert_snapshot!(codegen(src)); +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap index 8cdc2d495c..568ae29801 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap @@ -43,18 +43,18 @@ entry: %cl = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1 %x1 = getelementptr inbounds %MyClass, %MyClass* %cl, i32 0, i32 0 - %load_ = load i16, i16* %x1, align 2 - store i16 %load_, i16* %x, align 2 + %load_x = load i16, i16* %x1, align 2 + store i16 %load_x, i16* %x, align 2 %MyClass.testMethod_instance = alloca %MyClass.testMethod, align 8 %1 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance, i32 0, i32 0 - %load_x = load i16, i16* %x, align 2 - store i16 %load_x, i16* %1, align 2 + %load_x2 = load i16, i16* %x, align 2 + store i16 %load_x2, i16* %1, align 2 call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance) - %MyClass.testMethod_instance2 = alloca %MyClass.testMethod, align 8 - %2 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance2, i32 0, i32 0 - %load_x3 = load i16, i16* %x, align 2 - store i16 %load_x3, i16* %2, align 2 - call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance2) + %MyClass.testMethod_instance3 = alloca %MyClass.testMethod, align 8 + %2 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance3, i32 0, i32 0 + %load_x4 = load i16, i16* %x, align 2 + store i16 %load_x4, i16* %2, align 2 + call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fb_method_in_pou.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fb_method_in_pou.snap index 8cdc2d495c..568ae29801 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fb_method_in_pou.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fb_method_in_pou.snap @@ -43,18 +43,18 @@ entry: %cl = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1 %x1 = getelementptr inbounds %MyClass, %MyClass* %cl, i32 0, i32 0 - %load_ = load i16, i16* %x1, align 2 - store i16 %load_, i16* %x, align 2 + %load_x = load i16, i16* %x1, align 2 + store i16 %load_x, i16* %x, align 2 %MyClass.testMethod_instance = alloca %MyClass.testMethod, align 8 %1 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance, i32 0, i32 0 - %load_x = load i16, i16* %x, align 2 - store i16 %load_x, i16* %1, align 2 + %load_x2 = load i16, i16* %x, align 2 + store i16 %load_x2, i16* %1, align 2 call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance) - %MyClass.testMethod_instance2 = alloca %MyClass.testMethod, align 8 - %2 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance2, i32 0, i32 0 - %load_x3 = load i16, i16* %x, align 2 - store i16 %load_x3, i16* %2, align 2 - call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance2) + %MyClass.testMethod_instance3 = alloca %MyClass.testMethod, align 8 + %2 = getelementptr inbounds %MyClass.testMethod, %MyClass.testMethod* %MyClass.testMethod_instance3, i32 0, i32 0 + %load_x4 = load i16, i16* %x, align 2 + store i16 %load_x4, i16* %2, align 2 + call void @MyClass.testMethod(%MyClass* %cl, %MyClass.testMethod* %MyClass.testMethod_instance3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__program_with_casted_chars_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__program_with_casted_chars_assignment.snap index e164410073..1ef122fe2c 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__program_with_casted_chars_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__program_with_casted_chars_assignment.snap @@ -8,6 +8,8 @@ source_filename = "main" %mainPROG = type { i8, i16 } @mainPROG_instance = global %mainPROG zeroinitializer +@utf08_literal_0 = private unnamed_addr constant [2 x i8] c"B\00" +@utf16_literal_0 = private unnamed_addr constant [2 x i16] [i16 65, i16 0] define void @mainPROG(%mainPROG* %0) { entry: diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__reference_qualified_name.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__reference_qualified_name.snap index b6df6b4e8d..db507b1567 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__reference_qualified_name.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__reference_qualified_name.snap @@ -30,12 +30,12 @@ entry: define void @prg(%prg* %0) { entry: %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 - %load_ = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 0), align 4 - store i32 %load_, i32* %x, align 4 - %load_1 = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 1), align 4 - store i32 %load_1, i32* %x, align 4 - %load_2 = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 2, i32 0), align 4 - store i32 %load_2, i32* %x, align 4 + %load_x = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 0), align 4 + store i32 %load_x, i32* %x, align 4 + %load_y = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 1), align 4 + store i32 %load_y, i32* %x, align 4 + %load_x1 = load i32, i32* getelementptr inbounds (%foo, %foo* @foo_instance, i32 0, i32 2, i32 0), align 4 + store i32 %load_x1, i32* %x, align 4 ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__typed_enums_are_used_properly.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__typed_enums_are_used_properly.snap new file mode 100644 index 0000000000..bb16c33af4 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__typed_enums_are_used_properly.snap @@ -0,0 +1,31 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: result +--- +; ModuleID = 'main' +source_filename = "main" + +%prg = type { i8, i16, i32 } + +@prg_instance = global %prg zeroinitializer +@red = unnamed_addr constant i8 5 +@yellow = unnamed_addr constant i8 6 +@green = unnamed_addr constant i8 7 +@red.1 = unnamed_addr constant i16 15 +@yellow.2 = unnamed_addr constant i16 16 +@red.3 = unnamed_addr constant i32 25 +@yellow.4 = unnamed_addr constant i32 26 +@green.5 = unnamed_addr constant i16 17 +@green.6 = unnamed_addr constant i32 27 + +define void @prg(%prg* %0) { +entry: + %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 + %y = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1 + %z = getelementptr inbounds %prg, %prg* %0, i32 0, i32 2 + store i8 6, i8* %x, align 1 + store i16 16, i16* %y, align 2 + store i32 26, i32* %z, align 4 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__return_variable_in_nested_call.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__return_variable_in_nested_call.snap new file mode 100644 index 0000000000..c6b89c00a2 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__return_variable_in_nested_call.snap @@ -0,0 +1,32 @@ +--- +source: src/codegen/tests/function_tests.rs +expression: codegen(src) +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca i32, align 4 + %x2 = alloca i32, align 4 + store i32 0, i32* %x1, align 4 + store i32 0, i32* %x2, align 4 + store i32 0, i32* %main, align 4 + %0 = ptrtoint i32* %main to i64 + %call = call i32 @SMC_Read(i64 %0) + store i32 %call, i32* %x1, align 4 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + +define i32 @SMC_Read(i64 %0) { +entry: + %SMC_Read = alloca i32, align 4 + %ValAddr = alloca i64, align 8 + store i64 %0, i64* %ValAddr, align 4 + store i32 0, i32* %SMC_Read, align 4 + %SMC_Read_ret = load i32, i32* %SMC_Read, align 4 + ret i32 %SMC_Read_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__multifile_codegen_tests__function_defined_in_external_file.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__multifile_codegen_tests__function_defined_in_external_file.snap index 007bddd198..223c502842 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__multifile_codegen_tests__function_defined_in_external_file.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__multifile_codegen_tests__function_defined_in_external_file.snap @@ -67,7 +67,7 @@ source_filename = "prog.st" define void @prog(%prog* %0) { entry: %myFb = getelementptr inbounds %prog, %prog* %0, i32 0, i32 0 - %load_ = load i32, i32* getelementptr inbounds (%prg.5, %prg.5* @prg_instance, i32 0, i32 0), align 4 + %load_a = load i32, i32* getelementptr inbounds (%prg.5, %prg.5* @prg_instance, i32 0, i32 0), align 4 call void @prg2(%prg2.6* @prg2_instance) %call = call i32 @func() call void @fb(%fb.4* %myFb) diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__string_tests__program_with_casted_string_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__string_tests__program_with_casted_string_assignment.snap index 76cb3c4068..8b781eb565 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__string_tests__program_with_casted_string_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__string_tests__program_with_casted_string_assignment.snap @@ -9,7 +9,9 @@ source_filename = "main" @prg_instance = global %prg zeroinitializer @utf08_literal_0 = private unnamed_addr constant [12 x i8] c"im a genius\00" -@utf16_literal_0 = private unnamed_addr constant [18 x i16] [i16 105, i16 109, i16 32, i16 97, i16 32, i16 117, i16 116, i16 102, i16 49, i16 54, i16 32, i16 103, i16 101, i16 110, i16 105, i16 117, i16 115, i16 0] +@utf08_literal_1 = private unnamed_addr constant [18 x i8] c"im a utf16 genius\00" +@utf16_literal_0 = private unnamed_addr constant [12 x i16] [i16 105, i16 109, i16 32, i16 97, i16 32, i16 103, i16 101, i16 110, i16 105, i16 117, i16 115, i16 0] +@utf16_literal_1 = private unnamed_addr constant [18 x i16] [i16 105, i16 109, i16 32, i16 97, i16 32, i16 117, i16 116, i16 102, i16 49, i16 54, i16 32, i16 103, i16 101, i16 110, i16 105, i16 117, i16 115, i16 0] define void @prg(%prg* %0) { entry: @@ -18,7 +20,7 @@ entry: %1 = bitcast [81 x i8]* %y to i8* call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %1, i8* align 1 getelementptr inbounds ([12 x i8], [12 x i8]* @utf08_literal_0, i32 0, i32 0), i32 80, i1 false) %2 = bitcast [81 x i16]* %z to i8* - call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 2 %2, i8* align 2 bitcast ([18 x i16]* @utf16_literal_0 to i8*), i32 160, i1 false) + call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 2 %2, i8* align 2 bitcast ([18 x i16]* @utf16_literal_1 to i8*), i32 160, i1 false) ret void } diff --git a/src/index.rs b/src/index.rs index d1720e2d8f..9eee2af293 100644 --- a/src/index.rs +++ b/src/index.rs @@ -5,6 +5,7 @@ use crate::{ typesystem::{self, *}, }; use indexmap::IndexMap; +use itertools::Itertools; use plc_ast::ast::{ AstStatement, DirectAccessType, GenericBinding, HardwareAccessType, LinkageType, PouType, SourceRange, TypeNature, @@ -1062,11 +1063,13 @@ impl Index { pub fn find_fully_qualified_variable(&self, fully_qualified_name: &str) -> Option<&VariableIndexEntry> { let segments: Vec<&str> = fully_qualified_name.split('.').collect(); let (q, segments) = if segments.len() > 1 { - (Some(segments[0]), segments.iter().skip(1).copied().collect::>()) + // the last segment is th ename, everything before ist qualifier + // e.g. MyClass.MyMethod.x --> qualifier: "MyClass.MyMethod", name: "x" + (Some(segments.iter().take(segments.len() - 1).join(".")), vec![*segments.last().unwrap()]) } else { (None, segments) }; - self.find_variable(q, &segments[..]) + self.find_variable(q.as_deref(), &segments[..]) } pub fn find_variable(&self, context: Option<&str>, segments: &[&str]) -> Option<&VariableIndexEntry> { diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 7c68900a62..90ae43d6e4 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -965,7 +965,6 @@ fn pre_processing_generates_inline_array_of_array() { // AND the original variable should now point to the new DataType let var_data_type = &ast.units[0].variable_blocks[0].variables[0].data_type_declaration; - println!("{:#?}", var_data_type.get_location()); assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array".to_string(), diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums.snap index 075e98980b..0bf49ca890 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums.snap @@ -1,7 +1,6 @@ --- source: src/index/tests/index_tests.rs expression: new_enum_type - --- EnumType { name: Some( @@ -11,24 +10,45 @@ EnumType { elements: ExpressionList { expressions: [ Assignment { - left: Reference { - name: "a", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, }, right: LiteralInteger { value: 0, }, }, Assignment { - left: Reference { - name: "b", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, }, right: BinaryExpression { operator: Plus, - left: CastStatement { - type_name: "__foo_inline_enum", - target: Reference { - name: "a", - }, + left: ReferenceExpr { + kind: Cast( + Identifier { + name: "a", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "__foo_inline_enum", + }, + ), + base: None, + }, + ), }, right: LiteralInteger { value: 1, @@ -36,16 +56,32 @@ EnumType { }, }, Assignment { - left: Reference { - name: "c", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, }, right: BinaryExpression { operator: Plus, - left: CastStatement { - type_name: "__foo_inline_enum", - target: Reference { - name: "b", - }, + left: ReferenceExpr { + kind: Cast( + Identifier { + name: "b", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "__foo_inline_enum", + }, + ), + base: None, + }, + ), }, right: LiteralInteger { value: 1, diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums_global.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums_global.snap index 6705341f32..77a5e2f5e9 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums_global.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_enums_global.snap @@ -1,7 +1,6 @@ --- source: src/index/tests/index_tests.rs -expression: "ast.types[0].data_type" - +expression: "ast.user_types[0].data_type" --- EnumType { name: Some( @@ -11,24 +10,45 @@ EnumType { elements: ExpressionList { expressions: [ Assignment { - left: Reference { - name: "a", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, }, right: LiteralInteger { value: 0, }, }, Assignment { - left: Reference { - name: "b", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, }, right: BinaryExpression { operator: Plus, - left: CastStatement { - type_name: "__global_inline_enum", - target: Reference { - name: "a", - }, + left: ReferenceExpr { + kind: Cast( + Identifier { + name: "a", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "__global_inline_enum", + }, + ), + base: None, + }, + ), }, right: LiteralInteger { value: 1, @@ -36,16 +56,32 @@ EnumType { }, }, Assignment { - left: Reference { - name: "c", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, }, right: BinaryExpression { operator: Plus, - left: CastStatement { - type_name: "__global_inline_enum", - target: Reference { - name: "b", - }, + left: ReferenceExpr { + kind: Cast( + Identifier { + name: "b", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "__global_inline_enum", + }, + ), + base: None, + }, + ), }, right: LiteralInteger { value: 1, diff --git a/src/index/visitor.rs b/src/index/visitor.rs index 7b44b5987e..fb7257fab5 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -261,7 +261,7 @@ fn visit_implementation( index.register_pou(PouIndexEntry::create_action_entry( implementation.name.as_str(), implementation.type_name.as_str(), - ast::LinkageType::Internal, //TODO: where do I get correct linkage from? + implementation.linkage, symbol_location_factory.create_symbol_location(&implementation.name_location), )); index.register_pou_type(datatype); diff --git a/src/lexer/tests/lexer_tests.rs b/src/lexer/tests/lexer_tests.rs index 36c7563d1e..9f0b5bed4c 100644 --- a/src/lexer/tests/lexer_tests.rs +++ b/src/lexer/tests/lexer_tests.rs @@ -540,11 +540,11 @@ fn dot_statements() { fn range_statements() { let mut lexer = lex(r"123..ABC"); - println!("{:?}", lexer.token); + assert_eq!(lexer.token, LiteralInteger); lexer.advance(); - println!("{:?}", lexer.token); + assert_eq!(lexer.token, KeywordDotDot); lexer.advance(); - println!("{:?}", lexer.token); + assert_eq!(lexer.token, Identifier); lexer.advance(); } diff --git a/src/parser.rs b/src/parser.rs index 631b829680..17914e613b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,8 +2,8 @@ use plc_ast::{ ast::{ AccessModifier, ArgumentProperty, AstStatement, CompilationUnit, DataType, DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType, Implementation, LinkageType, NewLines, - PolymorphismMode, Pou, PouType, SourceRange, SourceRangeFactory, TypeNature, UserTypeDeclaration, - Variable, VariableBlock, VariableBlockType, + PolymorphismMode, Pou, PouType, ReferenceAccess, SourceRange, SourceRangeFactory, TypeNature, + UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, }, provider::IdProvider, }; @@ -700,7 +700,7 @@ fn parse_type_reference_type_definition( scope: lexer.scope.clone(), } } - Some(AstStatement::Reference { .. }) => { + Some(AstStatement::ReferenceExpr { access: ReferenceAccess::Member(_), .. }) => { // a enum with just one element DataTypeDeclaration::DataTypeDefinition { data_type: DataType::EnumType { @@ -927,7 +927,7 @@ pub fn parse_any_in_region T>( } fn parse_reference(lexer: &mut ParseSession) -> AstStatement { - match expressions_parser::parse_qualified_reference(lexer) { + match expressions_parser::parse_call_statement(lexer) { Ok(statement) => statement, Err(diagnostic) => { let statement = diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index 5c5094ff05..eefe7025c4 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -7,7 +7,7 @@ use crate::{ }; use core::str::Split; use plc_ast::{ - ast::{AstId, AstStatement, DirectAccessType, Operator, SourceRange}, + ast::{AstFactory, AstId, AstStatement, DirectAccessType, Operator, SourceRange}, literals::{AstLiteral, Time}, }; use plc_diagnostics::diagnostics::Diagnostic; @@ -142,11 +142,11 @@ fn parse_exponent_expression(lexer: &mut ParseSession) -> AstStatement { lexer.advance(); let right = parse_unary_expression(lexer); left = AstStatement::CallStatement { - operator: Box::new(AstStatement::Reference { - name: "EXPT".to_string(), - location: op_location, - id: lexer.next_id(), - }), + operator: Box::new(AstFactory::create_member_reference( + AstFactory::create_identifier("EXPT", &op_location, lexer.next_id()), + None, + lexer.next_id(), + )), parameters: Box::new(Some(AstStatement::ExpressionList { expressions: vec![left, right], id: lexer.next_id(), @@ -167,14 +167,13 @@ fn parse_unary_expression(lexer: &mut ParseSession) -> AstStatement { OperatorNot => Some(Operator::Not), OperatorPlus => Some(Operator::Plus), OperatorMinus => Some(Operator::Minus), - OperatorAmp => Some(Operator::Address), _ => None, } { operators.push(operator); lexer.advance(); } // created nested statements if necessary (e.g. &&) - let init = parse_parenthesized_expression(lexer); + let init = parse_leaf_expression(lexer); operators.iter().rev().fold(init, |expression, operator| { let expression_location = expression.get_location(); let location = lexer.source_range_factory.create_range(start..expression_location.get_end()); @@ -189,8 +188,8 @@ fn parse_unary_expression(lexer: &mut ParseSession) -> AstStatement { } // Return the reference itself instead of wrapping it inside a `AstStatement::UnaryExpression` - (Operator::Plus, AstStatement::Reference { name, .. }) => { - AstStatement::Reference { name: name.to_owned(), location, id: lexer.next_id() } + (Operator::Plus, AstStatement::Identifier { name, .. }) => { + AstFactory::create_identifier(name, &location, lexer.next_id()) } _ => AstStatement::UnaryExpression { @@ -203,27 +202,6 @@ fn parse_unary_expression(lexer: &mut ParseSession) -> AstStatement { }) } -// PARENTHESIZED (...) -fn parse_parenthesized_expression(lexer: &mut ParseSession) -> AstStatement { - let result = match lexer.token { - KeywordParensOpen => { - lexer.advance(); - super::parse_any_in_region(lexer, vec![KeywordParensClose], parse_expression) - } - _ => parse_leaf_expression(lexer), - }; - // we might deal with a deref after a paren-expr - match parse_access_modifiers(lexer, result) { - Ok(statement) => statement, - Err(diagnostic) => { - let statement = - AstStatement::EmptyStatement { location: diagnostic.get_location(), id: lexer.next_id() }; - lexer.accept_diagnostic(diagnostic); - statement - } - } -} - fn to_operator(token: &Token) -> Option { match token { OperatorPlus => Some(Operator::Plus), @@ -248,85 +226,11 @@ fn to_operator(token: &Token) -> Option { // Literals, Identifiers, etc. fn parse_leaf_expression(lexer: &mut ParseSession) -> AstStatement { - //see if there's a cast - let literal_cast = if lexer.token == TypeCastPrefix { - let location = lexer.location(); - let mut a = lexer.slice_and_advance(); - a.pop(); //drop last char '#' - the lexer made sure it ends with a '#' - Some((a, location)) - } else { - None - }; let literal_parse_result = match lexer.token { - // Check if we're dealing with a number that has an explicit '+' or '-' sign... - OperatorPlus | OperatorMinus => { - let is_negative = lexer.token == OperatorMinus; - lexer.advance(); - - match lexer.token { - LiteralInteger => parse_literal_number(lexer, is_negative), - LiteralIntegerBin => parse_literal_number_with_modifier(lexer, 2, is_negative), - LiteralIntegerOct => parse_literal_number_with_modifier(lexer, 8, is_negative), - LiteralIntegerHex => parse_literal_number_with_modifier(lexer, 16, is_negative), - _ => Err(Diagnostic::unexpected_token_found( - "Numeric Literal", - lexer.slice(), - lexer.location(), - )), - } - } OperatorMultiplication => parse_vla_range(lexer), - // ...and if not then this token may be anything - _ => match lexer.token { - Identifier => parse_qualified_reference(lexer), - HardwareAccess((hw_type, access_type)) => parse_hardware_access(lexer, hw_type, access_type), - LiteralInteger => parse_literal_number(lexer, false), - LiteralIntegerBin => parse_literal_number_with_modifier(lexer, 2, false), - LiteralIntegerOct => parse_literal_number_with_modifier(lexer, 8, false), - LiteralIntegerHex => parse_literal_number_with_modifier(lexer, 16, false), - LiteralDate => parse_literal_date(lexer), - LiteralTimeOfDay => parse_literal_time_of_day(lexer), - LiteralTime => parse_literal_time(lexer), - LiteralDateAndTime => parse_literal_date_and_time(lexer), - LiteralString => parse_literal_string(lexer, false), - LiteralWideString => parse_literal_string(lexer, true), - LiteralTrue => parse_bool_literal(lexer, true), - LiteralFalse => parse_bool_literal(lexer, false), - LiteralNull => parse_null_literal(lexer), - KeywordSquareParensOpen => parse_array_literal(lexer), - _ => { - if lexer.closing_keywords.contains(&vec![KeywordParensClose]) - && matches!(lexer.last_token, KeywordOutputAssignment | KeywordAssignment) - { - // due to closing keyword ')' and last_token '=>' / ':=' - // we are probably in a call statement missing a parameter assignment 'foo(param := ); - // optional parameter assignments are allowed, validation should handle any unwanted cases - Ok(AstStatement::EmptyStatement { location: lexer.location(), id: lexer.next_id() }) - } else { - Err(Diagnostic::unexpected_token_found("Literal", lexer.slice(), lexer.location())) - } - } - }, + _ => parse_call_statement(lexer), }; - let literal_parse_result = literal_parse_result.and_then(|statement| { - if let Some((cast, location)) = literal_cast { - //check if there is something between the literal-type and the literal itself - if location.get_end() != statement.get_location().get_start() { - return Err(Diagnostic::syntax_error("Incomplete statement", location)); - } - - Ok(AstStatement::CastStatement { - id: lexer.next_id(), - location: (location.get_start()..statement.get_location().get_end()).into(), - target: Box::new(statement), - type_name: cast, - }) - } else { - Ok(statement) - } - }); - match literal_parse_result { Ok(statement) => { if lexer.token == KeywordAssignment { @@ -356,6 +260,71 @@ fn parse_leaf_expression(lexer: &mut ParseSession) -> AstStatement { } } +/// parse an expression at the bottom of the parse-tree. +/// leaf-expressions are literals, identifier, direct-access and parenthesized expressions +/// (since the parentheses change the parse-priority) +fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Result { + // Check if we're dealing with a number that has an explicit '+' or '-' sign... + + match lexer.token { + OperatorPlus | OperatorMinus => { + let is_negative = lexer.token == OperatorMinus; + lexer.advance(); + + match lexer.token { + LiteralInteger => parse_literal_number(lexer, is_negative), + LiteralIntegerBin => parse_literal_number_with_modifier(lexer, 2, is_negative), + LiteralIntegerOct => parse_literal_number_with_modifier(lexer, 8, is_negative), + LiteralIntegerHex => parse_literal_number_with_modifier(lexer, 16, is_negative), + _ => Err(Diagnostic::unexpected_token_found( + "Numeric Literal", + lexer.slice(), + lexer.location(), + )), + } + } + KeywordParensOpen => { + parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { + lexer.advance(); // eat KeywordParensOpen + Ok(parse_expression(lexer)) + }) + } + Identifier => Ok(parse_identifier(lexer)), + HardwareAccess((hw_type, access_type)) => parse_hardware_access(lexer, hw_type, access_type), + LiteralInteger => parse_literal_number(lexer, false), + LiteralIntegerBin => parse_literal_number_with_modifier(lexer, 2, false), + LiteralIntegerOct => parse_literal_number_with_modifier(lexer, 8, false), + LiteralIntegerHex => parse_literal_number_with_modifier(lexer, 16, false), + LiteralDate => parse_literal_date(lexer), + LiteralTimeOfDay => parse_literal_time_of_day(lexer), + LiteralTime => parse_literal_time(lexer), + LiteralDateAndTime => parse_literal_date_and_time(lexer), + LiteralString => parse_literal_string(lexer, false), + LiteralWideString => parse_literal_string(lexer, true), + LiteralTrue => parse_bool_literal(lexer, true), + LiteralFalse => parse_bool_literal(lexer, false), + LiteralNull => parse_null_literal(lexer), + KeywordSquareParensOpen => parse_array_literal(lexer), + DirectAccess(access) => parse_direct_access(lexer, access), + _ => { + if lexer.closing_keywords.contains(&vec![KeywordParensClose]) + && matches!(lexer.last_token, KeywordOutputAssignment | KeywordAssignment) + { + // due to closing keyword ')' and last_token '=>' / ':=' + // we are probably in a call statement missing a parameter assignment 'foo(param := ); + // optional parameter assignments are allowed, validation should handle any unwanted cases + Ok(AstStatement::EmptyStatement { location: lexer.location(), id: lexer.next_id() }) + } else { + Err(Diagnostic::unexpected_token_found("Literal", lexer.slice(), lexer.location())) + } + } + } +} + +fn parse_identifier(lexer: &mut ParseSession<'_>) -> AstStatement { + AstFactory::create_identifier(&lexer.slice_and_advance(), &lexer.last_location(), lexer.next_id()) +} + fn parse_vla_range(lexer: &mut ParseSession) -> Result { lexer.advance(); Ok(AstStatement::VlaRangeStatement { id: lexer.next_id() }) @@ -394,39 +363,12 @@ fn parse_null_literal(lexer: &mut ParseSession) -> Result Result { - let start = lexer.range().start; - let mut reference_elements = vec![parse_reference_access(lexer)?]; - while lexer.try_consume(&KeywordDot) { - let segment = match lexer.token { - //Is this an integer? - LiteralInteger => { - let number = parse_strict_literal_integer(lexer)?; - let location = number.get_location().clone(); - Ok(AstStatement::DirectAccess { - access: DirectAccessType::Bit, - index: Box::new(number), - location, - id: lexer.next_id(), - }) - } - //Is this a direct access? - DirectAccess(access) => parse_direct_access(lexer, access), - _ => parse_reference_access(lexer), - }?; - - //Is this a direct access? - reference_elements.push(segment); - } - - let reference = match &reference_elements[..] { - [single_element] => single_element.clone(), - [_elements @ ..] => { - AstStatement::QualifiedReference { elements: reference_elements, id: lexer.next_id() } - } - }; +pub fn parse_call_statement(lexer: &mut ParseSession) -> Result { + let reference = parse_qualified_reference(lexer)?; + // is this a callstatement? if lexer.try_consume(&KeywordParensOpen) { + let start = reference.get_location().get_start(); // Call Statement let call_statement = if lexer.try_consume(&KeywordParensClose) { AstStatement::CallStatement { @@ -449,6 +391,104 @@ pub fn parse_qualified_reference(lexer: &mut ParseSession) -> Result Result { + let mut current = None; + let mut pos = lexer.parse_progress - 1; // force an initial loop + + // as long as we parse something we keep eating stuff eagerly + while lexer.parse_progress > pos { + pos = lexer.parse_progress; + match ( + current, + // only test for the tokens without eating it (Amp must not be consumed if it is in the middle of the chain) + [KeywordDot, KeywordSquareParensOpen, OperatorDeref, OperatorAmp, TypeCastPrefix] + .into_iter() + .find(|it| lexer.token == *it), + ) { + // No base, No token -> Beginning of a qualified reference + (None, None) => { + let exp = parse_atomic_leaf_expression(lexer)?; + // pack if this is something to be resolved + current = if matches!(exp, AstStatement::Identifier { .. }) { + Some(AstFactory::create_member_reference(exp, None, lexer.next_id())) + } else { + Some(exp) + }; + } + // base._ -> a segment of a qualified reference, we stand right after the dot + (Some(base), Some(KeywordDot)) => { + lexer.advance(); + let member = if lexer.token == LiteralInteger { + let index = parse_strict_literal_integer(lexer)?; + let location = index.get_location(); + AstFactory::create_direct_access(DirectAccessType::Bit, index, lexer.next_id(), location) + } else { + parse_atomic_leaf_expression(lexer)? + }; + current = Some(AstFactory::create_member_reference(member, Some(base), lexer.next_id())); + } + // CAST-Statement: INT#a.b.c + // this means INT#(a.b.c) rather than (INT#a).b.c + (_, Some(TypeCastPrefix)) => { + let location_start = lexer.location(); + let mut type_name = lexer.slice_and_advance(); + type_name.pop(); // get rid of the "#" at the end + let stmt = parse_atomic_leaf_expression(lexer)?; + let end = stmt.get_location(); + let type_range = + (location_start.get_start()..(location_start.get_start() + type_name.len())).into(); + current = Some(AstFactory::create_cast_statement( + AstFactory::create_member_reference( + AstFactory::create_identifier(type_name.as_str(), &type_range, lexer.next_id()), + None, + lexer.next_id(), + ), + stmt, + &location_start.span(&end), + lexer.next_id(), + )); + } + (Some(base), Some(KeywordSquareParensOpen)) => { + lexer.advance(); + let index_reference = + parse_any_in_region(lexer, vec![KeywordSquareParensClose], parse_expression); + let new_location = base.get_location().span(&lexer.last_location()); + current = Some({ + AstFactory::create_index_reference( + index_reference, + Some(base), + lexer.next_id(), + new_location, + ) + }) + } + (Some(base), Some(OperatorDeref)) => { + lexer.advance(); + let new_location = base.get_location().span(&lexer.last_location()); + current = Some(AstFactory::create_deref_reference(base, lexer.next_id(), new_location)) + } + (None, Some(OperatorAmp)) => { + lexer.advance(); + let op_location = lexer.last_location(); + // the address-of-operator has different order compared ot other segments, we first see the operator, then + // we expect the expression. so writing &a.b.c is more of &(a.b.c) instead of (&a).b.c. + // So we expect NO base, and operator and we parse the base now + let base = parse_call_statement(lexer)?; + let new_location = op_location.span(&base.get_location()); + current = Some(AstFactory::create_address_of_reference(base, lexer.next_id(), new_location)) + } + (last_current, _) => { + current = last_current; // exit the loop + } + } + } + if let Some(current) = current { + Ok(current) + } else { + parse_atomic_leaf_expression(lexer) + } +} + fn parse_direct_access( lexer: &mut ParseSession, access: DirectAccessType, @@ -459,7 +499,14 @@ fn parse_direct_access( //The next token can either be an integer or an identifier let index = match lexer.token { LiteralInteger => parse_strict_literal_integer(lexer), - Identifier => parse_reference_access(lexer), + Identifier => { + let location = lexer.location(); + Ok(AstFactory::create_member_reference( + AstFactory::create_identifier(lexer.slice_and_advance().as_str(), &location, lexer.next_id()), + None, + lexer.next_id(), + )) + } _ => Err(Diagnostic::unexpected_token_found("Integer or Reference", lexer.slice(), lexer.location())), }?; @@ -467,35 +514,6 @@ fn parse_direct_access( Ok(AstStatement::DirectAccess { access, index: Box::new(index), location, id: lexer.next_id() }) } -pub fn parse_reference_access(lexer: &mut ParseSession) -> Result { - let location = lexer.location(); - let reference = - AstStatement::Reference { name: lexer.slice_and_advance(), location, id: lexer.next_id() }; - parse_access_modifiers(lexer, reference) -} - -fn parse_access_modifiers( - lexer: &mut ParseSession, - original_reference: AstStatement, -) -> Result { - let mut reference = original_reference; - //If (while) we hit a dereference, parse and append the dereference to the result - while lexer.token == KeywordSquareParensOpen || lexer.token == OperatorDeref { - if lexer.try_consume(&KeywordSquareParensOpen) { - let access = parse_expression(lexer); - lexer.consume_or_report(KeywordSquareParensClose); - reference = AstStatement::ArrayAccess { - reference: Box::new(reference), - access: Box::new(access), - id: lexer.next_id(), - }; - } else if lexer.try_consume(&OperatorDeref) { - reference = AstStatement::PointerAccess { reference: Box::new(reference), id: lexer.next_id() } - } - } - Ok(reference) -} - fn parse_literal_number_with_modifier( lexer: &mut ParseSession, radix: u32, @@ -515,8 +533,8 @@ fn parse_literal_number_with_modifier( } fn parse_literal_number(lexer: &mut ParseSession, is_negative: bool) -> Result { - //correct the location if we just parsed a minus before let location = if is_negative { + //correct the location if we just parsed a minus before (lexer.last_range.start..lexer.location().get_end()).into() } else { lexer.location() diff --git a/src/parser/tests.rs b/src/parser/tests.rs index c275bf07bf..f9ccd75af8 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -1,5 +1,5 @@ use plc_ast::{ - ast::{AstStatement, SourceRange}, + ast::{AstFactory, AstStatement, ReferenceAccess, SourceRange}, literals::AstLiteral, }; @@ -20,7 +20,16 @@ mod variable_parser_tests; /// helper function to create references pub fn ref_to(name: &str) -> AstStatement { - AstStatement::Reference { location: SourceRange::undefined(), name: name.to_string(), id: 0 } + AstStatement::ReferenceExpr { + access: ReferenceAccess::Member(Box::new(AstFactory::create_identifier( + name, + &SourceRange::undefined(), + 0, + ))), + base: None, + id: 0, + location: SourceRange::undefined(), + } } /// helper function to create literal ints diff --git a/src/parser/tests/control_parser_tests.rs b/src/parser/tests/control_parser_tests.rs index 8e2e9655bf..906e77fc04 100644 --- a/src/parser/tests/control_parser_tests.rs +++ b/src/parser/tests/control_parser_tests.rs @@ -1,5 +1,6 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::test_utils::tests::parse; +use insta::assert_debug_snapshot; use plc_ast::{ ast::AstStatement, control_statements::{AstControlStatement, ForLoopStatement, IfStatement}, @@ -79,28 +80,7 @@ fn if_else_statement_with_expressions() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"IfStatement { - blocks: [ - ConditionalBlock { - condition: LiteralBool { - value: true, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ], - else_block: [ - Reference { - name: "y", - }, - ], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -122,48 +102,7 @@ fn if_elsif_elsif_else_statement_with_expressions() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"IfStatement { - blocks: [ - ConditionalBlock { - condition: LiteralBool { - value: true, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ConditionalBlock { - condition: Reference { - name: "y", - }, - body: [ - Reference { - name: "z", - }, - ], - }, - ConditionalBlock { - condition: Reference { - name: "w", - }, - body: [ - Reference { - name: "v", - }, - ], - }, - ], - else_block: [ - Reference { - name: "u", - }, - ], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -178,22 +117,7 @@ fn for_with_literals_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ForLoopStatement { - counter: Reference { - name: "y", - }, - start: Reference { - name: "x", - }, - end: LiteralInteger { - value: 10, - }, - by_step: None, - body: [], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -208,26 +132,7 @@ fn for_with_step_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ForLoopStatement { - counter: Reference { - name: "x", - }, - start: LiteralInteger { - value: 1, - }, - end: LiteralInteger { - value: 10, - }, - by_step: Some( - LiteralInteger { - value: 7, - }, - ), - body: [], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -242,22 +147,7 @@ fn for_with_reference_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ForLoopStatement { - counter: Reference { - name: "z", - }, - start: Reference { - name: "x", - }, - end: Reference { - name: "y", - }, - by_step: None, - body: [], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -275,28 +165,7 @@ fn for_with_body_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ForLoopStatement { - counter: Reference { - name: "z", - }, - start: Reference { - name: "x", - }, - end: Reference { - name: "y", - }, - by_step: None, - body: [ - Reference { - name: "x", - }, - Reference { - name: "y", - }, - ], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -334,21 +203,7 @@ fn while_with_expression() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"WhileLoopStatement { - condition: BinaryExpression { - operator: Less, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 7, - }, - }, - body: [], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -365,23 +220,7 @@ fn while_with_body_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"WhileLoopStatement { - condition: LiteralBool { - value: true, - }, - body: [ - Reference { - name: "x", - }, - Reference { - name: "y", - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -421,21 +260,7 @@ fn repeat_with_expression() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RepeatLoopStatement { - condition: BinaryExpression { - operator: Greater, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 7, - }, - }, - body: [], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -454,22 +279,7 @@ fn repeat_with_body_statement() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RepeatLoopStatement { - condition: LiteralBool { - value: true, - }, - body: [ - Reference { - name: "x", - }, - Reference { - name: "y", - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -486,27 +296,7 @@ fn case_statement_with_one_condition() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: LiteralInteger { - value: 1, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -524,28 +314,7 @@ fn case_statement_with_one_condition_with_trailling_comma() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: LiteralInteger { - value: 1, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -561,17 +330,7 @@ fn case_statement_with_else_and_no_condition() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -586,17 +345,7 @@ fn case_statement_with_no_conditions() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -615,31 +364,7 @@ fn case_statement_with_one_condition_and_an_else() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: LiteralInteger { - value: 1, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ], - else_block: [ - Reference { - name: "y", - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -658,27 +383,7 @@ fn case_statement_with_one_empty_condition_and_an_else() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: LiteralInteger { - value: 1, - }, - body: [], - }, - ], - else_block: [ - Reference { - name: "y", - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -697,53 +402,7 @@ fn case_statement_with_multiple_conditions() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: LiteralInteger { - value: 1, - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ConditionalBlock { - condition: LiteralInteger { - value: 2, - }, - body: [ - Reference { - name: "y", - }, - Reference { - name: "yy", - }, - Reference { - name: "yyy", - }, - ], - }, - ConditionalBlock { - condition: LiteralInteger { - value: 3, - }, - body: [ - Reference { - name: "z", - }, - ], - }, - ], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -760,60 +419,7 @@ fn case_statement_with_multiple_expressions_per_condition() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"CaseStatement { - selector: Reference { - name: "StateMachine", - }, - case_blocks: [ - ConditionalBlock { - condition: ExpressionList { - expressions: [ - LiteralInteger { - value: 1, - }, - LiteralInteger { - value: 2, - }, - LiteralInteger { - value: 3, - }, - ], - }, - body: [ - Reference { - name: "x", - }, - ], - }, - ConditionalBlock { - condition: ExpressionList { - expressions: [ - RangeStatement { - start: LiteralInteger { - value: 4, - }, - end: LiteralInteger { - value: 5, - }, - }, - LiteralInteger { - value: 6, - }, - ], - }, - body: [ - Reference { - name: "y", - }, - ], - }, - ], - else_block: [], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] diff --git a/src/parser/tests/expressions_parser_tests.rs b/src/parser/tests/expressions_parser_tests.rs index 20ed8009d5..77a5080514 100644 --- a/src/parser/tests/expressions_parser_tests.rs +++ b/src/parser/tests/expressions_parser_tests.rs @@ -1,10 +1,9 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder -use crate::parser::tests::{literal_int, ref_to}; +use crate::parser::tests::ref_to; use crate::test_utils::tests::parse; -use insta::assert_snapshot; +use insta::{assert_debug_snapshot, assert_snapshot}; use plc_ast::ast::{ - AstStatement, DataType, DataTypeDeclaration, DirectAccessType, LinkageType, Operator, Pou, PouType, - SourceRange, + AstFactory, AstStatement, DataType, DataTypeDeclaration, LinkageType, Operator, Pou, PouType, SourceRange, }; use plc_ast::literals::AstLiteral; use pretty_assertions::*; @@ -15,13 +14,7 @@ fn single_statement_parsed() { let result = parse(src).0; let prg = &result.implementations[0]; - let statement = &prg.statements[0]; - - if let AstStatement::Reference { name, .. } = statement { - assert_eq!(name, "x"); - } else { - panic!("Expected Reference but found {statement:?}"); - } + assert_debug_snapshot!(&prg.statements[0]); } #[test] @@ -30,22 +23,7 @@ fn qualified_reference_statement_parsed() { let result = parse(src).0; let prg = &result.implementations[0]; - let statement = &prg.statements[0]; - - if let AstStatement::QualifiedReference { elements, .. } = statement { - assert_eq!( - format!("{elements:?}"), - format!( - "{:?}", - &[ - AstStatement::Reference { name: "a".to_string(), location: (12..13).into(), id: 0 }, - AstStatement::Reference { name: "x".to_string(), location: (14..15).into(), id: 0 }, - ] - ) - ); - } else { - panic!("Expected Reference but found {statement:?}"); - } + assert_debug_snapshot!(&prg.statements[0]); } #[test] @@ -62,106 +40,7 @@ fn bitwise_access_parsed() { let (result, diagnostics) = parse(src); let prg = &result.implementations[0]; - let statement = &prg.statements; - let expected = vec![ - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - AstStatement::DirectAccess { - access: DirectAccessType::Bit, - index: Box::new(literal_int(0)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - AstStatement::DirectAccess { - access: DirectAccessType::Bit, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - AstStatement::DirectAccess { - access: DirectAccessType::Byte, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - AstStatement::DirectAccess { - access: DirectAccessType::Byte, - index: Box::new(ref_to("b")), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - AstStatement::ArrayAccess { - access: Box::new(literal_int(0)), - reference: Box::new(ref_to("a")), - id: 0, - }, - AstStatement::DirectAccess { - access: DirectAccessType::Word, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - ref_to("b"), - AstStatement::DirectAccess { - access: DirectAccessType::DWord, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - AstStatement::QualifiedReference { - elements: vec![ - ref_to("a"), - AstStatement::DirectAccess { - access: DirectAccessType::Byte, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - AstStatement::DirectAccess { - access: DirectAccessType::Bit, - index: Box::new(literal_int(1)), - location: SourceRange::undefined(), - id: 0, - }, - ], - id: 0, - }, - ]; - - assert_eq!(format!("{expected:?}"), format!("{statement:?}")); + assert_debug_snapshot!(&prg.statements); assert_eq!(true, diagnostics.is_empty()); } @@ -171,13 +50,7 @@ fn literal_can_be_parsed() { let result = parse(src).0; let prg = &result.implementations[0]; - let statement = &prg.statements[0]; - - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &7_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(&prg.statements[0]); } #[test] @@ -187,12 +60,7 @@ fn literal_binary_with_underscore_number_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &45_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -203,11 +71,7 @@ fn literal_hex_number_with_underscores_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &3735928559_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -218,11 +82,7 @@ fn literal_hex_number_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &3735928559_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -233,11 +93,7 @@ fn literal_oct_number_with_underscores_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &63_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -248,11 +104,7 @@ fn literal_dec_number_with_underscores_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &43000_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -263,38 +115,23 @@ fn literal_oct_number_with_underscore_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::Literal { kind: AstLiteral::Integer(value), .. } = statement { - assert_eq!(value, &63_i128); - } else { - panic!("Expected LiteralInteger but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] -fn additon_of_two_variables_parsed() { - let src = "PROGRAM exp x+y; END_PROGRAM"; +fn binary_stmts_of_two_variables_parsed() { + let src = "PROGRAM exp + x+y; + x.y = y.z; + x.y - y.z; + &x.y = y.z; + END_PROGRAM"; let result = parse(src).0; let prg = &result.implementations[0]; - let statement = &prg.statements[0]; + let statement = &prg.statements; - if let AstStatement::BinaryExpression { - operator, - left, //Box {name : left}), - right, //Box {name : right}), - .. - } = statement - { - if let AstStatement::Reference { name, .. } = &**left { - assert_eq!(name, "x"); - } - if let AstStatement::Reference { name, .. } = &**right { - assert_eq!(name, "y"); - } - assert_eq!(operator, &Operator::Plus); - } else { - panic!("Expected Reference but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -305,31 +142,7 @@ fn additon_of_three_variables_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::BinaryExpression { - operator, - left, //Box {name : left}), - right, //Box {name : right}), - .. - } = statement - { - assert_eq!(operator, &Operator::Minus); - if let AstStatement::BinaryExpression { operator, left, right, .. } = &**left { - if let AstStatement::Reference { name, .. } = &**left { - assert_eq!(name, "x"); - } - if let AstStatement::Reference { name, .. } = &**right { - assert_eq!(name, "y"); - } - assert_eq!(operator, &Operator::Plus); - } else { - panic!("Expected Reference but found {statement:?}"); - } - if let AstStatement::Reference { name, .. } = &**right { - assert_eq!(name, "z"); - } - } else { - panic!("Expected Reference but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -340,17 +153,7 @@ fn parenthesis_expressions_should_not_change_the_ast() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - if let AstStatement::BinaryExpression { operator, left, right, .. } = statement { - if let AstStatement::Reference { name, .. } = &**left { - assert_eq!(name, "x"); - } - if let AstStatement::Reference { name, .. } = &**right { - assert_eq!(name, "y"); - } - assert_eq!(operator, &Operator::Plus); - } else { - panic!("Expected Reference but found {statement:?}"); - } + assert_debug_snapshot!(statement); } #[test] @@ -361,24 +164,9 @@ fn multiplication_expressions_parse() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Division, - left: BinaryExpression { - operator: Multiplication, - left: LiteralInteger { - value: 1, - }, - right: LiteralInteger { - value: 2, - }, - }, - right: LiteralInteger { - value: 7, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } + #[test] fn exponent_expressions_parse() { let src = "PROGRAM exp 1**2; END_PROGRAM"; @@ -398,17 +186,7 @@ fn addition_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: LiteralInteger { - value: 2, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -419,23 +197,7 @@ fn multiplication_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: BinaryExpression { - operator: Multiplication, - left: LiteralInteger { - value: 2, - }, - right: LiteralInteger { - value: 3, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -446,29 +208,7 @@ fn term_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Plus, - left: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: BinaryExpression { - operator: Multiplication, - left: LiteralInteger { - value: 2, - }, - right: LiteralInteger { - value: 3, - }, - }, - }, - right: LiteralInteger { - value: 4, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -480,18 +220,7 @@ fn module_expression_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Modulo, - left: LiteralInteger { - value: 5, - }, - right: LiteralInteger { - value: 2, - }, -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -502,29 +231,7 @@ fn parenthesized_term_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Multiplication, - left: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: LiteralInteger { - value: 2, - }, - }, - right: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 3, - }, - right: LiteralInteger { - value: 4, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -535,17 +242,7 @@ fn boolean_literals_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Or, - left: LiteralBool { - value: true, - }, - right: LiteralBool { - value: false, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -553,40 +250,8 @@ fn assignment_test() { let src = "PROGRAM exp x := 3; x := 1 + 2; END_PROGRAM"; let result = parse(src).0; - let prg = &result.implementations[0]; - { - let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 3, - }, -}"#; - assert_eq!(ast_string, expected_ast); - } - - { - let statement = &prg.statements[1]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: LiteralInteger { - value: 2, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); - } + let prg = &result.implementations[0].statements; + assert_debug_snapshot!(prg); } #[test] @@ -594,49 +259,10 @@ fn equality_expression_test() { let src = "PROGRAM exp x = 3; x - 0 <> 1 + 2; END_PROGRAM"; let result = parse(src).0; - let prg = &result.implementations[0]; - { - let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Equal, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 3, - }, -}"#; - assert_eq!(ast_string, expected_ast); - } - - { - let statement = &prg.statements[1]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: NotEqual, - left: BinaryExpression { - operator: Minus, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 0, - }, - }, - right: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 1, - }, - right: LiteralInteger { - value: 2, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); - } + let prg = &result.implementations[0].statements; + assert_debug_snapshot!(prg); } + #[test] fn comparison_expression_test() { let src = "PROGRAM exp @@ -648,90 +274,8 @@ fn comparison_expression_test() { END_PROGRAM"; let result = parse(src).0; - let prg = &result.implementations[0]; - { - let statement = &prg.statements[0]; - let expected_ast = r#"BinaryExpression { - operator: Less, - left: Reference { - name: "a", - }, - right: LiteralInteger { - value: 3, - }, -}"#; - assert_eq!(format!("{statement:#?}"), expected_ast); - } - { - let statement = &prg.statements[1]; // b > 0 - let expected_ast = r#"BinaryExpression { - operator: Greater, - left: Reference { - name: "b", - }, - right: LiteralInteger { - value: 0, - }, -}"#; - assert_eq!(format!("{statement:#?}"), expected_ast); - } - { - let statement = &prg.statements[2]; // c <= 7 - let expected_ast = r#"BinaryExpression { - operator: LessOrEqual, - left: Reference { - name: "c", - }, - right: LiteralInteger { - value: 7, - }, -}"#; - assert_eq!(format!("{statement:#?}"), expected_ast); - } - { - let statement = &prg.statements[3]; // d >= 4 - let expected_ast = r#"BinaryExpression { - operator: GreaterOrEqual, - left: Reference { - name: "d", - }, - right: LiteralInteger { - value: 4, - }, -}"#; - assert_eq!(format!("{statement:#?}"), expected_ast); - } - { - //e := 2 + 1 > 3 + 1; - let statement = &prg.statements[4]; - let expected_ast = r#"Assignment { - left: Reference { - name: "e", - }, - right: BinaryExpression { - operator: Greater, - left: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 2, - }, - right: LiteralInteger { - value: 1, - }, - }, - right: BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 3, - }, - right: LiteralInteger { - value: 1, - }, - }, - }, -}"#; - assert_eq!(format!("{statement:#?}"), expected_ast); - } + let prg = &result.implementations[0].statements; + assert_debug_snapshot!(prg); } #[test] @@ -742,32 +286,7 @@ fn boolean_expression_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Or, - left: BinaryExpression { - operator: And, - left: Reference { - name: "a", - }, - right: UnaryExpression { - operator: Not, - value: Reference { - name: "b", - }, - }, - }, - right: BinaryExpression { - operator: Xor, - left: Reference { - name: "c", - }, - right: Reference { - name: "d", - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -778,32 +297,7 @@ fn boolean_expression_param_ast_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: And, - left: Reference { - name: "a", - }, - right: BinaryExpression { - operator: Xor, - left: UnaryExpression { - operator: Not, - value: BinaryExpression { - operator: Or, - left: Reference { - name: "b", - }, - right: Reference { - name: "c", - }, - }, - }, - right: Reference { - name: "d", - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -818,11 +312,7 @@ fn signed_literal_minus_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"LiteralInteger { - value: -1, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1241,24 +731,24 @@ fn literal_real_test() { assert_eq!(ast_string, expected_ast); } -fn literal_int_cast(data_type: &str, value: i128) -> AstStatement { - AstStatement::CastStatement { - id: 0, - location: SourceRange::undefined(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_integer(value), - }), - type_name: data_type.to_string(), - } +fn cast(data_type: &str, value: AstStatement) -> AstStatement { + AstFactory::create_cast_statement( + AstFactory::create_member_reference( + AstFactory::create_identifier(data_type, &SourceRange::undefined(), 0), + None, + 0, + ), + value, + &SourceRange::undefined(), + 0, + ) } #[test] fn literal_enum_parse_test() { let src = r#" PROGRAM exp - MyEnum#Val1; + MyEnum#Val7; MyEnum#Val2; MyEnum#Val3; END_PROGRAM @@ -1267,34 +757,7 @@ fn literal_enum_parse_test() { let prg = &result.implementations[0]; let statement = &prg.statements; - - let ast_string = format!("{statement:#?}"); - assert_eq!( - ast_string, - format!( - "{:#?}", - vec![ - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "MyEnum".into(), - target: Box::new(ref_to("Val1")) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "MyEnum".into(), - target: Box::new(ref_to("Val2")) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "MyEnum".into(), - target: Box::new(ref_to("Val3")) - } - ] - ) - ); + assert_debug_snapshot!(statement); } #[test] @@ -1322,88 +785,28 @@ fn literal_cast_parse_test() { let statement = &prg.statements; let ast_string = format!("{statement:#?}"); + fn literal(value: AstLiteral) -> AstStatement { + AstStatement::Literal { kind: value, location: SourceRange::undefined(), id: 0 } + } + assert_eq!( ast_string, format!( "{:#?}", vec![ - literal_int_cast("SINT", 100), - literal_int_cast("DINT", 45054), - literal_int_cast("BYTE", 63), - literal_int_cast("WORD", 10), - literal_int_cast("INT", 100), - literal_int_cast("DINT", -100), - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "REAL".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_real("-3.1415".to_string()) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "BOOL".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_integer(1) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "BOOL".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_bool(false) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "STRING".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_string("abc".to_string(), true) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "WSTRING".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_string("xyz".to_string(), false) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "CHAR".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - - kind: AstLiteral::new_string("A".to_string(), true) - }) - }, - AstStatement::CastStatement { - id: 0, - location: (0..0).into(), - type_name: "WCHAR".into(), - target: Box::new(AstStatement::Literal { - id: 0, - location: (0..0).into(), - kind: AstLiteral::new_string("B".to_string(), false) - }) - }, + cast("SINT", literal(AstLiteral::new_integer(100))), + cast("DINT", literal(AstLiteral::new_integer(45054))), + cast("BYTE", literal(AstLiteral::new_integer(63))), + cast("WORD", literal(AstLiteral::new_integer(10))), + cast("INT", literal(AstLiteral::new_integer(100))), + cast("DINT", literal(AstLiteral::new_integer(-100))), + cast("REAL", literal(AstLiteral::new_real("-3.1415".into()))), + cast("BOOL", literal(AstLiteral::new_integer(1))), + cast("BOOL", literal(AstLiteral::new_bool(false))), + cast("STRING", literal(AstLiteral::new_string("abc".into(), true))), + cast("WSTRING", literal(AstLiteral::new_string("xyz".into(), false))), + cast("CHAR", literal(AstLiteral::new_string("A".into(), true))), + cast("WCHAR", literal(AstLiteral::new_string("B".to_string(), false))), ] ) ); @@ -1462,20 +865,7 @@ fn signed_literal_expression_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: 2, - }, - right: UnaryExpression { - operator: Minus, - value: Reference { - name: "x", - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1490,14 +880,7 @@ fn assignment_to_null() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralNull, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1515,20 +898,7 @@ fn assignment_to_number_with_implicit_and_explicit_plus_sign() { let result = parse(src).0; let statements = &result.implementations[0].statements; - let ast_string_implicit = format!("{:#?}", statements[0]); - let ast_string_explicit = format!("{:#?}", statements[1]); - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, -}"#; - - // Both the implicit and explicit assignment should yield the same output, namely `expected_ast` - assert_eq!(ast_string_implicit, ast_string_explicit); - assert_eq!(ast_string_implicit, expected_ast); + assert_debug_snapshot!(statements); } #[test] @@ -1545,18 +915,7 @@ fn assignment_to_number_reference_with_explicit_plus_sign() { let result = parse(src).0; let statements = &result.implementations[0].statements; - - let ast_string_explicit = format!("{:#?}", statements[1]); - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: Reference { - name: "x", - }, -}"#; - - assert_eq!(ast_string_explicit, expected_ast); + assert_debug_snapshot!(statements); } #[test] @@ -1570,15 +929,7 @@ fn pointer_address_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"UnaryExpression { - operator: Address, - value: Reference { - name: "x", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1593,13 +944,7 @@ fn pointer_dereference_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"PointerAccess { - reference: Reference { - name: "x", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1614,36 +959,7 @@ fn pointer_dereference_test_nested() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"PointerAccess { - reference: PointerAccess { - reference: ArrayAccess { - reference: PointerAccess { - reference: ArrayAccess { - reference: ArrayAccess { - reference: PointerAccess { - reference: PointerAccess { - reference: Reference { - name: "x", - }, - }, - }, - access: LiteralInteger { - value: 0, - }, - }, - access: LiteralInteger { - value: 1, - }, - }, - }, - access: LiteralInteger { - value: 2, - }, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1658,17 +974,7 @@ fn signed_literal_expression_reversed_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Plus, - left: LiteralInteger { - value: -4, - }, - right: LiteralInteger { - value: 5, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1683,23 +989,7 @@ fn or_compare_expressions_priority_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Or, - left: BinaryExpression { - operator: Greater, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, - }, - right: Reference { - name: "b1", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1714,29 +1004,7 @@ fn addition_compare_or_priority_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Or, - left: BinaryExpression { - operator: Greater, - left: BinaryExpression { - operator: Plus, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, - }, - right: LiteralInteger { - value: 2, - }, - }, - right: Reference { - name: "b1", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1751,17 +1019,7 @@ fn and_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: And, - left: Reference { - name: "b", - }, - right: Reference { - name: "c", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1772,21 +1030,10 @@ fn amp_as_and_test() { END_PROGRAM "; let result = parse(src).0; - println!("result= {result:?}"); let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: And, - left: Reference { - name: "b", - }, - right: Reference { - name: "c", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1797,24 +1044,10 @@ fn amp_as_and_with_address_test() { END_PROGRAM "; let result = parse(src).0; - println!("result= {result:?}"); let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: And, - left: Reference { - name: "b", - }, - right: UnaryExpression { - operator: Address, - value: Reference { - name: "c", - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1829,29 +1062,7 @@ fn boolean_priority_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Or, - left: BinaryExpression { - operator: Xor, - left: BinaryExpression { - operator: And, - left: Reference { - name: "a", - }, - right: Reference { - name: "b", - }, - }, - right: Reference { - name: "c", - }, - }, - right: Reference { - name: "d", - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1865,30 +1076,7 @@ fn comparison_priority_test() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"BinaryExpression { - operator: Equal, - left: BinaryExpression { - operator: Less, - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 7, - }, - }, - right: BinaryExpression { - operator: Greater, - left: Reference { - name: "y", - }, - right: LiteralInteger { - value: 6, - }, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1904,21 +1092,7 @@ fn expression_list() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ExpressionList { - expressions: [ - LiteralInteger { - value: 1, - }, - LiteralInteger { - value: 2, - }, - LiteralInteger { - value: 3, - }, - ], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1934,36 +1108,7 @@ fn expression_list_assignments() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"ExpressionList { - expressions: [ - Assignment { - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, - }, - Assignment { - left: Reference { - name: "y", - }, - right: LiteralInteger { - value: 2, - }, - }, - Assignment { - left: Reference { - name: "z", - }, - right: LiteralInteger { - value: 3, - }, - }, - ], -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -1980,57 +1125,8 @@ fn range_expression() { let result = parse(src).0; let prg = &result.implementations[0]; - let statement = &prg.statements[0]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RangeStatement { - start: Reference { - name: "a", - }, - end: Reference { - name: "b", - }, -}"#; - assert_eq!(ast_string, expected_ast); - - let statement = &prg.statements[1]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RangeStatement { - start: LiteralInteger { - value: 1, - }, - end: LiteralInteger { - value: 2, - }, -}"#; - assert_eq!(ast_string, expected_ast); - - let statement = &prg.statements[2]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RangeStatement { - start: Reference { - name: "a", - }, - end: LiteralInteger { - value: 2, - }, -}"#; - assert_eq!(ast_string, expected_ast); - - let statement = &prg.statements[3]; - - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"RangeStatement { - start: LiteralInteger { - value: 2, - }, - end: Reference { - name: "a", - }, -}"#; - assert_eq!(ast_string, expected_ast); + let statements = &prg.statements; + assert_debug_snapshot!(statements) } #[test] @@ -2117,17 +1213,7 @@ fn function_call_no_params() { let parse_result = parse(src).0; let statement = &parse_result.implementations[0].statements[0]; - - let ast_string = format!("{statement:#?}"); - - let expected_ast = r#"CallStatement { - operator: Reference { - name: "fn", - }, - parameters: None, -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2141,30 +1227,7 @@ fn function_call_params() { let statement = &parse_result.implementations[0].statements[0]; - let ast_string = format!("{statement:#?}"); - - let expected_ast = r#"CallStatement { - operator: Reference { - name: "fn", - }, - parameters: Some( - ExpressionList { - expressions: [ - LiteralInteger { - value: 1, - }, - LiteralInteger { - value: 2, - }, - LiteralInteger { - value: 3, - }, - ], - }, - ), -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2179,31 +1242,7 @@ fn function_call_params_with_trailling_comma() { assert_eq!(diagnostics, vec![]); let statement = &parse_result.implementations[0].statements[0]; - - let ast_string = format!("{statement:#?}"); - - let expected_ast = r#"CallStatement { - operator: Reference { - name: "fn", - }, - parameters: Some( - ExpressionList { - expressions: [ - LiteralInteger { - value: 1, - }, - LiteralInteger { - value: 2, - }, - LiteralInteger { - value: 3, - }, - ], - }, - ), -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2230,27 +1269,7 @@ fn string_can_be_parsed() { "###); let statements = &prg.statements; - let ast_string = format!("{:#?}", statements[0]); - assert_snapshot!(ast_string, @r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralString { - value: "Hello, World!", - is_wide: false, - }, -}"#); - - let ast_string = format!("{:#?}", statements[1]); - assert_snapshot!(ast_string, @r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralString { - value: "", - is_wide: false, - }, -}"#); + assert_debug_snapshot!(statements); } #[test] @@ -2277,27 +1296,7 @@ fn wide_string_can_be_parsed() { "###); let statements = &prg.statements; - let ast_string = format!("{:#?}", statements[0]); - assert_snapshot!(ast_string, @r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralString { - value: "Hello, World!", - is_wide: true, - }, -}"#); - - let ast_string = format!("{:#?}", statements[1]); - assert_snapshot!(ast_string, @r#"Assignment { - left: Reference { - name: "x", - }, - right: LiteralString { - value: "", - is_wide: true, - }, -}"#); + assert_debug_snapshot!(statements); } #[test] @@ -2340,39 +1339,7 @@ fn arrays_can_be_parsed() { "###); let statements = &prg.statements; - let ast_string = format!("{:#?}", statements[0]); - assert_snapshot!(ast_string, @r#"Assignment { - left: ArrayAccess { - reference: Reference { - name: "x", - }, - access: LiteralInteger { - value: 0, - }, - }, - right: LiteralString { - value: "Hello, World!", - is_wide: false, - }, -}"#); - //assert_eq!(ast_string, expected_ast); - - let ast_string = format!("{:#?}", statements[1]); - let expected_ast = r#"Assignment { - left: ArrayAccess { - reference: Reference { - name: "x", - }, - access: Reference { - name: "y", - }, - }, - right: LiteralString { - value: "", - is_wide: false, - }, -}"#; - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statements); } #[test] @@ -2427,47 +1394,7 @@ fn nested_arrays_can_be_parsed() { "###); let statements = &prg.statements; - let ast_string = format!("{:#?}", statements[0]); - assert_snapshot!(ast_string, @r#"Assignment { - left: ArrayAccess { - reference: ArrayAccess { - reference: Reference { - name: "x", - }, - access: LiteralInteger { - value: 0, - }, - }, - access: LiteralInteger { - value: 1, - }, - }, - right: LiteralString { - value: "Hello, World!", - is_wide: false, - }, -}"#); - - let ast_string = format!("{:#?}", statements[1]); - assert_snapshot!(ast_string, @r#"Assignment { - left: ArrayAccess { - reference: ArrayAccess { - reference: Reference { - name: "x", - }, - access: Reference { - name: "y", - }, - }, - access: LiteralInteger { - value: 1, - }, - }, - right: LiteralString { - value: "", - is_wide: false, - }, -}"#); + assert_debug_snapshot!(statements); } #[test] @@ -2520,51 +1447,7 @@ fn multidim_arrays_can_be_parsed() { "###); let statements = &prg.statements; - let ast_string = format!("{:#?}", statements[0]); - assert_snapshot!(ast_string, @r#"Assignment { - left: ArrayAccess { - reference: Reference { - name: "x", - }, - access: ExpressionList { - expressions: [ - LiteralInteger { - value: 0, - }, - LiteralInteger { - value: 1, - }, - ], - }, - }, - right: LiteralString { - value: "Hello, World!", - is_wide: false, - }, -}"#); - - let ast_string = format!("{:#?}", statements[1]); - assert_snapshot!(ast_string, @r#"Assignment { - left: ArrayAccess { - reference: Reference { - name: "x", - }, - access: ExpressionList { - expressions: [ - Reference { - name: "y", - }, - LiteralInteger { - value: 1, - }, - ], - }, - }, - right: LiteralString { - value: "", - is_wide: false, - }, -}"#); + assert_debug_snapshot!(statements); } #[test] @@ -2575,24 +1458,7 @@ fn arrays_in_structs_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"QualifiedReference { - elements: [ - Reference { - name: "x", - }, - ArrayAccess { - reference: Reference { - name: "y", - }, - access: LiteralInteger { - value: 7, - }, - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2603,24 +1469,7 @@ fn arrays_of_structs_can_be_parsed() { let prg = &result.implementations[0]; let statement = &prg.statements[0]; - let ast_string = format!("{statement:#?}"); - let expected_ast = r#"QualifiedReference { - elements: [ - ArrayAccess { - reference: Reference { - name: "x", - }, - access: LiteralInteger { - value: 1, - }, - }, - Reference { - name: "y", - }, - ], -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2633,46 +1482,7 @@ fn function_call_formal_params() { let parse_result = parse(src).0; let statement = &parse_result.implementations[0].statements[0]; - - let ast_string = format!("{statement:#?}"); - - let expected_ast = r#"CallStatement { - operator: Reference { - name: "fn", - }, - parameters: Some( - ExpressionList { - expressions: [ - Assignment { - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, - }, - Assignment { - left: Reference { - name: "y", - }, - right: LiteralInteger { - value: 2, - }, - }, - OutputAssignment { - left: Reference { - name: "z", - }, - right: Reference { - name: "a", - }, - }, - ], - }, - ), -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2685,36 +1495,7 @@ fn function_call_return_params() { let parse_result = parse(src).0; let statement = &parse_result.implementations[0].statements[0]; - - let ast_string = format!("{statement:#?}"); - - let expected_ast = r#"Assignment { - left: Reference { - name: "x", - }, - right: CallStatement { - operator: Reference { - name: "fn", - }, - parameters: Some( - ExpressionList { - expressions: [ - LiteralInteger { - value: 1, - }, - LiteralInteger { - value: 2, - }, - LiteralInteger { - value: 3, - }, - ], - }, - ), - }, -}"#; - - assert_eq!(ast_string, expected_ast); + assert_debug_snapshot!(statement); } #[test] @@ -2762,6 +1543,26 @@ fn reference_location_test() { assert_eq!(source[location.get_start()..location.get_end()].to_string(), "ccc"); } +#[test] +fn qualified_reference_location_test() { + let source = "PROGRAM prg a.b.c;aa.bb.cc[2];aaa.bbb.ccc^;&aaa.bbb.ccc; END_PROGRAM"; + let parse_result = parse(source).0; + + let unit = &parse_result.implementations[0]; + + let location = &unit.statements[0].get_location(); + assert_eq!(source[location.get_start()..location.get_end()].to_string(), "a.b.c"); + + let location = &unit.statements[1].get_location(); + assert_eq!(source[location.get_start()..location.get_end()].to_string(), "aa.bb.cc[2]"); + + let location = &unit.statements[2].get_location(); + assert_eq!(source[location.get_start()..location.get_end()].to_string(), "aaa.bbb.ccc^"); + + let location = &unit.statements[3].get_location(); + assert_eq!(source[location.get_start()..location.get_end()].to_string(), "&aaa.bbb.ccc"); +} + #[test] fn expressions_location_test() { let source = " @@ -2967,7 +1768,7 @@ fn direct_access_as_expression_parsed() { // GIVEN a program with several types of direct access let src = " PROGRAM prg - x := 5 + %IX2.1; + x := 6 + %IX2.1; y := %MB200; z := %GD5 * 2; END_PROGRAM @@ -2977,5 +1778,5 @@ fn direct_access_as_expression_parsed() { let (result, _) = parse(src); //THEN the AST contains direct address nodes at the access location - assert_snapshot!(format!("{result:?}")); + assert_debug_snapshot!(result); } diff --git a/src/parser/tests/initializer_parser_tests.rs b/src/parser/tests/initializer_parser_tests.rs index 034af69730..840a05087c 100644 --- a/src/parser/tests/initializer_parser_tests.rs +++ b/src/parser/tests/initializer_parser_tests.rs @@ -1,3 +1,5 @@ +use insta::assert_debug_snapshot; + use crate::test_utils::tests::parse; #[test] @@ -295,35 +297,7 @@ fn struct_initializer_can_be_parsed() { "; let (parse_result, ..) = parse(src); let x = &parse_result.global_vars[0].variables[0]; - let expected = r#"Variable { - name: "x", - data_type: DataTypeReference { - referenced_type: "Point", - }, - initializer: Some( - ExpressionList { - expressions: [ - Assignment { - left: Reference { - name: "x", - }, - right: LiteralInteger { - value: 1, - }, - }, - Assignment { - left: Reference { - name: "y", - }, - right: LiteralInteger { - value: 2, - }, - }, - ], - }, - ), -}"#; - assert_eq!(expected, format!("{x:#?}").as_str()); + assert_debug_snapshot!(x); } #[test] diff --git a/src/parser/tests/misc_parser_tests.rs b/src/parser/tests/misc_parser_tests.rs index 029bfbb2c7..c6068d6ad4 100644 --- a/src/parser/tests/misc_parser_tests.rs +++ b/src/parser/tests/misc_parser_tests.rs @@ -1,12 +1,14 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +use crate::parser::tests::ref_to; use core::panic; -use std::ops::Range; +use std::{collections::HashSet, ops::Range}; use crate::{parser::tests::empty_stmt, test_utils::tests::parse}; use plc_ast::{ ast::{ AccessModifier, ArgumentProperty, AstFactory, AstStatement, DataTypeDeclaration, Implementation, - LinkageType, Operator, Pou, PouType, SourceRange, Variable, VariableBlock, VariableBlockType, + LinkageType, Operator, Pou, PouType, ReferenceAccess, SourceRange, Variable, VariableBlock, + VariableBlockType, }, control_statements::{AstControlStatement, CaseStatement, ForLoopStatement, IfStatement, LoopStatement}, literals::AstLiteral, @@ -82,11 +84,7 @@ fn exponent_literals_parsed_as_variables() { linkage: LinkageType::Internal, pou_type: PouType::Function, statements: vec![AstStatement::Assignment { - left: Box::new(AstStatement::Reference { - name: "E5".into(), - id: 0, - location: SourceRange::undefined(), - }), + left: Box::new(ref_to("E5")), right: Box::new(AstStatement::Literal { kind: AstLiteral::new_real("1.0E6".into()), id: 0, @@ -123,16 +121,16 @@ fn ids_are_assigned_to_parsed_literals() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - - assert_eq!(implementation.statements[0].get_id(), 1); - assert_eq!(implementation.statements[1].get_id(), 2); - assert_eq!(implementation.statements[2].get_id(), 3); - assert_eq!(implementation.statements[3].get_id(), 4); - assert_eq!(implementation.statements[4].get_id(), 5); - assert_eq!(implementation.statements[5].get_id(), 6); - assert_eq!(implementation.statements[6].get_id(), 7); - assert_eq!(implementation.statements[7].get_id(), 8); - assert_eq!(implementation.statements[8].get_id(), 9); + let mut ids = HashSet::new(); + assert!(ids.insert(implementation.statements[0].get_id())); + assert!(ids.insert(implementation.statements[1].get_id())); + assert!(ids.insert(implementation.statements[2].get_id())); + assert!(ids.insert(implementation.statements[3].get_id())); + assert!(ids.insert(implementation.statements[4].get_id())); + assert!(ids.insert(implementation.statements[5].get_id())); + assert!(ids.insert(implementation.statements[6].get_id())); + assert!(ids.insert(implementation.statements[7].get_id())); + assert!(ids.insert(implementation.statements[8].get_id())); } #[test] @@ -144,11 +142,12 @@ fn ids_are_assigned_to_parsed_assignments() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; + let mut ids = HashSet::new(); if let AstStatement::Assignment { id, left, right } = &implementation.statements[0] { - assert_eq!(left.get_id(), 1); - assert_eq!(right.get_id(), 2); - assert_eq!(*id, 3); + assert!(ids.insert(left.get_id())); + assert!(ids.insert(right.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } @@ -158,55 +157,56 @@ fn ids_are_assigned_to_parsed_assignments() { fn ids_are_assigned_to_callstatements() { let src = " PROGRAM PRG - foo(); - foo(1,2,3); - foo(a := 1, b => c, d); + foo(); + foo(1,2,3); + foo(a := 1, b => c, d); END_PROGRAM "; + let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - + let mut ids = HashSet::new(); if let AstStatement::CallStatement { id, operator, .. } = &implementation.statements[0] { - assert_eq!(operator.get_id(), 1); - assert_eq!(*id, 2); + assert!(ids.insert(operator.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::CallStatement { id, operator, parameters, .. } = &implementation.statements[1] { - assert_eq!(operator.get_id(), 3); + assert!(ids.insert(operator.get_id())); if let Some(AstStatement::ExpressionList { expressions, id }) = &**parameters { - assert_eq!(expressions[0].get_id(), 4); - assert_eq!(expressions[1].get_id(), 5); - assert_eq!(expressions[2].get_id(), 6); - assert_eq!(*id, 7); + assert!(ids.insert(expressions[0].get_id())); + assert!(ids.insert(expressions[1].get_id())); + assert!(ids.insert(expressions[2].get_id())); + assert!(ids.insert(*id)); } - assert_eq!(*id, 8); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::CallStatement { id, operator, parameters, .. } = &implementation.statements[2] { - assert_eq!(operator.get_id(), 9); + assert!(ids.insert(operator.get_id())); if let Some(AstStatement::ExpressionList { expressions, id }) = &**parameters { if let AstStatement::Assignment { left, right, id, .. } = &expressions[0] { - assert_eq!(left.get_id(), 10); - assert_eq!(right.get_id(), 11); - assert_eq!(*id, 12); + assert!(ids.insert(left.get_id())); + assert!(ids.insert(right.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::OutputAssignment { left, right, id, .. } = &expressions[1] { - assert_eq!(left.get_id(), 13); - assert_eq!(right.get_id(), 14); - assert_eq!(*id, 15); + assert!(ids.insert(left.get_id())); + assert!(ids.insert(right.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } - assert_eq!(expressions[2].get_id(), 16); - assert_eq!(*id, 17); + assert!(ids.insert(expressions[2].get_id())); + assert!(ids.insert(*id)); } - assert_eq!(*id, 18); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } @@ -228,63 +228,81 @@ fn ids_are_assigned_to_expressions() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; + let mut ids = HashSet::new(); if let AstStatement::BinaryExpression { id, left, right, .. } = &implementation.statements[0] { - assert_eq!(left.get_id(), 1); - assert_eq!(right.get_id(), 2); - assert_eq!(*id, 3); + assert!(ids.insert(left.get_id())); + assert!(ids.insert(right.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } - if let AstStatement::QualifiedReference { id, elements, .. } = &implementation.statements[1] { - assert_eq!(elements[0].get_id(), 4); - assert_eq!(elements[1].get_id(), 5); - assert_eq!(*id, 6); + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(m), base: Some(base), id, .. } = + &implementation.statements[1] + { + assert!(ids.insert(*id)); + assert!(ids.insert(m.get_id())); + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(m), base: None, .. } = + base.as_ref() + { + assert!(ids.insert(m.get_id())); + } else { + panic!("unexpected statement"); + } } else { panic!("unexpected statement"); } - if let AstStatement::Reference { id, .. } = &implementation.statements[2] { - assert_eq!(*id, 7); + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(m), base: None, id, .. } = + &implementation.statements[2] + { + assert!(ids.insert(*id)); + assert!(ids.insert(m.get_id())); } else { panic!("unexpected statement"); } - if let AstStatement::ArrayAccess { id, reference, access, .. } = &implementation.statements[3] { - assert_eq!(reference.get_id(), 8); - assert_eq!(access.get_id(), 9); - assert_eq!(*id, 10); + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Index(access), + base: Some(reference), + id, + .. + } = &implementation.statements[3] + { + assert!(ids.insert(reference.get_id())); + assert!(ids.insert(access.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::UnaryExpression { id, value, .. } = &implementation.statements[4] { - assert_eq!(value.get_id(), 11); - assert_eq!(*id, 12); + assert!(ids.insert(value.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::ExpressionList { id, expressions, .. } = &implementation.statements[5] { - assert_eq!(expressions[0].get_id(), 13); - assert_eq!(expressions[1].get_id(), 14); - assert_eq!(*id, 15); + assert!(ids.insert(expressions[0].get_id())); + assert!(ids.insert(expressions[1].get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::RangeStatement { id, start, end, .. } = &implementation.statements[6] { - assert_eq!(start.get_id(), 16); - assert_eq!(end.get_id(), 17); - assert_eq!(*id, 18); + assert!(ids.insert(start.get_id())); + assert!(ids.insert(end.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } if let AstStatement::MultipliedStatement { id, element, .. } = &implementation.statements[7] { - assert_eq!(element.get_id(), 19); - assert_eq!(*id, 20); + assert!(ids.insert(element.get_id())); + assert!(ids.insert(*id)); } else { panic!("unexpected statement"); } @@ -303,16 +321,16 @@ fn ids_are_assigned_to_if_statements() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - + let mut ids = HashSet::new(); match &implementation.statements[0] { AstStatement::ControlStatement { kind: AstControlStatement::If(IfStatement { blocks, else_block, .. }), .. } => { - assert_eq!(blocks[0].condition.get_id(), 1); - assert_eq!(blocks[0].body[0].get_id(), 2); - assert_eq!(else_block[0].get_id(), 3); - assert_eq!(implementation.statements[0].get_id(), 4); + assert!(ids.insert(blocks[0].condition.get_id())); + assert!(ids.insert(blocks[0].body[0].get_id())); + assert!(ids.insert(else_block[0].get_id())); + assert!(ids.insert(implementation.statements[0].get_id())); } _ => panic!("invalid statement"), } @@ -331,21 +349,21 @@ fn ids_are_assigned_to_for_statements() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - + let mut ids = HashSet::new(); match &implementation.statements[0] { AstStatement::ControlStatement { id, kind: AstControlStatement::ForLoop(ForLoopStatement { counter, start, end, by_step, body, .. }), .. } => { - assert_eq!(counter.get_id(), 1); - assert_eq!(start.get_id(), 2); - assert_eq!(end.get_id(), 3); - assert_eq!(by_step.as_ref().unwrap().get_id(), 4); - assert_eq!(body[0].get_id(), 5); - assert_eq!(body[1].get_id(), 6); - assert_eq!(body[2].get_id(), 7); - assert_eq!(*id, 8); + assert!(ids.insert(counter.get_id())); + assert!(ids.insert(start.get_id())); + assert!(ids.insert(end.get_id())); + assert!(ids.insert(by_step.as_ref().unwrap().get_id())); + assert!(ids.insert(body[0].get_id())); + assert!(ids.insert(body[1].get_id())); + assert!(ids.insert(body[2].get_id())); + assert!(ids.insert(*id)); } _ => panic!("invalid statement"), } @@ -362,16 +380,16 @@ fn ids_are_assigned_to_while_statements() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - + let mut ids = HashSet::new(); match &implementation.statements[0] { AstStatement::ControlStatement { kind: AstControlStatement::WhileLoop(LoopStatement { condition, body, .. }), .. } => { - assert_eq!(condition.get_id(), 1); - assert_eq!(body[0].get_id(), 2); - assert_eq!(body[1].get_id(), 3); - assert_eq!(implementation.statements[0].get_id(), 4); + assert!(ids.insert(condition.get_id())); + assert!(ids.insert(body[0].get_id())); + assert!(ids.insert(body[1].get_id())); + assert!(ids.insert(implementation.statements[0].get_id())); } _ => panic!("invalid statement"), } @@ -388,16 +406,17 @@ fn ids_are_assigned_to_repeat_statements() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; + let mut ids = HashSet::new(); match &implementation.statements[0] { AstStatement::ControlStatement { kind: AstControlStatement::RepeatLoop(LoopStatement { condition, body, .. }), .. } => { - assert_eq!(body[0].get_id(), 1); - assert_eq!(body[1].get_id(), 2); - assert_eq!(condition.get_id(), 3); - assert_eq!(implementation.statements[0].get_id(), 4); + assert!(ids.insert(body[0].get_id())); + assert!(ids.insert(body[1].get_id())); + assert!(ids.insert(condition.get_id())); + assert!(ids.insert(implementation.statements[0].get_id())); } _ => panic!("invalid statement"), } @@ -419,29 +438,29 @@ fn ids_are_assigned_to_case_statements() { "; let parse_result = parse(src).0; let implementation = &parse_result.implementations[0]; - + let mut ids = HashSet::new(); match &implementation.statements[0] { AstStatement::ControlStatement { kind: AstControlStatement::Case(CaseStatement { case_blocks, else_block, selector, .. }), .. } => { //1st case block - assert_eq!(selector.get_id(), 1); - assert_eq!(case_blocks[0].condition.get_id(), 2); - assert_eq!(case_blocks[0].body[0].get_id(), 4); + assert!(ids.insert(selector.get_id())); + assert!(ids.insert(case_blocks[0].condition.get_id())); + assert!(ids.insert(case_blocks[0].body[0].get_id())); //2nd case block if let AstStatement::ExpressionList { expressions, id, .. } = case_blocks[1].condition.as_ref() { - assert_eq!(expressions[0].get_id(), 5); - assert_eq!(expressions[1].get_id(), 6); - assert_eq!(*id, 7); + assert!(ids.insert(expressions[0].get_id())); + assert!(ids.insert(expressions[1].get_id())); + assert!(ids.insert(*id)); } else { panic!("expected expression list") } - assert_eq!(case_blocks[1].body[0].get_id(), 9); + assert!(ids.insert(case_blocks[1].body[0].get_id())); //else block - assert_eq!(else_block[0].get_id(), 10); + assert!(ids.insert(else_block[0].get_id())); } _ => panic!("invalid statement"), @@ -450,15 +469,6 @@ fn ids_are_assigned_to_case_statements() { #[test] fn id_implementation_for_all_statements() { - assert_eq!( - AstStatement::ArrayAccess { - access: Box::new(empty_stmt()), - reference: Box::new(empty_stmt()), - id: 7 - } - .get_id(), - 7 - ); assert_eq!( AstStatement::Assignment { left: Box::new(empty_stmt()), right: Box::new(empty_stmt()), id: 7 } .get_id(), @@ -523,14 +533,13 @@ fn id_implementation_for_all_statements() { .get_id(), 7 ); - assert_eq!(AstStatement::QualifiedReference { elements: vec![], id: 7 }.get_id(), 7); assert_eq!( AstStatement::RangeStatement { start: Box::new(empty_stmt()), end: Box::new(empty_stmt()), id: 7 } .get_id(), 7 ); assert_eq!( - AstStatement::Reference { name: "ab".to_string(), location: (1..5).into(), id: 7 }.get_id(), + AstStatement::Identifier { name: "ab".to_string(), location: (1..5).into(), id: 7 }.get_id(), 7 ); assert_eq!(AstFactory::create_repeat_statement(empty_stmt(), vec![], (1..5).into(), 7).get_id(), 7); @@ -553,11 +562,6 @@ fn at(location: Range) -> AstStatement { #[test] fn location_implementation_for_all_statements() { - assert_eq!( - AstStatement::ArrayAccess { reference: Box::new(at(0..1)), access: Box::new(at(2..4)), id: 7 } - .get_location(), - (0..4).into() - ); assert_eq!( AstStatement::Assignment { left: Box::new(at(0..2)), right: Box::new(at(3..8)), id: 7 } .get_location(), @@ -624,17 +628,13 @@ fn location_implementation_for_all_statements() { .get_location(), (0..9).into() ); - assert_eq!( - AstStatement::QualifiedReference { elements: vec![at(0..3), at(4..5)], id: 7 }.get_location(), - (0..5).into() - ); assert_eq!( AstStatement::RangeStatement { start: Box::new(at(0..3)), end: Box::new(at(6..9)), id: 7 } .get_location(), (0..9).into() ); assert_eq!( - AstStatement::Reference { name: "ab".to_string(), location: (1..5).into(), id: 7 }.get_location(), + AstStatement::Identifier { name: "ab".to_string(), location: (1..5).into(), id: 7 }.get_location(), (1..5).into() ); assert_eq!( 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 f4ba7e92c7..804b984233 100644 --- a/src/parser/tests/parse_errors/parse_error_containers_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_containers_tests.rs @@ -1,8 +1,9 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::{lexer::Token, test_utils::tests::parse}; +use insta::assert_debug_snapshot; use plc_ast::ast::{ - AccessModifier, AstStatement, DataTypeDeclaration, LinkageType, PouType, SourceRange, Variable, - VariableBlock, VariableBlockType, + AccessModifier, DataTypeDeclaration, LinkageType, PouType, SourceRange, Variable, VariableBlock, + VariableBlockType, }; use plc_diagnostics::diagnostics::Diagnostic; use pretty_assertions::*; @@ -32,13 +33,7 @@ fn missing_pou_name() { assert_eq!(diagnostics[0], expected); let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements[0]), - format!( - "{:#?}", - AstStatement::Reference { name: "a".into(), location: SourceRange::undefined(), id: 0 } - ) - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -61,13 +56,7 @@ fn missing_pou_name_2() { ); let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements[1]), - format!( - "{:#?}", - AstStatement::Reference { name: "x".into(), location: SourceRange::undefined(), id: 0 } - ) - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -91,13 +80,7 @@ fn illegal_end_pou_keyword() { //check if baz was parsed successfully let pou = &compilation_unit.implementations[1]; - assert_eq!( - format!("{:#?}", pou.statements), - format!( - "{:#?}", - vec![AstStatement::Reference { name: "b".into(), location: SourceRange::undefined(), id: 0 }] - ) - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -119,14 +102,7 @@ fn function_without_return_variable_declaration() { // AND I expect the body to be parsed successfully let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements), - r#"[ - Reference { - name: "a", - }, - ]"# - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -146,14 +122,7 @@ fn function_with_illegal_return_variable_declaration() { //check if a was parsed successfully let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements), - r#"[ - Reference { - name: "a", - }, -]"# - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -173,14 +142,7 @@ fn function_return_type_with_initializer() { //check if a was parsed successfully let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements), - r#"[ - Reference { - name: "a", - }, -]"# - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -200,13 +162,7 @@ fn program_with_illegal_return_variable_declaration() { //check if a was parsed successfully let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements), - format!( - "{:#?}", - vec![AstStatement::Reference { name: "a".into(), location: SourceRange::undefined(), id: 0 }] - ) - ); + assert_debug_snapshot!(pou.statements); } #[test] diff --git a/src/parser/tests/parse_errors/parse_error_literals_tests.rs b/src/parser/tests/parse_errors/parse_error_literals_tests.rs index 67b6c39ca0..51c1f04629 100644 --- a/src/parser/tests/parse_errors/parse_error_literals_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_literals_tests.rs @@ -202,5 +202,6 @@ fn literal_cast_with_space() { let src = "PROGRAM exp INT# 123; END_PROGRAM"; let (_, diagnostics) = parse(src); - assert_eq!(vec![Diagnostic::syntax_error("Incomplete statement", (12..16).into())], diagnostics); + // THEN this should work + assert_eq!(Vec::::new(), diagnostics); } 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 67c362e29e..eea6612245 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -1,5 +1,6 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::{lexer::Token, parser::tests::ref_to, test_utils::tests::parse}; +use insta::assert_debug_snapshot; use plc_ast::ast::{ AccessModifier, AstStatement, DataType, DataTypeDeclaration, LinkageType, SourceRange, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, @@ -35,17 +36,7 @@ fn missing_semicolon_after_call() { assert_eq!(diagnostics[0], expected); let pou = &compilation_unit.implementations[0]; - assert_eq!( - format!("{:#?}", pou.statements), - r#"[ - CallStatement { - operator: Reference { - name: "buz", - }, - parameters: None, - }, -]"# - ); + assert_debug_snapshot!(pou.statements); } #[test] @@ -152,8 +143,13 @@ fn incomplete_statement_test() { }, right: EmptyStatement, }, - Reference { - name: "x", + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, ]"# ); @@ -194,8 +190,13 @@ fn incomplete_statement_in_parantheses_recovery_test() { value: 3, }, }, - Reference { - name: "x", + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, ]"# ); @@ -226,8 +227,13 @@ fn mismatched_parantheses_recovery_test() { value: 2, }, }, - Reference { - name: "x", + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, ]"# ); @@ -384,11 +390,21 @@ fn test_nested_if_with_missing_end_if() { }, body: [ Assignment { - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, ], @@ -397,11 +413,21 @@ fn test_nested_if_with_missing_end_if() { else_block: [], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -459,8 +485,13 @@ fn test_nested_for_with_missing_end_for() { @r###" [ ForLoopStatement { - counter: Reference { - name: "x", + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, start: LiteralInteger { value: 1, @@ -471,8 +502,13 @@ fn test_nested_for_with_missing_end_for() { by_step: None, body: [ ForLoopStatement { - counter: Reference { - name: "x", + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, start: LiteralInteger { value: 1, @@ -483,21 +519,41 @@ fn test_nested_for_with_missing_end_for() { by_step: None, body: [ Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], }, Assignment { - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, ], @@ -533,17 +589,32 @@ fn test_repeat_with_missing_semicolon_in_body() { RepeatLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ Assignment { - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, right: LiteralInteger { value: 3, @@ -552,11 +623,21 @@ fn test_repeat_with_missing_semicolon_in_body() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ] @@ -595,11 +676,21 @@ fn test_nested_repeat_with_missing_until_end_repeat() { RepeatLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ @@ -607,11 +698,21 @@ fn test_nested_repeat_with_missing_until_end_repeat() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -654,11 +755,21 @@ fn test_nested_repeat_with_missing_condition_and_end_repeat() { RepeatLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ @@ -666,11 +777,21 @@ fn test_nested_repeat_with_missing_condition_and_end_repeat() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -709,22 +830,42 @@ fn test_nested_repeat_with_missing_end_repeat() { RepeatLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ RepeatLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ @@ -732,11 +873,21 @@ fn test_nested_repeat_with_missing_end_repeat() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -773,17 +924,32 @@ fn test_while_with_missing_semicolon_in_body() { WhileLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ Assignment { - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, right: LiteralInteger { value: 3, @@ -792,11 +958,21 @@ fn test_while_with_missing_semicolon_in_body() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ] @@ -832,22 +1008,42 @@ fn test_nested_while_with_missing_end_while() { WhileLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ WhileLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ @@ -855,11 +1051,21 @@ fn test_nested_while_with_missing_end_while() { ], }, Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -889,20 +1095,40 @@ fn test_while_with_missing_do() { WhileLoopStatement { condition: BinaryExpression { operator: Equal, - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, - right: Reference { - name: "y", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, }, body: [ Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "x", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, }, ], @@ -936,21 +1162,41 @@ fn test_case_body_with_missing_semicolon() { @r###" [ CaseStatement { - selector: Reference { - name: "x", + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, case_blocks: [ ConditionalBlock { - condition: Reference { - name: "y", + condition: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, body: [ Assignment { - left: Reference { - name: "y", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, }, - right: Reference { - name: "z", + right: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, }, }, ], @@ -978,8 +1224,13 @@ fn test_case_without_condition() { format!("{:#?}", cu.implementations[0].statements), r#"[ CaseStatement { - selector: Reference { - name: "x", + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, case_blocks: [ ConditionalBlock { @@ -992,8 +1243,13 @@ fn test_case_without_condition() { condition: EmptyStatement, body: [ Assignment { - left: Reference { - name: "x", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, }, right: LiteralInteger { value: 3, @@ -1083,16 +1339,14 @@ fn pointer_type_with_wrong_keyword_to_test() { #[test] fn bitwise_access_error_validation() { let src = "PROGRAM exp - a.1e5; - b.%f6; + a.1e5; // exponent illegal + b.%f6; // f is no valid direct access modifier END_PROGRAM"; - let (ast, diagnostics) = parse(src); - println!("{ast:?}"); - - assert_eq!(2, diagnostics.len()); + let (_, diagnostics) = parse(src); let errs = vec![ Diagnostic::unexpected_token_found("Integer", r#"Exponent value: 1e5"#, (19..22).into()), - Diagnostic::unexpected_token_found("KeywordSemicolon", "'f6'", (32..34).into()), + Diagnostic::unexpected_token_found("Literal", r#"%"#, (52..53).into()), + Diagnostic::unexpected_token_found("KeywordSemicolon", "'%f6'", (52..55).into()), ]; assert_eq!(errs, diagnostics); } diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_return_type_with_initializer.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_return_type_with_initializer.snap new file mode 100644 index 0000000000..e5fdb9796d --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_return_type_with_initializer.snap @@ -0,0 +1,14 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_with_illegal_return_variable_declaration.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_with_illegal_return_variable_declaration.snap new file mode 100644 index 0000000000..e5fdb9796d --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__function_with_illegal_return_variable_declaration.snap @@ -0,0 +1,14 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__illegal_end_pou_keyword.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__illegal_end_pou_keyword.snap new file mode 100644 index 0000000000..89c9f51b87 --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__illegal_end_pou_keyword.snap @@ -0,0 +1,14 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name.snap new file mode 100644 index 0000000000..e5fdb9796d --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name.snap @@ -0,0 +1,14 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name_2.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name_2.snap new file mode 100644 index 0000000000..1daf4372f2 --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__missing_pou_name_2.snap @@ -0,0 +1,15 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + EmptyStatement, + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__program_with_illegal_return_variable_declaration.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__program_with_illegal_return_variable_declaration.snap new file mode 100644 index 0000000000..e5fdb9796d --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_containers_tests__program_with_illegal_return_variable_declaration.snap @@ -0,0 +1,14 @@ +--- +source: src/parser/tests/parse_errors/parse_error_containers_tests.rs +expression: pou.statements +--- +[ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, +] diff --git a/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_statements_tests__missing_semicolon_after_call.snap b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_statements_tests__missing_semicolon_after_call.snap new file mode 100644 index 0000000000..0cf7e4926f --- /dev/null +++ b/src/parser/tests/parse_errors/snapshots/rusty__parser__tests__parse_errors__parse_error_statements_tests__missing_semicolon_after_call.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/tests/parse_errors/parse_error_statements_tests.rs +expression: pou.statements +--- +[ + CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "buz", + }, + ), + base: None, + }, + parameters: None, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_else_and_no_condition.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_else_and_no_condition.snap new file mode 100644 index 0000000000..bd8994ffea --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_else_and_no_condition.snap @@ -0,0 +1,16 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_conditions.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_conditions.snap new file mode 100644 index 0000000000..8486ee2fae --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_conditions.snap @@ -0,0 +1,78 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: LiteralInteger { + value: 1, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ConditionalBlock { + condition: LiteralInteger { + value: 2, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "yy", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "yyy", + }, + ), + base: None, + }, + ], + }, + ConditionalBlock { + condition: LiteralInteger { + value: 3, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_expressions_per_condition.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_expressions_per_condition.snap new file mode 100644 index 0000000000..5f4fbcc0f5 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_multiple_expressions_per_condition.snap @@ -0,0 +1,69 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: ExpressionList { + expressions: [ + LiteralInteger { + value: 1, + }, + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 3, + }, + ], + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ConditionalBlock { + condition: ExpressionList { + expressions: [ + RangeStatement { + start: LiteralInteger { + value: 4, + }, + end: LiteralInteger { + value: 5, + }, + }, + LiteralInteger { + value: 6, + }, + ], + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_no_conditions.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_no_conditions.snap new file mode 100644 index 0000000000..bd8994ffea --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_no_conditions.snap @@ -0,0 +1,16 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition.snap new file mode 100644 index 0000000000..4212eed180 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition.snap @@ -0,0 +1,32 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: LiteralInteger { + value: 1, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_and_an_else.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_and_an_else.snap new file mode 100644 index 0000000000..c0321089f8 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_and_an_else.snap @@ -0,0 +1,41 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: LiteralInteger { + value: 1, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_with_trailling_comma.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_with_trailling_comma.snap new file mode 100644 index 0000000000..4212eed180 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_condition_with_trailling_comma.snap @@ -0,0 +1,32 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: LiteralInteger { + value: 1, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_empty_condition_and_an_else.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_empty_condition_and_an_else.snap new file mode 100644 index 0000000000..629e5e8fbd --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__case_statement_with_one_empty_condition_and_an_else.snap @@ -0,0 +1,32 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +CaseStatement { + selector: ReferenceExpr { + kind: Member( + Identifier { + name: "StateMachine", + }, + ), + base: None, + }, + case_blocks: [ + ConditionalBlock { + condition: LiteralInteger { + value: 1, + }, + body: [], + }, + ], + else_block: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_body_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_body_statement.snap new file mode 100644 index 0000000000..42da9c8bd9 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_body_statement.snap @@ -0,0 +1,49 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +ForLoopStatement { + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + start: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + end: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + by_step: None, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_literals_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_literals_statement.snap new file mode 100644 index 0000000000..61d49f475c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_literals_statement.snap @@ -0,0 +1,27 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +ForLoopStatement { + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + start: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + end: LiteralInteger { + value: 10, + }, + by_step: None, + body: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_reference_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_reference_statement.snap new file mode 100644 index 0000000000..f2b1490fa2 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_reference_statement.snap @@ -0,0 +1,32 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +ForLoopStatement { + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + start: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + end: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + by_step: None, + body: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_step_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_step_statement.snap new file mode 100644 index 0000000000..cff9a2ca4e --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__for_with_step_statement.snap @@ -0,0 +1,26 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +ForLoopStatement { + counter: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + start: LiteralInteger { + value: 1, + }, + end: LiteralInteger { + value: 10, + }, + by_step: Some( + LiteralInteger { + value: 7, + }, + ), + body: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_else_statement_with_expressions.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_else_statement_with_expressions.snap new file mode 100644 index 0000000000..e7b1e92480 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_else_statement_with_expressions.snap @@ -0,0 +1,33 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +IfStatement { + blocks: [ + ConditionalBlock { + condition: LiteralBool { + value: true, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_elsif_elsif_else_statement_with_expressions.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_elsif_elsif_else_statement_with_expressions.snap new file mode 100644 index 0000000000..9e336bc000 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__if_elsif_elsif_else_statement_with_expressions.snap @@ -0,0 +1,73 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +IfStatement { + blocks: [ + ConditionalBlock { + condition: LiteralBool { + value: true, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ], + }, + ConditionalBlock { + condition: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + ], + }, + ConditionalBlock { + condition: ReferenceExpr { + kind: Member( + Identifier { + name: "w", + }, + ), + base: None, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "v", + }, + ), + base: None, + }, + ], + }, + ], + else_block: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "u", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_body_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_body_statement.snap new file mode 100644 index 0000000000..e34b4f8f3f --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_body_statement.snap @@ -0,0 +1,27 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +RepeatLoopStatement { + condition: LiteralBool { + value: true, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_expression.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_expression.snap new file mode 100644 index 0000000000..6cc52b3508 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__repeat_with_expression.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +RepeatLoopStatement { + condition: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 7, + }, + }, + body: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_body_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_body_statement.snap new file mode 100644 index 0000000000..664d9ebb5c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_body_statement.snap @@ -0,0 +1,27 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +WhileLoopStatement { + condition: LiteralBool { + value: true, + }, + body: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_expression.snap b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_expression.snap new file mode 100644 index 0000000000..4192b590f6 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__control_parser_tests__while_with_expression.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/tests/control_parser_tests.rs +expression: statement +--- +WhileLoopStatement { + condition: BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 7, + }, + }, + body: [], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_ast_test.snap new file mode 100644 index 0000000000..6ea967b491 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_ast_test.snap @@ -0,0 +1,13 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: LiteralInteger { + value: 2, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_compare_or_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_compare_or_priority_test.snap new file mode 100644 index 0000000000..3ba9c7b3ed --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__addition_compare_or_priority_test.snap @@ -0,0 +1,35 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Or, + left: BinaryExpression { + operator: Greater, + left: BinaryExpression { + operator: Plus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + right: LiteralInteger { + value: 2, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b1", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_three_variables_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_three_variables_parsed.snap new file mode 100644 index 0000000000..504e385d05 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_three_variables_parsed.snap @@ -0,0 +1,34 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Minus, + left: BinaryExpression { + operator: Plus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_two_variables_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_two_variables_parsed.snap new file mode 100644 index 0000000000..5c250ad768 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__additon_of_two_variables_parsed.snap @@ -0,0 +1,141 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +[ + BinaryExpression { + operator: Plus, + left: ReferenceExpr { + kind: Member( + Reference { + name: "x", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: None, + }, + }, + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Reference { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, + BinaryExpression { + operator: Minus, + left: ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Reference { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Address, + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Reference { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Reference { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_test.snap new file mode 100644 index 0000000000..eb79003f3d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_test.snap @@ -0,0 +1,23 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_with_address_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_with_address_test.snap new file mode 100644 index 0000000000..dddb76f745 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__amp_as_and_with_address_test.snap @@ -0,0 +1,28 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Address, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + ), + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__and_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__and_test.snap new file mode 100644 index 0000000000..eb79003f3d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__and_test.snap @@ -0,0 +1,23 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_can_be_parsed-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_can_be_parsed-2.snap new file mode 100644 index 0000000000..cc71ff4ff5 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_can_be_parsed-2.snap @@ -0,0 +1,57 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Index( + LiteralInteger { + value: 0, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: LiteralString { + value: "Hello, World!", + is_wide: false, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Index( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: LiteralString { + value: "", + is_wide: false, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_in_structs_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_in_structs_can_be_parsed.snap new file mode 100644 index 0000000000..76e0ad7f58 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_in_structs_can_be_parsed.snap @@ -0,0 +1,30 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ReferenceExpr { + kind: Index( + LiteralInteger { + value: 7, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_of_structs_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_of_structs_can_be_parsed.snap new file mode 100644 index 0000000000..5c5a260bdd --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__arrays_of_structs_can_be_parsed.snap @@ -0,0 +1,30 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_test.snap new file mode 100644 index 0000000000..306a6d855d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_test.snap @@ -0,0 +1,38 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: prg +--- +[ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 3, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: LiteralInteger { + value: 2, + }, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_null.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_null.snap new file mode 100644 index 0000000000..c05ef6259b --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_null.snap @@ -0,0 +1,15 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralNull, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_reference_with_explicit_plus_sign.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_reference_with_explicit_plus_sign.snap new file mode 100644 index 0000000000..65342bde8d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_reference_with_explicit_plus_sign.snap @@ -0,0 +1,40 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: UnaryExpression { + operator: Plus, + value: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_with_implicit_and_explicit_plus_sign.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_with_implicit_and_explicit_plus_sign.snap new file mode 100644 index 0000000000..40fdaf8aa2 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__assignment_to_number_with_implicit_and_explicit_plus_sign.snap @@ -0,0 +1,32 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__binary_stmts_of_two_variables_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__binary_stmts_of_two_variables_parsed.snap new file mode 100644 index 0000000000..7253af4f5a --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__binary_stmts_of_two_variables_parsed.snap @@ -0,0 +1,141 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +[ + BinaryExpression { + operator: Plus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + }, + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, + BinaryExpression { + operator: Minus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Address, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ), + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__bitwise_access_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__bitwise_access_parsed.snap new file mode 100644 index 0000000000..d9367dc265 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__bitwise_access_parsed.snap @@ -0,0 +1,181 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: "&prg.statements" +--- +[ + ReferenceExpr { + kind: Member( + DirectAccess { + access: Bit, + index: LiteralInteger { + value: 0, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: Bit, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: Byte, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: Byte, + index: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: Word, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 0, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: DWord, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ), + }, + ReferenceExpr { + kind: Member( + DirectAccess { + access: Bit, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + DirectAccess { + access: Byte, + index: LiteralInteger { + value: 1, + }, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), + }, + ), + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_ast_test.snap new file mode 100644 index 0000000000..6f5d651689 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_ast_test.snap @@ -0,0 +1,48 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Or, + left: BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: UnaryExpression { + operator: Not, + value: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: Xor, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_param_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_param_ast_test.snap new file mode 100644 index 0000000000..5c3e143df6 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_expression_param_ast_test.snap @@ -0,0 +1,48 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: BinaryExpression { + operator: Xor, + left: UnaryExpression { + operator: Not, + value: BinaryExpression { + operator: Or, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_literals_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_literals_can_be_parsed.snap new file mode 100644 index 0000000000..5a700a5c31 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_literals_can_be_parsed.snap @@ -0,0 +1,13 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Or, + left: LiteralBool { + value: true, + }, + right: LiteralBool { + value: false, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_priority_test.snap new file mode 100644 index 0000000000..17056a05a8 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__boolean_priority_test.snap @@ -0,0 +1,45 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Or, + left: BinaryExpression { + operator: Xor, + left: BinaryExpression { + operator: And, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_expression_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_expression_test.snap new file mode 100644 index 0000000000..06e25bced5 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_expression_test.snap @@ -0,0 +1,93 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: prg +--- +[ + BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 3, + }, + }, + BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 0, + }, + }, + BinaryExpression { + operator: LessOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 7, + }, + }, + BinaryExpression { + operator: GreaterOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 4, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "e", + }, + ), + base: None, + }, + right: BinaryExpression { + operator: Greater, + left: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 2, + }, + right: LiteralInteger { + value: 1, + }, + }, + right: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 3, + }, + right: LiteralInteger { + value: 1, + }, + }, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_priority_test.snap new file mode 100644 index 0000000000..0d95fb8b9b --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__comparison_priority_test.snap @@ -0,0 +1,35 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Equal, + left: BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 7, + }, + }, + right: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 6, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap index bb744dd71a..df64887707 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap @@ -1,5 +1,128 @@ --- source: src/parser/tests/expressions_parser_tests.rs -expression: "format!(\"{result:?}\")" +expression: result --- -CompilationUnit { global_vars: [], units: [POU { name: "prg", variable_blocks: [], pou_type: Program, return_type: None }], implementations: [Implementation { name: "prg", type_name: "prg", linkage: Internal, pou_type: Program, statements: [Assignment { left: Reference { name: "x" }, right: BinaryExpression { operator: Plus, left: LiteralInteger { value: 5 }, right: HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 2 }, LiteralInteger { value: 1 }], location: SourceRange { range: 32..40 } } } }, Assignment { left: Reference { name: "y" }, right: HardwareAccess { direction: Memory, access: Byte, address: [LiteralInteger { value: 200 }], location: SourceRange { range: 52..61 } } }, Assignment { left: Reference { name: "z" }, right: BinaryExpression { operator: Multiplication, left: HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 5 }], location: SourceRange { range: 73..80 } }, right: LiteralInteger { value: 2 } } }], location: SourceRange { range: 25..101 }, name_location: SourceRange { range: 13..16 }, overriding: false, generic: false, access: None }], user_types: [], file_name: "test.st", new_lines: NewLines { line_breaks: [1, 17, 42, 63, 86, 102, 107] } } +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "prg", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "prg", + type_name: "prg", + linkage: Internal, + pou_type: Program, + statements: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 6, + }, + right: HardwareAccess { + direction: Input, + access: Bit, + address: [ + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 1, + }, + ], + location: SourceRange { + range: 32..40, + }, + }, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: HardwareAccess { + direction: Memory, + access: Byte, + address: [ + LiteralInteger { + value: 200, + }, + ], + location: SourceRange { + range: 52..61, + }, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + right: BinaryExpression { + operator: Multiplication, + left: HardwareAccess { + direction: Global, + access: DWord, + address: [ + LiteralInteger { + value: 5, + }, + ], + location: SourceRange { + range: 73..80, + }, + }, + right: LiteralInteger { + value: 2, + }, + }, + }, + ], + location: SourceRange { + range: 25..101, + }, + name_location: SourceRange { + range: 13..16, + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", + new_lines: NewLines { + line_breaks: [ + 1, + 17, + 42, + 63, + 86, + 102, + 107, + ], + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__equality_expression_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__equality_expression_test.snap new file mode 100644 index 0000000000..806527f51a --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__equality_expression_test.snap @@ -0,0 +1,46 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: prg +--- +[ + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 3, + }, + }, + BinaryExpression { + operator: NotEqual, + left: BinaryExpression { + operator: Minus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 0, + }, + }, + right: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: LiteralInteger { + value: 2, + }, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap index 81ad11a97a..fdd1c04b2a 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap @@ -25,21 +25,41 @@ CompilationUnit { statements: [ BinaryExpression { operator: Multiplication, - left: Reference { - name: "a", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, }, right: CallStatement { - operator: Reference { - name: "EXPT", + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "EXPT", + }, + ), + base: None, }, parameters: Some( ExpressionList { expressions: [ - Reference { - name: "b", + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, }, - Reference { - name: "c", + ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, }, ], }, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exponent_expressions_parse.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exponent_expressions_parse.snap index d46e56519d..d4711cb102 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exponent_expressions_parse.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exponent_expressions_parse.snap @@ -3,8 +3,13 @@ source: src/parser/tests/expressions_parser_tests.rs expression: statement --- CallStatement { - operator: Reference { - name: "EXPT", + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "EXPT", + }, + ), + base: None, }, parameters: Some( ExpressionList { diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list.snap new file mode 100644 index 0000000000..e1fcfdcedf --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ExpressionList { + expressions: [ + LiteralInteger { + value: 1, + }, + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 3, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list_assignments.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list_assignments.snap new file mode 100644 index 0000000000..cb04ea67a2 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__expression_list_assignments.snap @@ -0,0 +1,47 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 2, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 3, + }, + }, + ], +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_formal_params.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_formal_params.snap new file mode 100644 index 0000000000..4a0b684fb6 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_formal_params.snap @@ -0,0 +1,64 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "fn", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 2, + }, + }, + OutputAssignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "z", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + }, + ], + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_no_params.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_no_params.snap new file mode 100644 index 0000000000..f20ccf320d --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_no_params.snap @@ -0,0 +1,15 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "fn", + }, + ), + base: None, + }, + parameters: None, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params.snap new file mode 100644 index 0000000000..288397a966 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params.snap @@ -0,0 +1,29 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "fn", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: 1, + }, + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 3, + }, + ], + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params_with_trailling_comma.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params_with_trailling_comma.snap new file mode 100644 index 0000000000..288397a966 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_params_with_trailling_comma.snap @@ -0,0 +1,29 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "fn", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: 1, + }, + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 3, + }, + ], + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_return_params.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_return_params.snap new file mode 100644 index 0000000000..752728700e --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__function_call_return_params.snap @@ -0,0 +1,39 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "fn", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: 1, + }, + LiteralInteger { + value: 2, + }, + LiteralInteger { + value: 3, + }, + ], + }, + ), + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_binary_with_underscore_number_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_binary_with_underscore_number_can_be_parsed.snap new file mode 100644 index 0000000000..a7147aa2b6 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_binary_with_underscore_number_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 45, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_can_be_parsed.snap new file mode 100644 index 0000000000..46ddb2f551 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: "&prg.statements[0]" +--- +LiteralInteger { + value: 7, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_dec_number_with_underscores_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_dec_number_with_underscores_can_be_parsed.snap new file mode 100644 index 0000000000..5de8c04eda --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_dec_number_with_underscores_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 43000, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_enum_parse_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_enum_parse_test.snap new file mode 100644 index 0000000000..e656f1998c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_enum_parse_test.snap @@ -0,0 +1,57 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +[ + ReferenceExpr { + kind: Cast( + Identifier { + name: "Val7", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "MyEnum", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Cast( + Identifier { + name: "Val2", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "MyEnum", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Cast( + Identifier { + name: "Val3", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "MyEnum", + }, + ), + base: None, + }, + ), + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_can_be_parsed.snap new file mode 100644 index 0000000000..9e2265336a --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 3735928559, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_with_underscores_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_with_underscores_can_be_parsed.snap new file mode 100644 index 0000000000..9e2265336a --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_hex_number_with_underscores_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 3735928559, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscore_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscore_can_be_parsed.snap new file mode 100644 index 0000000000..a7d49680d1 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscore_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 63, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscores_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscores_can_be_parsed.snap new file mode 100644 index 0000000000..a7d49680d1 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__literal_oct_number_with_underscores_can_be_parsed.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: 63, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__module_expression_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__module_expression_test.snap new file mode 100644 index 0000000000..001af8cbae --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__module_expression_test.snap @@ -0,0 +1,13 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Modulo, + left: LiteralInteger { + value: 5, + }, + right: LiteralInteger { + value: 2, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multidim_arrays_can_be_parsed-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multidim_arrays_can_be_parsed-2.snap new file mode 100644 index 0000000000..d8f67a0a26 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multidim_arrays_can_be_parsed-2.snap @@ -0,0 +1,71 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Index( + ExpressionList { + expressions: [ + LiteralInteger { + value: 0, + }, + LiteralInteger { + value: 1, + }, + ], + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: LiteralString { + value: "Hello, World!", + is_wide: false, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Index( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + LiteralInteger { + value: 1, + }, + ], + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + right: LiteralString { + value: "", + is_wide: false, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_ast_test.snap new file mode 100644 index 0000000000..a10ab9cb90 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_ast_test.snap @@ -0,0 +1,19 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: BinaryExpression { + operator: Multiplication, + left: LiteralInteger { + value: 2, + }, + right: LiteralInteger { + value: 3, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_expressions_parse.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_expressions_parse.snap new file mode 100644 index 0000000000..44cb6027c3 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__multiplication_expressions_parse.snap @@ -0,0 +1,19 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Division, + left: BinaryExpression { + operator: Multiplication, + left: LiteralInteger { + value: 1, + }, + right: LiteralInteger { + value: 2, + }, + }, + right: LiteralInteger { + value: 7, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__nested_arrays_can_be_parsed-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__nested_arrays_can_be_parsed-2.snap new file mode 100644 index 0000000000..e349f6a5db --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__nested_arrays_can_be_parsed-2.snap @@ -0,0 +1,75 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 0, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), + }, + right: LiteralString { + value: "Hello, World!", + is_wide: false, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Index( + ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), + }, + right: LiteralString { + value: "", + is_wide: false, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__or_compare_expressions_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__or_compare_expressions_priority_test.snap new file mode 100644 index 0000000000..28b5f5b7bf --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__or_compare_expressions_priority_test.snap @@ -0,0 +1,29 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Or, + left: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b1", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesis_expressions_should_not_change_the_ast.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesis_expressions_should_not_change_the_ast.snap new file mode 100644 index 0000000000..91d34bdc76 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesis_expressions_should_not_change_the_ast.snap @@ -0,0 +1,23 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesized_term_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesized_term_ast_test.snap new file mode 100644 index 0000000000..89cbd255c0 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__parenthesized_term_ast_test.snap @@ -0,0 +1,25 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Multiplication, + left: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: LiteralInteger { + value: 2, + }, + }, + right: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 3, + }, + right: LiteralInteger { + value: 4, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_address_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_address_test.snap new file mode 100644 index 0000000000..af1020de55 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_address_test.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ReferenceExpr { + kind: Address, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test.snap new file mode 100644 index 0000000000..84e505366c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test_nested.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test_nested.snap new file mode 100644 index 0000000000..3a02905fe4 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__pointer_dereference_test_nested.snap @@ -0,0 +1,64 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 2, + }, + ), + base: Some( + ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 0, + }, + ), + base: Some( + ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + ), + }, + ), + }, + ), + }, + ), + }, + ), + }, + ), + }, + ), + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__qualified_reference_statement_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__qualified_reference_statement_parsed.snap new file mode 100644 index 0000000000..8f5037cb44 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__qualified_reference_statement_parsed.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: "&prg.statements[0]" +--- +ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__range_expression.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__range_expression.snap new file mode 100644 index 0000000000..3c2a44a819 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__range_expression.snap @@ -0,0 +1,66 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + RangeStatement { + start: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + end: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + RangeStatement { + start: LiteralInteger { + value: 1, + }, + end: LiteralInteger { + value: 2, + }, + }, + RangeStatement { + start: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + end: LiteralInteger { + value: 2, + }, + }, + RangeStatement { + start: LiteralInteger { + value: 2, + }, + end: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + }, + RangeStatement { + start: LiteralInteger { + value: -2, + }, + end: LiteralInteger { + value: -1, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_reversed_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_reversed_test.snap new file mode 100644 index 0000000000..0d9c2bd7d3 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_reversed_test.snap @@ -0,0 +1,13 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: -4, + }, + right: LiteralInteger { + value: 5, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_test.snap new file mode 100644 index 0000000000..eb173b9c06 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_expression_test.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 2, + }, + right: UnaryExpression { + operator: Minus, + value: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_minus_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_minus_test.snap new file mode 100644 index 0000000000..2be52e8a7c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__signed_literal_minus_test.snap @@ -0,0 +1,7 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +LiteralInteger { + value: -1, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__single_statement_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__single_statement_parsed.snap new file mode 100644 index 0000000000..7dedbdf1e2 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__single_statement_parsed.snap @@ -0,0 +1,12 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: "&prg.statements[0]" +--- +ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__string_can_be_parsed-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__string_can_be_parsed-2.snap new file mode 100644 index 0000000000..dff47de6f7 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__string_can_be_parsed-2.snap @@ -0,0 +1,34 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralString { + value: "Hello, World!", + is_wide: false, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralString { + value: "", + is_wide: false, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__term_ast_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__term_ast_test.snap new file mode 100644 index 0000000000..b6d21c180c --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__term_ast_test.snap @@ -0,0 +1,25 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statement +--- +BinaryExpression { + operator: Plus, + left: BinaryExpression { + operator: Plus, + left: LiteralInteger { + value: 1, + }, + right: BinaryExpression { + operator: Multiplication, + left: LiteralInteger { + value: 2, + }, + right: LiteralInteger { + value: 3, + }, + }, + }, + right: LiteralInteger { + value: 4, + }, +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__wide_string_can_be_parsed-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__wide_string_can_be_parsed-2.snap new file mode 100644 index 0000000000..efd5decfca --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__wide_string_can_be_parsed-2.snap @@ -0,0 +1,34 @@ +--- +source: src/parser/tests/expressions_parser_tests.rs +expression: statements +--- +[ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralString { + value: "Hello, World!", + is_wide: true, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralString { + value: "", + is_wide: true, + }, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__initializer_parser_tests__struct_initializer_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__initializer_parser_tests__struct_initializer_can_be_parsed.snap new file mode 100644 index 0000000000..dd6606161b --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__initializer_parser_tests__struct_initializer_can_be_parsed.snap @@ -0,0 +1,42 @@ +--- +source: src/parser/tests/initializer_parser_tests.rs +expression: x +--- +Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "Point", + }, + initializer: Some( + ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 2, + }, + }, + ], + }, + ), +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap index 307751f802..ee1ba6f387 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap @@ -106,27 +106,47 @@ CompilationUnit { pou_type: Program, statements: [ CallStatement { - operator: Reference { - name: "foo", + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, }, parameters: Some( ExpressionList { expressions: [ Assignment { - left: Reference { - name: "input1", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "input1", + }, + ), + base: None, }, right: EmptyStatement, }, OutputAssignment { - left: Reference { - name: "output1", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "output1", + }, + ), + base: None, }, right: EmptyStatement, }, OutputAssignment { - left: Reference { - name: "inout1", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "inout1", + }, + ), + base: None, }, right: EmptyStatement, }, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__multilevel_inline_struct_and_enum_declaration_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__multilevel_inline_struct_and_enum_declaration_can_be_parsed.snap index 812ae9172d..4ccbd09fa0 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__multilevel_inline_struct_and_enum_declaration_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__multilevel_inline_struct_and_enum_declaration_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/statement_parser_tests.rs expression: ast_string - --- Variable { name: "my_struct", @@ -17,14 +16,29 @@ Variable { numeric_type: "DINT", elements: ExpressionList { expressions: [ - Reference { - name: "red", + ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, - Reference { - name: "yellow", + ReferenceExpr { + kind: Member( + Identifier { + name: "yellow", + }, + ), + base: None, }, - Reference { - name: "green", + ReferenceExpr { + kind: Member( + Identifier { + name: "green", + }, + ), + base: None, }, ], }, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_type_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_type_can_be_parsed.snap index 9597369c0e..88dbc4cfc2 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_type_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_type_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/type_parser_tests.rs -expression: "result.types[0]" - +expression: "result.user_types[0]" --- UserTypeDeclaration { data_type: EnumType { @@ -11,14 +10,29 @@ UserTypeDeclaration { numeric_type: "DINT", elements: ExpressionList { expressions: [ - Reference { - name: "red", + ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, - Reference { - name: "yellow", + ReferenceExpr { + kind: Member( + Identifier { + name: "yellow", + }, + ), + base: None, }, - Reference { - name: "green", + ReferenceExpr { + kind: Member( + Identifier { + name: "green", + }, + ), + base: None, }, ], }, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_numeric_type_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_numeric_type_can_be_parsed.snap index ab6924ed46..bb8847f65e 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_numeric_type_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_numeric_type_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/type_parser_tests.rs -expression: "result.types[0]" - +expression: "result.user_types[0]" --- UserTypeDeclaration { data_type: EnumType { @@ -11,14 +10,29 @@ UserTypeDeclaration { numeric_type: "INT", elements: ExpressionList { expressions: [ - Reference { - name: "red", + ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, - Reference { - name: "yellow", + ReferenceExpr { + kind: Member( + Identifier { + name: "yellow", + }, + ), + base: None, }, - Reference { - name: "green", + ReferenceExpr { + kind: Member( + Identifier { + name: "green", + }, + ), + base: None, }, ], }, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_one_element_numeric_type_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_one_element_numeric_type_can_be_parsed.snap index f832d9c1d5..f85ac7556d 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_one_element_numeric_type_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__simple_enum_with_one_element_numeric_type_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/type_parser_tests.rs -expression: "result.types[0]" - +expression: "result.user_types[0]" --- UserTypeDeclaration { data_type: EnumType { @@ -9,8 +8,13 @@ UserTypeDeclaration { "SampleEnum", ), numeric_type: "INT", - elements: Reference { - name: "red", + elements: ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, }, initializer: None, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_enum_with_initial_values_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_enum_with_initial_values_can_be_parsed.snap index 56fc5b54ee..8819e6c2aa 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_enum_with_initial_values_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_enum_with_initial_values_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/type_parser_tests.rs -expression: "result.types[0]" - +expression: "result.user_types[0]" --- UserTypeDeclaration { data_type: EnumType { @@ -12,24 +11,39 @@ UserTypeDeclaration { elements: ExpressionList { expressions: [ Assignment { - left: Reference { - name: "red", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, right: LiteralInteger { value: 1, }, }, Assignment { - left: Reference { - name: "yellow", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "yellow", + }, + ), + base: None, }, right: LiteralInteger { value: 2, }, }, Assignment { - left: Reference { - name: "green", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "green", + }, + ), + base: None, }, right: LiteralInteger { value: 4, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap index d9041ff2b1..81b1f76169 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap @@ -1,7 +1,6 @@ --- source: src/parser/tests/type_parser_tests.rs expression: "result.units[0]" - --- POU { name: "prg", @@ -17,24 +16,39 @@ POU { elements: ExpressionList { expressions: [ Assignment { - left: Reference { - name: "red", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "red", + }, + ), + base: None, }, right: LiteralInteger { value: 1, }, }, Assignment { - left: Reference { - name: "yellow", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "yellow", + }, + ), + base: None, }, right: LiteralInteger { value: 2, }, }, Assignment { - left: Reference { - name: "green", + left: ReferenceExpr { + kind: Member( + Identifier { + name: "green", + }, + ), + base: None, }, right: LiteralInteger { value: 4, diff --git a/src/parser/tests/statement_parser_tests.rs b/src/parser/tests/statement_parser_tests.rs index 142b7ae473..843f741a1f 100644 --- a/src/parser/tests/statement_parser_tests.rs +++ b/src/parser/tests/statement_parser_tests.rs @@ -1,6 +1,6 @@ use crate::{parser::tests::ref_to, test_utils::tests::parse, typesystem::DINT_TYPE}; use insta::assert_snapshot; -use plc_ast::ast::{AstStatement, DataType, DataTypeDeclaration, SourceRange, Variable}; +use plc_ast::ast::{AstFactory, AstStatement, DataType, DataTypeDeclaration, SourceRange, Variable}; use pretty_assertions::*; #[test] @@ -39,7 +39,11 @@ fn empty_statements_are_parsed_before_a_statement() { AstStatement::EmptyStatement { location: SourceRange::undefined(), id: 0 }, AstStatement::EmptyStatement { location: SourceRange::undefined(), id: 0 }, AstStatement::EmptyStatement { location: SourceRange::undefined(), id: 0 }, - AstStatement::Reference { name: "x".into(), location: SourceRange::undefined(), id: 0 }, + AstFactory::create_member_reference( + AstFactory::create_identifier("x".into(), &SourceRange::undefined(), 0), + None, + 0 + ), ] ), ); @@ -54,9 +58,14 @@ fn empty_statements_are_ignored_after_a_statement() { let statement = &prg.statements[0]; let ast_string = format!("{statement:#?}"); - let expected_ast = r#"Reference { - name: "x", -}"#; + let expected_ast = format!( + "{:#?}", + AstFactory::create_member_reference( + AstFactory::create_identifier("x", &SourceRange::undefined(), 0), + None, + 0 + ) + ); assert_eq!(ast_string, expected_ast); } diff --git a/src/resolver.rs b/src/resolver.rs index 40fc1d1617..7d672ef905 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -14,13 +14,14 @@ use indexmap::{IndexMap, IndexSet}; use plc_ast::{ ast::{ self, flatten_expression_list, AstFactory, AstId, AstStatement, CompilationUnit, DataType, - DataTypeDeclaration, DirectAccessType, Operator, Pou, TypeNature, UserTypeDeclaration, Variable, + DataTypeDeclaration, DirectAccessType, Operator, Pou, ReferenceAccess, TypeNature, + UserTypeDeclaration, Variable, }, control_statements::AstControlStatement, literals::{Array, AstLiteral, StringValue}, provider::IdProvider, }; -use plc_util::convention::{internal_type_name, qualified_name}; +use plc_util::convention::internal_type_name; pub mod const_evaluator; pub mod generics; @@ -66,8 +67,6 @@ pub struct VisitorContext<'s> { /// Inside the left hand side of an assignment is in the context of the call's POU /// `foo(a := a)` actually means: `foo(foo.a := POU.a)` lhs: Option<&'s str>, - /// true if visiting a call statement - is_call: bool, /// true if the expression passed a constant-variable on the way /// e.g. true for `x` if x is declared in a constant block @@ -78,6 +77,9 @@ pub struct VisitorContext<'s> { in_body: bool, id_provider: IdProvider, + + // what's the current strategy for resolving + resolve_strategy: Vec, } impl<'s> VisitorContext<'s> { @@ -87,10 +89,10 @@ impl<'s> VisitorContext<'s> { pou: self.pou, qualifier: Some(qualifier), lhs: self.lhs, - is_call: self.is_call, constant: false, in_body: self.in_body, id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), } } @@ -100,10 +102,10 @@ impl<'s> VisitorContext<'s> { pou: Some(pou), qualifier: self.qualifier.clone(), lhs: self.lhs, - is_call: self.is_call, constant: false, in_body: self.in_body, id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), } } @@ -113,23 +115,23 @@ impl<'s> VisitorContext<'s> { pou: self.pou, qualifier: self.qualifier.clone(), lhs: Some(lhs_pou), - is_call: self.is_call, constant: false, in_body: self.in_body, id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), } } /// returns a copy of the current context and changes the `is_call` to true - fn set_is_call(&self) -> VisitorContext<'s> { + fn with_const(&self, const_state: bool) -> VisitorContext<'s> { VisitorContext { pou: self.pou, qualifier: self.qualifier.clone(), lhs: self.lhs, - is_call: true, - constant: self.constant, + constant: const_state, in_body: self.in_body, id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), } } @@ -139,10 +141,23 @@ impl<'s> VisitorContext<'s> { pou: self.pou, qualifier: self.qualifier.clone(), lhs: self.lhs, - is_call: self.is_call, constant: self.constant, in_body: true, id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), + } + } + + // returns a copy of the current context and sets the resolve_strategy field to the given strategies + fn with_resolving_strategy(&self, resolve_strategy: Vec) -> Self { + VisitorContext { + pou: self.pou, + qualifier: self.qualifier.clone(), + lhs: self.lhs, + constant: self.constant, + in_body: true, + id_provider: self.id_provider.clone(), + resolve_strategy, } } @@ -314,6 +329,17 @@ impl StatementAnnotation { pub fn new_value(type_name: String) -> Self { StatementAnnotation::Value { resulting_type: type_name } } + + pub fn is_const(&self) -> bool { + match self { + StatementAnnotation::Variable { constant, .. } => *constant, + _ => false, + } + } + + pub fn data_type(type_name: &str) -> Self { + StatementAnnotation::Type { type_name: type_name.into() } + } } impl From<&PouIndexEntry> for StatementAnnotation { @@ -400,9 +426,9 @@ pub trait AnnotationMap { .get_hint(statement) .or_else(|| self.get(statement)) .and_then(|it| self.get_type_name_for_annotation(it)), - StatementAnnotation::Function { .. } - | StatementAnnotation::Type { .. } - | StatementAnnotation::Program { .. } => None, + StatementAnnotation::Program { qualified_name } => Some(qualified_name.as_str()), + StatementAnnotation::Function { .. } => None, + StatementAnnotation::Type { type_name } => Some(type_name), } } @@ -613,10 +639,10 @@ impl<'i> TypeAnnotator<'i> { pou: None, qualifier: None, lhs: None, - is_call: false, constant: false, in_body: false, id_provider, + resolve_strategy: ResolvingScope::default_scopes(), }; for global_variable in unit.global_vars.iter().flat_map(|it| it.variables.iter()) { @@ -648,6 +674,10 @@ impl<'i> TypeAnnotator<'i> { if let Some((Some(statement), scope)) = enum_element.initial_value.map(|i| index.get_const_expressions().find_expression(&i)) { + if visitor.annotation_map.get(statement).is_none() { + panic!("new expression we did not visit yet") + } + if let Some(scope) = scope { visitor.visit_statement(&ctx.with_pou(scope), statement); } else { @@ -706,7 +736,7 @@ impl<'i> TypeAnnotator<'i> { .find_range_check_implementation_for(expected_type.get_type_information()) .map(|f| { AstFactory::create_call_to_check_function_ast( - f.get_call_name().to_string(), + f.get_call_name(), right_side.clone(), sub_range.clone(), &annotated_left_side.get_location(), @@ -766,8 +796,8 @@ impl<'i> TypeAnnotator<'i> { // find out left's type and update a type hint for right if let ( typesystem::DataTypeInformation::Struct { name: qualifier, .. }, - AstStatement::Reference { name: variable_name, .. }, - ) = (expected_type.get_type_information(), left.as_ref()) + Some(variable_name), + ) = (expected_type.get_type_information(), left.as_ref().get_flat_reference_name()) { if let Some(v) = self.index.find_member(qualifier, variable_name) { if let Some(target_type) = self.index.find_effective_type_by_name(v.get_type_name()) { @@ -870,6 +900,7 @@ impl<'i> TypeAnnotator<'i> { } } + // FIXME: can this be done witin the real visiting? fn annotate_array_of_struct( &mut self, expected_type: &typesystem::DataType, @@ -879,7 +910,9 @@ impl<'i> TypeAnnotator<'i> { match expected_type.get_type_information() { DataTypeInformation::Array { inner_type_name, .. } => { let inner_type = self.index.get_effective_type_or_void_by_name(inner_type_name); - let ctx = ctx.with_qualifier(inner_type.get_name().to_string()); + // TODO this seems wrong + let ctx = + ctx.with_qualifier(inner_type.get_name().to_string()).with_lhs(inner_type.get_name()); if inner_type.get_type_information().is_struct() { let expressions = match initializer { @@ -996,8 +1029,9 @@ impl<'i> TypeAnnotator<'i> { self.update_expected_types(expected_type, bounds); } } - DataType::EnumType { elements, .. } => { - self.visit_statement(ctx, elements); + DataType::EnumType { elements, name, .. } => { + let ctx = name.as_ref().map(|n| ctx.with_lhs(n)).unwrap_or(ctx.clone()); + self.visit_statement(&ctx, elements); } DataType::PointerType { referenced_type, .. } => { self.visit_data_type_declaration(ctx, referenced_type.as_ref()) @@ -1067,57 +1101,6 @@ impl<'i> TypeAnnotator<'i> { /// annotate an expression statement fn visit_statement_expression(&mut self, ctx: &VisitorContext, statement: &AstStatement) { match statement { - AstStatement::ArrayAccess { reference, access, .. } => { - visit_all_statements!(self, ctx, reference); - - self.visit_statement( - &VisitorContext { - lhs: None, - is_call: false, - constant: false, - pou: ctx.pou, - qualifier: None, - in_body: ctx.in_body, - id_provider: ctx.id_provider.clone(), - }, - access, - ); - let array_type = - self.annotation_map.get_type_or_void(reference, self.index).get_type_information(); - - let inner_type_name = match array_type { - DataTypeInformation::Array { inner_type_name, .. } - | DataTypeInformation::Struct { - source: - StructSource::Internal(InternalType::VariableLengthArray { inner_type_name, .. }), - .. - } => Some( - self.index.get_effective_type_or_void_by_name(inner_type_name).get_name().to_string(), - ), - _ => None, - }; - - if let Some(inner_type_name) = inner_type_name { - self.annotate(statement, StatementAnnotation::new_value(inner_type_name)); - } - } - AstStatement::PointerAccess { reference, .. } => { - visit_all_statements!(self, ctx, reference); - let pointer_type = - self.annotation_map.get_type_or_void(reference, self.index).get_type_information(); - if let DataTypeInformation::Pointer { inner_type_name, .. } = pointer_type { - let t = self - .index - .get_effective_type_by_name(inner_type_name) - .unwrap_or_else(|_| { - self.annotation_map.new_index.get_effective_type_or_void_by_name(inner_type_name) - }) - .get_name(); - // borrow-checker won't allow using t in annotate() without claiming ownership first due to immutable borrow - let t = t.to_owned(); - self.annotate(statement, StatementAnnotation::value(t.as_str())); - } - } AstStatement::DirectAccess { access, index, .. } => { let ctx = VisitorContext { qualifier: None, ..ctx.clone() }; visit_all_statements!(self, &ctx, index); @@ -1239,124 +1222,14 @@ impl<'i> TypeAnnotator<'i> { .get_name() .to_string(); - if operator == &Operator::Address { - //this becomes a pointer to the given type: - Some(add_pointer_type(&mut self.annotation_map.new_index, inner_type)) - } else { - Some(inner_type) - } + Some(inner_type) }; if let Some(statement_type) = statement_type { self.annotate(statement, StatementAnnotation::new_value(statement_type)); } } - AstStatement::Reference { name, .. } => { - let annotation = if let Some(qualifier) = ctx.qualifier.as_deref() { - // if we see a qualifier, we only consider [qualifier].[name] as candidates - self.index - // 1st try a qualified member variable qualifier.name - .find_member(qualifier, name) - // 2nd try an enum-element qualifier#name - .or_else(|| self.index.find_enum_element(qualifier, name.as_str())) - // 3rd try - look for a method qualifier.name - .map_or_else( - || self.index.find_method(qualifier, name).map(|it| it.into()), - |v| Some(to_variable_annotation(v, self.index, ctx.constant)), - ) - } else { - // if we see no qualifier, we try some strategies... - ctx.pou - .and_then(|qualifier| { - // ...first look at POU-local variables - self.index - .find_member(qualifier, name) - .and_then(|m| { - // If we're dealing with a call statement, check if... - if ctx.is_call { - // ...the POU name is the same as the member, which indicates a - // recursive function call (e.g. `FUNCTION foo : INT foo(); END_FUNCTION`) - if m.get_name() == qualifier { - return None; // We need the POU instead - } - - // ...there exists a **function** with the same name as the member, - // which indicates that we would incorrectly annotate a call statement as a variable. - // Note: We explicitily check for functions because e.g. FBs can be callable variables - if self.index.find_pou(name).is_some_and(|it| it.is_function()) { - return None; // We need the POU instead - } - } - - Some(to_variable_annotation(m, self.index, ctx.constant)) - }) - //TODO find parent of super class to start the search - // ...then check if we're in a method and we're referencing - // a member variable of the corresponding class - .or(self - .index - .find_pou(qualifier) - .filter(|it| matches!(it, PouIndexEntry::Method { .. })) - .and_then(PouIndexEntry::get_instance_struct_type_name) - .and_then(|class_name| self.index.find_member(class_name, name)) - .map(|v| to_variable_annotation(v, self.index, ctx.constant))) - // try to find a local action with this name - .or(self - .index - .find_pou(&qualified_name(qualifier, name)) - .map(StatementAnnotation::from)) - }) - // ...then try if we find a scoped-pou with that name (maybe it's a call to a local method or action?) - .or(ctx.pou.and_then(|pou_name| self.index.find_pou(pou_name)).and_then(|it| { - self.index.find_pou(&qualified_name(it.get_container(), name)).map(Into::into) - })) - // ...then try if we find a global-pou with that name (maybe it's a call to a function or program?) - .or(self.index.find_pou(name).map(|it| it.into())) - // ...last option is a global variable, where we ignore the current pou's name as a qualifier - .or(self - .index - .find_global_variable(name) - .map(|v| to_variable_annotation(v, self.index, ctx.constant))) - }; - - if let Some(annotation) = annotation { - self.annotate(statement, annotation); - self.maybe_annotate_vla(ctx, statement); - } - } - AstStatement::QualifiedReference { elements, .. } => { - let mut ctx = ctx.clone(); - for s in elements.iter() { - self.visit_statement(&ctx, s); - - let (qualifier, constant) = self - .annotation_map - .get(s) - .map(|it| match it { - StatementAnnotation::Value { resulting_type } => (resulting_type.as_str(), false), - StatementAnnotation::Variable { resulting_type, constant, .. } => { - (resulting_type.as_str(), *constant) - } - StatementAnnotation::Function { .. } - | StatementAnnotation::ReplacementAst { .. } => (VOID_TYPE, false), - StatementAnnotation::Type { type_name } => (type_name.as_str(), false), - StatementAnnotation::Program { qualified_name } => { - (qualified_name.as_str(), false) - } - }) - .unwrap_or_else(|| (VOID_TYPE, false)); - let mut new_ctx = ctx.with_qualifier(qualifier.to_string()); - new_ctx.constant = constant; - ctx = new_ctx; - } - //the last guy represents the type of the whole qualified expression - if let Some(last) = elements.last() { - if let Some(annotation) = self.annotation_map.get(last).cloned() { - self.annotate(statement, annotation); - } - } - } AstStatement::ExpressionList { expressions, .. } => { expressions.iter().for_each(|e| self.visit_statement(ctx, e)) } @@ -1368,7 +1241,7 @@ impl<'i> TypeAnnotator<'i> { self.visit_statement(ctx, right); if let Some(lhs) = ctx.lhs { //special context for left hand side - self.visit_statement(&ctx.with_pou(lhs), left); + self.visit_statement(&ctx.with_pou(lhs).with_lhs(lhs), left); } else { self.visit_statement(ctx, left); } @@ -1439,41 +1312,193 @@ impl<'i> TypeAnnotator<'i> { self.annotate(stmt, StatementAnnotation::new_value(annotation)); } } + AstStatement::ReferenceExpr { access, base, .. } => { + self.visit_reference_expr(access, base.as_deref(), statement, ctx); + } _ => { self.visit_statement_literals(ctx, statement); } } } - /// Checks if the given reference statement is a VLA struct and if so annotates it with a type hint + fn visit_reference_expr( + &mut self, + access: &ast::ReferenceAccess, + base: Option<&AstStatement>, + stmt: &AstStatement, + ctx: &VisitorContext, + ) { + // first resolve base + if let Some(base) = base { + self.visit_statement(ctx, base); + }; + + match ( + access, + base.and_then(|it| { + self.annotation_map.get_type(it, self.index).map(|it| it.get_name().to_string()) + }), + ) { + (ReferenceAccess::Member(reference), qualifier) => { + // uppdate the context's const field + let new_ctx = base.map(|base| ctx.with_const(self.is_const_reference(base, ctx))); + let new_ctx = new_ctx.as_ref().unwrap_or(ctx); + + if let Some(annotation) = + self.resolve_reference_expression(reference.as_ref(), qualifier.as_deref(), new_ctx) + { + self.annotate(stmt, annotation.clone()); + self.annotate(reference, annotation); + // if this was a vla, we update a typehint + if self.annotation_map.get_type(stmt, self.index).filter(|it| it.is_vla()).is_some() { + self.annotate_vla_hint(new_ctx, stmt); + } + } + } + (ReferenceAccess::Cast(target), Some(qualifier)) => { + // STRING#"abc" + // base + if let Some(base_type) = base.and_then(|base| self.annotation_map.get_type(base, self.index)) + { + // if base is an enum, we need to look for members of this specific enum + let optional_enum_qualifier = Some(qualifier.as_str()).filter(|_| base_type.is_enum()); + if ctx.is_in_a_body() { + accept_cast_string_literal(&mut self.string_literals, base_type, target); + } + + if let Some(annotation) = self.resolve_reference_expression( + target, + optional_enum_qualifier, + &ctx.with_resolving_strategy(vec![ResolvingScope::Variable]), + ) { + self.annotate(target.as_ref(), annotation); + self.annotate(stmt, StatementAnnotation::value(qualifier.as_str())); + + if let AstStatement::Literal { .. } = target.as_ref() { + // treate casted literals as the casted type + self.annotate(target.as_ref(), StatementAnnotation::value(qualifier.as_str())); + } + } + } + } + (ReferenceAccess::Index(index), Some(base)) => { + self.visit_statement(ctx, index); + if let Some(inner_type) = self + .index + .find_effective_type_info(base.as_str()) + .and_then(|t| t.get_inner_array_type_name()) + .and_then(|it| self.index.find_effective_type_by_name(it).map(|it| it.get_name())) + { + self.annotate(stmt, StatementAnnotation::value(inner_type)) + } + } + (ReferenceAccess::Deref, _) => { + if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: false, .. }) = base + .map(|base| self.annotation_map.get_type_or_void(base, self.index)) + .map(|it| it.get_type_information()) + { + if let Some(inner_type) = self + .index + .find_effective_type_by_name(inner_type_name) + .or(self.annotation_map.new_index.find_effective_type_by_name(inner_type_name)) + { + self.annotate(stmt, StatementAnnotation::value(inner_type.get_name())) + } + } + } + (ReferenceAccess::Address, _) => { + if let Some(inner_type) = base + .map(|base| self.annotation_map.get_type_or_void(base, self.index).get_name().to_string()) + { + let ptr_type = add_pointer_type(&mut self.annotation_map.new_index, inner_type); + self.annotate(stmt, StatementAnnotation::new_value(ptr_type)) + } + } + _ => {} + } + } + + fn is_const_reference(&self, stmt: &AstStatement, ctx: &VisitorContext<'_>) -> bool { + self.annotation_map + .get(stmt) + .map(|it| it.is_const()) + .filter(|it| *it != ctx.constant) + .unwrap_or(false) + } + + /// resolves the given reference, under the optional qualifier and returns the resulting + /// Statement annotation if one can be derived. This method the annotation! + fn resolve_reference_expression( + &mut self, + reference: &AstStatement, + qualifier: Option<&str>, + ctx: &VisitorContext<'_>, + ) -> Option { + match reference { + AstStatement::Identifier { name, .. } => ctx + .resolve_strategy + .iter() + .find_map(|scope| scope.resolve_name(name, qualifier, self.index, ctx)), + + AstStatement::Literal { .. } => { + self.visit_statement_literals(ctx, reference); + let literal_annotation = self.annotation_map.get(reference).cloned(); // return what we just annotated //TODO not elegant, we need to clone + if let Some((base_type, literal_type)) = + qualifier.and_then(|base| self.index.find_type(base)).zip( + literal_annotation + .as_ref() + .and_then(|a| self.annotation_map.get_type_for_annotation(self.index, a)), + ) + { + // see if this was casted + if base_type != literal_type { + return Some(StatementAnnotation::value(base_type.get_name())); + } + } + literal_annotation + } + + AstStatement::DirectAccess { access, index, .. } if qualifier.is_some() => { + // x.%X1 - bit access + self.visit_statement(ctx, index.as_ref()); + Some(StatementAnnotation::value(get_direct_access_type(access))) + } + _ => None, + } + } + + /// annotates the vla-statement it with a type hint /// referencing the contained array. This is needed to simplify codegen and validation. - fn maybe_annotate_vla(&mut self, ctx: &VisitorContext, statement: &AstStatement) { - if let DataTypeInformation::Struct { + fn annotate_vla_hint(&mut self, ctx: &VisitorContext, statement: &AstStatement) { + let DataTypeInformation::Struct { source: StructSource::Internal(InternalType::VariableLengthArray { .. }), members, .. - } = self.annotation_map.get_type_or_void(statement, self.index).get_type_information() + } = self.annotation_map.get_type_or_void(statement, self.index).get_type_information() else { + unreachable!("expected a vla reference, but got {statement:#?}"); + }; + if let DataTypeInformation::Pointer { inner_type_name, .. } = &self + .index + .get_effective_type_or_void_by_name( + members.get(0).expect("internal VLA struct ALWAYS has this member").get_type_name(), + ) + .get_type_information() { - if let DataTypeInformation::Pointer { inner_type_name, .. } = &self - .index - .get_effective_type_or_void_by_name( - members.get(0).expect("internal VLA struct ALWAYS has this member").get_type_name(), - ) - .get_type_information() - { - let Some(qualified_name) = self.annotation_map.get_qualified_name(statement) else { + let Some(qualified_name) = self.annotation_map.get_qualified_name(statement) else { unreachable!("VLAs are defined within POUs, such that the qualified name *must* exist") }; - let Some(pou) = ctx.pou else { + let Some(pou) = ctx.pou else { unreachable!("VLA not allowed outside of POUs") }; - let AstStatement::Reference { name, .. } = statement else { - unreachable!("must be a reference to a VLA") - }; + let name = if let AstStatement::Identifier { name, .. } = statement { + name.as_str() + } else { + statement.get_flat_reference_name().expect("must be a reference to a VLA") + }; - let Some(argument_type) = self.index.get_pou_members(pou) + let Some(argument_type) = self.index.get_pou_members(pou) .iter() .filter(|it| it.get_name() == name) .map(|it| it.get_declaration_type()) @@ -1481,15 +1506,14 @@ impl<'i> TypeAnnotator<'i> { unreachable!() }; - let hint_annotation = StatementAnnotation::Variable { - resulting_type: inner_type_name.to_owned(), - qualified_name: qualified_name.to_string(), - constant: false, - argument_type, - is_auto_deref: false, - }; - self.annotation_map.annotate_type_hint(statement, hint_annotation) - } + let hint_annotation = StatementAnnotation::Variable { + resulting_type: inner_type_name.to_owned(), + qualified_name: qualified_name.to_string(), + constant: false, + argument_type, + is_auto_deref: false, + }; + self.annotation_map.annotate_type_hint(statement, hint_annotation) } } @@ -1501,9 +1525,10 @@ impl<'i> TypeAnnotator<'i> { unreachable!("Always a call statement"); }; // #604 needed for recursive function calls - self.visit_statement(&ctx.set_is_call(), operator); + self.visit_statement(&ctx.with_resolving_strategy(ResolvingScope::call_operator_scopes()), operator); let operator_qualifier = self.get_call_name(operator); //Use the context without the is_call =true + //TODO why do we start a lhs context here??? let ctx = ctx.with_lhs(operator_qualifier.as_str()); let parameters = if let Some(parameters) = parameters_stmt { self.visit_statement(&ctx, parameters); @@ -1513,7 +1538,7 @@ impl<'i> TypeAnnotator<'i> { }; if let Some(annotation) = builtins::get_builtin(&operator_qualifier).and_then(BuiltIn::get_annotation) { - annotation(self, operator, parameters_stmt, ctx) + annotation(self, operator, parameters_stmt, ctx.to_owned()) } else { //If builtin, skip this let mut generics_candidates: HashMap> = HashMap::new(); @@ -1615,7 +1640,7 @@ impl<'i> TypeAnnotator<'i> { operator_qualifier, operator, parameters_stmt, - ctx, + ctx.to_owned(), ); } if let Some(StatementAnnotation::Function { return_type, .. }) = self.annotation_map.get(operator) { @@ -1649,10 +1674,12 @@ impl<'i> TypeAnnotator<'i> { StatementAnnotation::Value { resulting_type } => { // make sure we come from an array or function_block access match operator { - AstStatement::ArrayAccess { .. } => Some(resulting_type.clone()), - AstStatement::PointerAccess { .. } => { - self.index.find_pou(resulting_type.as_str()).map(|it| it.get_name().to_string()) - } + AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_),.. } => Some(resulting_type.clone()), + AstStatement::ReferenceExpr { access: ReferenceAccess::Deref, .. } => + // AstStatement::ArrayAccess { .. } => Some(resulting_type.clone()), + // AstStatement::PointerAccess { .. } => { + self.index.find_pou(resulting_type.as_str()).map(|it| it.get_name().to_string()), + // } _ => None, } } @@ -1794,6 +1821,29 @@ pub(crate) fn add_pointer_type(index: &mut Index, inner_type_name: String) -> St new_type_name } +fn to_pou_annotation(p: &PouIndexEntry, index: &Index) -> Option { + match p { + PouIndexEntry::Program { name, .. } => { + Some(StatementAnnotation::Program { qualified_name: name.into() }) + } + PouIndexEntry::Function { name, return_type, .. } => Some(StatementAnnotation::Function { + return_type: return_type.into(), + qualified_name: name.into(), + call_name: None, + }), + PouIndexEntry::FunctionBlock { name, .. } => { + Some(StatementAnnotation::Type { type_name: name.into() }) + } + PouIndexEntry::Action { name, parent_pou_name, .. } => match index.find_pou(parent_pou_name) { + Some(PouIndexEntry::Program { .. }) => { + Some(StatementAnnotation::Program { qualified_name: name.into() }) + } + _ => None, + }, + _ => None, + } +} + fn to_variable_annotation( v: &VariableIndexEntry, index: &Index, @@ -1843,6 +1893,151 @@ fn get_real_type_name_for(value: &str) -> &'static str { REAL_TYPE } +#[derive(Clone)] +pub enum ResolvingScope { + Variable, //try to resolve a variable + POU, //try to resolve a POU + DataType, //try to resolve a DataType + EnumTypeOnly, //only consider EnumTypes + FunctionsOnly, //only consider functions +} + +impl ResolvingScope { + /// scopes that can be used for general references. Will resolve to local/global + /// variables, Pous or datatypes + pub fn default_scopes() -> Vec { + vec![ResolvingScope::Variable, ResolvingScope::POU, ResolvingScope::DataType] + } + + /// scopes intended for call-statement-operators + fn call_operator_scopes() -> Vec { + let mut strategy = vec![ResolvingScope::FunctionsOnly]; + strategy.extend(Self::default_scopes()); + strategy + } + + /// tries to resolve the given name using the reprsented scope + /// - `name` the name to resolve + /// - `qualifier` an optional qualifier to prefix to the name, + /// if the qualifier is present, this method only resolves to targets with this qualifier + /// - `index` the index to perform the lookups on + fn resolve_name( + &self, + name: &str, + qualifier: Option<&str>, + index: &Index, + ctx: &VisitorContext, + ) -> Option { + match self { + // try to resolve the name as a variable + ResolvingScope::Variable => { + if let Some(qualifier) = qualifier { + // look for variable, enum with name "qualifier.name" + index + .find_member(qualifier, name) + .or_else(|| index.find_qualified_enum_element(format!("{qualifier}.{name}").as_str())) + .map(|it| to_variable_annotation(it, index, it.is_constant() || ctx.constant)) + } else { + // look for member variable with name "pou.name" + // then try fopr a global variable called "name" + ctx.pou + .and_then(|pou| index.find_member(pou, name)) + .or_else(|| index.find_global_variable(name)) + .map(|g| to_variable_annotation(g, index, g.is_constant())) + } + } + // try to resolve the name as POU/Action/Method + ResolvingScope::POU => { + if let Some(qualifier) = qualifier { + // look for Pou/Action with name "qualifier.name" + index + .find_pou(format!("{qualifier}.{name}").as_str()) + .or_else(|| index.find_method(qualifier, name)) + .map(|action| action.into()) + } else { + // look for Pou with name "name" + index.find_pou(name).and_then(|pou| to_pou_annotation(pou, index)).or_else(|| { + ctx.pou.and_then(|pou| + // retry with local pou as qualifier + ResolvingScope::POU.resolve_name(name, Some(pou), index, ctx)) + }) + } + } + // try to resolve the name as a datatype + ResolvingScope::DataType => { + if qualifier.is_none() { + // look for datatype with name "name" + index + .find_type(name) + .map(|data_type| StatementAnnotation::data_type(data_type.get_name())) + } else { + // there are no qualified types + None + } + } + // try to resolve the name as an enum-type + ResolvingScope::EnumTypeOnly => { + if qualifier.is_none() { + // look for enum-tyoe with name "name" + index + .find_type(name) + .filter(|it| it.is_enum()) + .map(|enum_type| StatementAnnotation::data_type(enum_type.get_name())) + } else { + // there are no qualified types + None + } + } + // try to resolve this name as a function + ResolvingScope::FunctionsOnly => { + if qualifier.is_none() { + // look for function with name "name" + index.find_pou(name).filter(|it| it.is_function()).map(|pou| pou.into()) + } else { + // there are no qualified functions + None + } + } + } + } +} + +/// registers the resulting string-literal if the given literal is a String with a different encoding than what is +/// requested from given cast_type (e.g. STRING#"i am utf16", or WSTRING#'i am utf8') +fn accept_cast_string_literal( + literals: &mut StringLiterals, + cast_type: &typesystem::DataType, + literal: &AstStatement, +) { + // check if we need to register an additional string-literal + match (cast_type.get_type_information(), literal) { + ( + DataTypeInformation::String { encoding: StringEncoding::Utf8, .. }, + AstStatement::Literal { + kind: AstLiteral::String(StringValue { value, is_wide: is_wide @ true }), + .. + }, + ) + | ( + DataTypeInformation::String { encoding: StringEncoding::Utf16, .. }, + AstStatement::Literal { + kind: AstLiteral::String(StringValue { value, is_wide: is_wide @ false }), + .. + }, + ) => { + // re-register the string-literal in the opposite encoding + if *is_wide { + literals.utf08.insert(value.to_string()); + } else { + literals.utf16.insert(value.to_string()); + } + } + _ => { + //ignore + } + } +} + #[cfg(test)] mod resolver_tests { use super::{get_int_type_name_for, get_real_type_name_for}; diff --git a/src/resolver/const_evaluator.rs b/src/resolver/const_evaluator.rs index 930f416249..8c77b2d661 100644 --- a/src/resolver/const_evaluator.rs +++ b/src/resolver/const_evaluator.rs @@ -377,11 +377,15 @@ fn evaluate_with_target_hint( location, ) } - AstStatement::CastStatement { target, type_name, .. } => { - let dti = index.find_effective_type_info(type_name); + AstStatement::ReferenceExpr { + access: ReferenceAccess::Cast(target), base: Some(type_name), .. + } => { + let dti = type_name + .get_flat_reference_name() + .and_then(|type_name| index.find_effective_type_info(type_name)); match dti { Some(DataTypeInformation::Enum { name: enum_name, .. }) => { - if let AstStatement::Reference { name: ref_name, .. } = target.as_ref() { + if let AstStatement::Identifier { name: ref_name, .. } = target.as_ref() { return index .find_enum_element(enum_name, ref_name) .ok_or_else(|| { @@ -394,33 +398,26 @@ fn evaluate_with_target_hint( return Err(UnresolvableKind::Misc("Cannot resolve unknown constant.".to_string())); } } - _ => { - evaluate_with_target_hint(target, scope, index, Some(type_name))?; - Some(get_cast_statement_literal(target, type_name, scope, index)?) + Some(dti) => { + evaluate_with_target_hint(target, scope, index, Some(dti.get_name()))?; + Some(get_cast_statement_literal(target, dti.get_name(), scope, index)?) } + None => return Err(UnresolvableKind::Misc("Cannot resolve unknown Type-Cast.".to_string())), } } - AstStatement::Reference { name, .. } => index - .find_variable(scope, std::slice::from_ref(&name.as_str())) - .map(|variable| resolve_const_reference(variable, name, index)) - .transpose()? - .flatten(), - AstStatement::QualifiedReference { elements, .. } => { - // we made sure that there are exactly two references - //TODO https://github.com/ghaith/rusty/issues/291 - once we can initialize structs, we need to allow generic qualified references here - if elements.len() == 2 { - if let ( - AstStatement::Reference { name: pou_name, .. }, - AstStatement::Reference { name: variable_name, .. }, - ) = (&elements[0], &elements[1]) - { - return index - .find_member(pou_name, variable_name) - .ok_or_else(|| UnresolvableKind::Misc("Cannot resolve unknown constant.".to_string())) - .and_then(|variable| resolve_const_reference(variable, variable_name, index)); - } + AstStatement::ReferenceExpr { access: ReferenceAccess::Member(reference), base, .. } => { + if let Some(name) = reference.get_flat_reference_name() { + index + .find_variable( + base.as_ref().and_then(|it| it.get_flat_reference_name()).or(scope), + std::slice::from_ref(&name), + ) + .map(|variable| resolve_const_reference(variable, name, index)) + .transpose()? + .flatten() + } else { + None } - return Err(UnresolvableKind::Misc("Qualified references only allow references to qualified variables in the form of 'POU.variable'".to_string())); } AstStatement::BinaryExpression { left, right, operator, id, .. } => { let eval_left = evaluate(left, scope, index)?; @@ -739,6 +736,6 @@ macro_rules! compare_expression { } use compare_expression; use plc_ast::{ - ast::{AstId, AstStatement, Operator, SourceRange}, + ast::{AstId, AstStatement, Operator, ReferenceAccess, SourceRange}, literals::{Array, AstLiteral, StringValue}, }; diff --git a/src/resolver/tests/const_resolver_tests.rs b/src/resolver/tests/const_resolver_tests.rs index b5dd697575..b94163addc 100644 --- a/src/resolver/tests/const_resolver_tests.rs +++ b/src/resolver/tests/const_resolver_tests.rs @@ -1061,8 +1061,6 @@ fn nested_array_literals_type_resolving() { index.find_effective_type_by_name(a.get_type_name()) ); - println!("{initializer:#?}"); - //check the initializer's array-element's types if let AstStatement::Literal { kind: AstLiteral::Array(Array { elements: Some(e) }), .. } = initializer { if let Some(DataTypeInformation::Array { inner_type_name, .. }) = @@ -1159,7 +1157,6 @@ fn nested_array_literals_multiplied_statement_type_resolving() { { //check if the inner thing really got the BYTE hint // multiplied-element = 2 - println!("{:#?}", multiplied_element.as_ref()); assert_eq!( annotations.get_type_hint(multiplied_element.as_ref(), &index), index.find_effective_type_by_name("BYTE") diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 4d634455c3..4f01bd3334 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -2,7 +2,7 @@ use core::panic; use insta::{assert_debug_snapshot, assert_snapshot}; use plc_ast::{ - ast::{flatten_expression_list, AstStatement, DataType, Pou, UserTypeDeclaration}, + ast::{flatten_expression_list, AstStatement, DataType, Pou, ReferenceAccess, UserTypeDeclaration}, control_statements::{AstControlStatement, CaseStatement}, literals::{Array, AstLiteral}, provider::IdProvider, @@ -11,7 +11,7 @@ use plc_ast::{ use crate::{ index::{ArgumentType, Index, VariableType}, resolver::{AnnotationMap, AnnotationMapImpl, StatementAnnotation}, - test_utils::tests::{annotate_with_ids, codegen, index_with_ids}, + test_utils::tests::{annotate_with_ids, index_with_ids}, typesystem::{ DataTypeInformation, Dimension, TypeSize, BOOL_TYPE, BYTE_TYPE, DINT_TYPE, DWORD_TYPE, INT_TYPE, LINT_TYPE, LREAL_TYPE, LWORD_TYPE, REAL_TYPE, SINT_TYPE, UINT_TYPE, USINT_TYPE, VOID_TYPE, WORD_TYPE, @@ -54,6 +54,156 @@ fn binary_expressions_resolves_types() { assert_eq!(expected_types, types); } +#[test] +fn cast_expressions_resolves_types() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "PROGRAM PRG + VAR a : SINT; END_VAR + BYTE#7; + INT#a; + UINT#7; + DWORD#7; + END_PROGRAM", + id_provider.clone(), + ); + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let statements = &unit.implementations[0].statements; + assert_type_and_hint!(&annotations, &index, &statements[0], BYTE_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[1], INT_TYPE, None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), ..} = &statements[1] else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, target.as_ref(), SINT_TYPE, None); + + assert_type_and_hint!(&annotations, &index, &statements[2], UINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[3], DWORD_TYPE, None); +} + +#[test] +fn cast_expression_literals_get_casted_types() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "PROGRAM PRG + INT#16#FFFF; + WORD#16#FFFF; + END_PROGRAM", + id_provider.clone(), + ); + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let statements = &unit.implementations[0].statements; + { + assert_type_and_hint!(&annotations, &index, &statements[0], INT_TYPE, None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), ..} = &statements[0] else {unreachable!()}; + let t = target.as_ref(); + assert_eq!( + format!( + "{:#?}", + AstStatement::Literal { kind: AstLiteral::Integer(0xFFFF), location: (0..0).into(), id: 0 } + ), + format!("{t:#?}") + ); + assert_type_and_hint!(&annotations, &index, target.as_ref(), INT_TYPE, None); + } + { + assert_type_and_hint!(&annotations, &index, &statements[1], WORD_TYPE, None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), ..} = &statements[1] else {unreachable!()}; + let t = target.as_ref(); + assert_eq!( + format!( + "{:#?}", + AstStatement::Literal { kind: AstLiteral::Integer(0xFFFF), location: (0..0).into(), id: 0 } + ), + format!("{t:#?}") + ); + assert_type_and_hint!(&annotations, &index, target.as_ref(), WORD_TYPE, None); + } +} + +#[test] +fn cast_expressions_of_enum_with_resolves_types() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "PROGRAM PRs + MyEnum#a; + MyEnum#b; + END_PROGRAM + TYPE MyEnum : (a,b,c); END_TYPE + ", + id_provider.clone(), + ); + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let statements = &unit.implementations[0].statements; + assert_type_and_hint!(&annotations, &index, &statements[0], "MyEnum", None); + assert_type_and_hint!(&annotations, &index, &statements[1], "MyEnum", None); + + let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(access), ..} = &statements[0] else { unreachable!()}; + assert_eq!( + annotations.get(access), + Some(&StatementAnnotation::Variable { + resulting_type: "MyEnum".to_string(), + qualified_name: "MyEnum.a".to_string(), + constant: true, + argument_type: ArgumentType::ByVal(VariableType::Global), + is_auto_deref: false + }) + ); + + let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(access), ..} = &statements[1] else { unreachable!()}; + assert_eq!( + annotations.get(access), + Some(&StatementAnnotation::Variable { + resulting_type: "MyEnum".to_string(), + qualified_name: "MyEnum.b".to_string(), + constant: true, + argument_type: ArgumentType::ByVal(VariableType::Global), + is_auto_deref: false + }) + ); +} + +#[test] +fn cast_expressions_of_enum_with_direct_access_resolves_types() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "PROGRAM PRs + MyEnum#a.1; + MyEnum#a.%W1; + END_PROGRAM + TYPE MyEnum : (a,b,c); END_TYPE + ", + id_provider.clone(), + ); + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let statements = &unit.implementations[0].statements; + assert_type_and_hint!(&annotations, &index, &statements[0], BOOL_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[1], WORD_TYPE, None); +} + +#[test] +fn direct_access_using_enum_values_resolves_types() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "PROGRAM PRs + VAR + x : INT; + END_VAR + + x.%XBIT01; + x.%XBIT02; + x.BIT01; + x.BIT02; + END_PROGRAM + TYPE MyEnum : (BIT01:=0,BIT02,BIT03); END_TYPE + ", + id_provider.clone(), + ); + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let statements = &unit.implementations[0].statements; + assert_type_and_hint!(&annotations, &index, &statements[0], BOOL_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[1], BOOL_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[2], VOID_TYPE, None); + assert_type_and_hint!(&annotations, &index, &statements[3], VOID_TYPE, None); +} + #[test] fn binary_expressions_resolves_types_for_mixed_signed_ints() { let id_provider = IdProvider::default(); @@ -196,7 +346,7 @@ fn binary_expressions_resolves_types_for_literals_directly() { } #[test] -fn addition_substraction_expression_with_pointers_resolves_to_pointer_type() { +fn addition_subtraction_expression_with_pointers_resolves_to_pointer_type() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( "PROGRAM PRG @@ -839,8 +989,8 @@ fn pou_expressions_resolve_types() { let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); let statements = &unit.implementations[3].statements; - //none of these pou's should really resolve to a type - let expected_types = vec![VOID_TYPE, VOID_TYPE, VOID_TYPE]; + //Functions and Functionblocks should not resolve to a type + let expected_types = vec!["OtherPrg", VOID_TYPE, "OtherFuncBlock"]; let type_names: Vec<&str> = statements.iter().map(|s| annotations.get_type_or_void(s, &index).get_name()).collect(); assert_eq!(format!("{expected_types:?}"), format!("{type_names:?}")); @@ -1063,6 +1213,17 @@ fn function_call_expression_resolves_to_the_function_itself_not_its_return_type( // AND we expect no type to be associated with the expression let associated_type = annotations.get_type(&statements[0], &index); assert_eq!(index.find_effective_type_by_name("INT"), associated_type); + + // AND the reference itself should be ... + let AstStatement::CallStatement { operator,.. } = &statements[0] else {unreachable!()}; + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "INT".into(), + qualified_name: "foo".into(), + call_name: None + }), + annotations.get(operator) + ); } #[test] @@ -1092,15 +1253,15 @@ fn comparison_resolves_to_function_call() { // THEN we expect it to be annotated with the new function call ast for comparison let annotation = annotations.get(&statements[0]); - assert_snapshot!(format!("{annotation:?}")); + assert_snapshot!(format!("{annotation:#?}")); let annotation = annotations.get(&statements[1]); - assert_snapshot!(format!("{annotation:?}")); + assert_snapshot!(format!("{annotation:#?}")); let annotation = annotations.get(&statements[2]); - assert_snapshot!(format!("{annotation:?}")); + assert_snapshot!(format!("{annotation:#?}")); let annotation = annotations.get(&statements[3]); - assert_snapshot!(format!("{annotation:?}")); + assert_snapshot!(format!("{annotation:#?}")); let annotation = annotations.get(&statements[4]); - assert_snapshot!(format!("{annotation:?}")); + assert_snapshot!(format!("{annotation:#?}")); } #[test] @@ -1125,6 +1286,37 @@ fn shadowed_function_is_annotated_correctly() { assert_type_and_hint!(&annotations, &index, &statements[0], "DINT", None); } +#[test] +fn alias_and_subrange_expressions_resolve_types() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + TYPE MyAlias : INT; END_TYPE; + TYPE MySubrange : INT(0..100); END_TYPE; + + PROGRAM PRG + VAR + i : INT; + a : MyAlias; + s : MySubrange; + END_VAR + i; + a; + s; + END_PROGRAM", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + let statements = &unit.implementations[0].statements; + + let expected_types = vec!["INT", "INT", "MySubrange"]; + let type_names: Vec<&str> = + statements.iter().map(|s| annotations.get_type_or_void(s, &index).get_name()).collect(); + + assert_eq!(format!("{expected_types:?}"), format!("{type_names:?}")); +} + #[test] fn qualified_expressions_to_aliased_structs_resolve_types() { let id_provider = IdProvider::default(); @@ -1415,13 +1607,13 @@ fn actions_are_resolved() { prg.foo; END_PROGRAM ACTIONS prg - ACTION foo - END_ACTION + ACTION foo + END_ACTION END_ACTIONS FUNCTION buz : INT - prg.foo(); - prg.foo; + prg.foo(); + prg.foo; END_FUNCTION ", id_provider.clone(), @@ -1523,6 +1715,40 @@ fn bitaccess_is_resolved() { #[test] fn variable_direct_access_type_resolved() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + r" + PROGRAM prg + VAR + a : INT; + END_VAR + a.%X1; + a.%W2; + END_PROGRAM + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + let statements = &unit.implementations[0].statements; + + { + let a_x1 = &statements[0]; + assert_type_and_hint!(&annotations, &index, a_x1, BOOL_TYPE, None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(x1), base: Some(a), .. } = a_x1 else { unreachable!()}; + assert_type_and_hint!(&annotations, &index, a, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, x1, BOOL_TYPE, None); + } + { + let a_w2 = &statements[1]; + assert_type_and_hint!(&annotations, &index, a_w2, WORD_TYPE, None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(w2), base: Some(a), .. } = a_w2 else { unreachable!()}; + assert_type_and_hint!(&annotations, &index, a, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, w2, WORD_TYPE, None); + } +} + +#[test] +fn variable_direct_access_type_resolved2() { let id_provider = IdProvider::default(); let (unit, index) = index_with_ids( r" @@ -1546,12 +1772,9 @@ fn variable_direct_access_type_resolved() { let type_names: Vec<&str> = statements .iter() .map(|s| { - if let AstStatement::QualifiedReference { elements, .. } = s { - if let AstStatement::DirectAccess { index, .. } = elements.last().unwrap() { - return index; - } - } - panic!("Wrong type {:?}", s); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(reference), .. } = s else { unreachable!("expected ReferenceExpr") }; + let AstStatement::DirectAccess { index, .. } = reference.as_ref() else { unreachable!("expected DirectAccess") }; + index }) .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); @@ -1786,7 +2009,6 @@ fn global_enums_type_resolving() { id_provider.clone(), ); let annotations = annotate_with_ids(&unit, &mut index, id_provider); - //check the type-annotation of a,b,c's implicit initializers let initalizer_types = index @@ -1997,6 +2219,58 @@ fn struct_members_initializers_type_hint_test() { } } +#[test] +fn struct_member_explicit_initialization_test() { + // GIVEN a struct-initialization with explicit assignments + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + "FUNCTION main : DINT + VAR + x : myStruct; + END_VAR + x := (var1 := 1, var2 := 7); + END_FUNCTION + + TYPE myStruct : STRUCT + var1 : DINT; + var2 : BYTE; + END_STRUCT + END_TYPE", + id_provider.clone(), + ); + + // WHEN this type is annotated + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + + // THEN the initializers assignments have correct annotations + let AstStatement::Assignment { right, ..} = &unit.implementations[0].statements[0] else { unreachable!()}; + let AstStatement::ExpressionList { expressions, ..} = right.as_ref() else {unreachable!()}; + + let AstStatement::Assignment{left, ..} = &expressions[0] else {unreachable!()}; + assert_eq!( + Some(&StatementAnnotation::Variable { + resulting_type: "DINT".to_string(), + qualified_name: "myStruct.var1".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Input), + is_auto_deref: false + }), + annotations.get(left) + ); + + let AstStatement::Assignment{left, ..} = &expressions[1] else {unreachable!()}; + assert_eq!( + Some(&StatementAnnotation::Variable { + resulting_type: "BYTE".to_string(), + qualified_name: "myStruct.var2".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Input), + is_auto_deref: false + }), + annotations.get(left) + ); +} + #[test] fn program_members_initializers_type_hint_test() { //GIVEN a pou with some initialization @@ -2065,10 +2339,10 @@ fn data_type_initializers_type_hint_test() { ); } } else { - unreachable!() + unreachable!("{:#?}", unit) } } else { - unreachable!() + unreachable!("{:#?}", unit) } } else { unreachable!() @@ -2519,26 +2793,20 @@ fn nested_bitwise_access_resolves_correctly() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let assignment = &unit.implementations[0].statements[0]; - if let AstStatement::Assignment { right, .. } = assignment { - assert_type_and_hint!(&annotations, &index, right, "BOOL", Some("BOOL")); //strange - if let AstStatement::QualifiedReference { elements, .. } = right.as_ref() { - assert_type_and_hint!(&annotations, &index, &elements[0], "LWORD", None); - assert_type_and_hint!(&annotations, &index, &elements[1], "DWORD", None); - assert_type_and_hint!(&annotations, &index, &elements[2], "WORD", None); - assert_type_and_hint!(&annotations, &index, &elements[3], "BYTE", None); - assert_type_and_hint!(&annotations, &index, &elements[4], "BOOL", None); - - if let AstStatement::DirectAccess { index: idx_stmt, .. } = &elements[4] { - assert_type_and_hint!(&annotations, &index, idx_stmt, "DINT", None); - } else { - unreachable!() - } - } else { - unreachable!() - } - } else { - unreachable!(); - } + let AstStatement::Assignment { right, .. } = assignment else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, right, "BOOL", Some("BOOL")); //strange + + let AstStatement::ReferenceExpr { base: Some(base), ..} = right.as_ref() else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, base, "BYTE", None); + + let AstStatement::ReferenceExpr { base: Some(base), ..} = base.as_ref() else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, base, "WORD", None); + + let AstStatement::ReferenceExpr { base: Some(base), ..} = base.as_ref() else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, base, "DWORD", None); + + let AstStatement::ReferenceExpr { base: Some(base), ..} = base.as_ref() else {unreachable!()}; + assert_type_and_hint!(&annotations, &index, base, "LWORD", None); } #[test] @@ -2600,15 +2868,16 @@ fn array_accessor_in_struct_array_is_annotated() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let qr = &unit.implementations[0].statements[0]; - if let AstStatement::QualifiedReference { elements, .. } = qr { - if let AstStatement::ArrayAccess { access, .. } = &elements[1] { - assert_type_and_hint!(&annotations, &index, access.as_ref(), "INT", None); - } else { - unreachable!() - } - } else { - unreachable!(); - } + assert_type_and_hint!(&annotations, &index, qr, "INT", None); + let AstStatement::ReferenceExpr { base: Some(base), .. } = qr else { + panic!("expected ReferenceExpr for {:?}", qr); + }; + assert_type_and_hint!(&annotations, &index, base, "__MyStruct_arr1", None); + + let AstStatement::ReferenceExpr { base: Some(base), .. } = base.as_ref() else { + panic!("expected ReferenceExpr for {:?}", base); + }; + assert_type_and_hint!(&annotations, &index, base, "MyStruct", None); } #[test] @@ -2838,8 +3107,12 @@ fn assigning_ptr_to_lword_will_annotate_correctly2() { assert_type_and_hint!(&annotations, &index, left, DWORD_TYPE, None); assert_type_and_hint!(&annotations, &index, right, INT_TYPE, Some(DWORD_TYPE)); - if let AstStatement::PointerAccess { reference, .. } = right.as_ref() { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Deref, base: Some(reference), .. } = + right.as_ref() + { assert_type_and_hint!(&annotations, &index, reference, ptr_type, None); + } else { + unreachable!() } } else { unreachable!() @@ -2903,6 +3176,60 @@ fn pointer_assignment_with_incompatible_types_hints_correctly() { } } +#[test] +fn call_explicit_parameter_name_is_resolved() { + //GIVEN + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + FUNCTION_BLOCK fb + VAR_INPUT + a: INT; + b: DINT; + END_VAR + END_FUNCTION_BLOCK + + PROGRAM PRG + VAR + f : fb; + END_VAR + f(b:= 1, a:= 3); + END_PROGRAM + ", + id_provider.clone(), + ); + + //WHEN the AST is annotated + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + // should be the call statement + // should contain array access as operator + let AstStatement::CallStatement { parameters, .. } = &unit.implementations[1].statements[0] else { unreachable!("expected callstatement")}; + let AstStatement::Assignment { left: b, .. } = flatten_expression_list(parameters.as_ref().as_ref().unwrap())[0] else { unreachable!()}; + let AstStatement::Assignment { left: a, .. } = flatten_expression_list(parameters.as_ref().as_ref().unwrap())[1] else { unreachable!()}; + + assert_eq!( + Some(&StatementAnnotation::Variable { + resulting_type: DINT_TYPE.to_string(), + qualified_name: "fb.b".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Input), + is_auto_deref: false + }), + annotations.get(b.as_ref()) + ); + + assert_eq!( + Some(&StatementAnnotation::Variable { + resulting_type: INT_TYPE.to_string(), + qualified_name: "fb.a".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Input), + is_auto_deref: false + }), + annotations.get(a) + ); +} + #[test] fn call_on_function_block_array() { //GIVEN @@ -2927,13 +3254,13 @@ fn call_on_function_block_array() { // should be the call statement let statements = &unit.implementations[1].statements[0]; // should contain array access as operator - let operator = match statements { - AstStatement::CallStatement { operator, .. } => Some(operator.as_ref()), - _ => None, - }; - assert!(matches!(operator, Some(&AstStatement::ArrayAccess { .. })),); + let AstStatement::CallStatement { operator, .. } = statements else { unreachable!("expected callstatement")}; + assert!(matches!( + operator.as_ref(), + &AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), .. } + ),); - let annotation = annotations.get(operator.unwrap()); + let annotation = annotations.get(operator.as_ref()); assert_eq!(Some(&StatementAnnotation::Value { resulting_type: "fb".into() }), annotation); } @@ -3017,9 +3344,21 @@ fn resolve_recursive_function_call() { //WHEN the AST is annotated let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); let type_map = annotations.type_map; - let annotated_types = format!("{type_map:#?}"); - insta::assert_snapshot!(annotated_types); + let call = &unit.implementations[0].statements[0]; + + let AstStatement::CallStatement { operator, .. } = call else { unreachable!(); }; + + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "DINT".into(), + qualified_name: "foo".into(), + call_name: None + }), + type_map.get(&operator.get_id()) + ); + + // insta::assert_snapshot!(annotated_types); } #[test] @@ -3050,9 +3389,16 @@ fn resolve_recursive_program_call() { //WHEN the AST is annotated let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); let type_map = annotations.type_map; - let annotated_types = format!("{type_map:#?}"); - insta::assert_snapshot!(annotated_types); + let call = &unit.implementations[0].statements[0]; + let AstStatement::CallStatement { operator, .. } = call else { unreachable!(); }; + + assert_eq!( + Some(&StatementAnnotation::Program { qualified_name: "mainProg".into() }), + type_map.get(&operator.get_id()) + ); + + // insta::assert_snapshot!(annotated_types); } #[test] @@ -3229,7 +3575,7 @@ fn resolve_return_variable_in_nested_call() { } // AND we want a call passing the return-variable as apointer (actually the adress as a LWORD) - assert_snapshot!(codegen(src)); + // assert_snapshot!(codegen(src)); // TODO moved to codegen tests } #[test] @@ -3328,20 +3674,17 @@ fn multiple_pointer_with_dereference_annotates_and_nests_correctly() { let mut annotations = annotate_with_ids(&unit, &mut index, id_provider); let statement = &unit.implementations[0].statements[0]; index.import(std::mem::take(&mut annotations.new_index)); + // THEN the expressions are nested and annotated correctly - if let AstStatement::PointerAccess { reference, .. } = &statement { - assert_type_and_hint!(&annotations, &index, reference, "__POINTER_TO___POINTER_TO_BYTE", None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Deref, base: Some(value), ..} = &statement else { unreachable!("expected ReferenceExpr, but got {statement:#?}")}; + assert_type_and_hint!(&annotations, &index, value, "__POINTER_TO___POINTER_TO_BYTE", None); - if let AstStatement::UnaryExpression { value, .. } = &reference.as_ref() { - assert_type_and_hint!(&annotations, &index, value, "__POINTER_TO_BYTE", None); + let AstStatement::ReferenceExpr { access: ReferenceAccess::Address, base: Some(base), ..} = value.as_ref() else { unreachable!("expected ReferenceExpr, but got {value:#?}")}; + assert_type_and_hint!(&annotations, &index, base, "__POINTER_TO_BYTE", None); + + let AstStatement::ReferenceExpr { access: ReferenceAccess::Address, base: Some(base), ..} = base.as_ref() else { unreachable!("expected ReferenceExpr, but got {base:#?}")}; + assert_type_and_hint!(&annotations, &index, base, "BYTE", None); - if let AstStatement::UnaryExpression { value, .. } = &value.as_ref() { - assert_type_and_hint!(&annotations, &index, value, "BYTE", None); - } - } - } else { - panic!("Not a pointer") - } // AND the overall type of the statement is annotated correctly assert_type_and_hint!(&annotations, &index, statement, "__POINTER_TO_BYTE", None); } @@ -3581,7 +3924,10 @@ fn array_passed_to_function_with_vla_param_is_annotated_correctly() { ); let annotations = annotate_with_ids(&unit, &mut index, id_provider); let stmt = &unit.implementations[0].statements[0]; - if let AstStatement::ArrayAccess { reference, .. } = &stmt { + + assert_type_and_hint!(&annotations, &index, stmt, "INT", None); + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), base: Some(reference), .. } = stmt + { assert_type_and_hint!(&annotations, &index, reference.as_ref(), "__foo_arr", Some("__arr_vla_1_int")); } else { unreachable!() @@ -3750,12 +4096,12 @@ fn vla_access_is_annotated_correctly() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let stmt = &unit.implementations[0].statements[0]; - if let AstStatement::ArrayAccess { reference, .. } = stmt { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), base: Some(base), .. } = stmt { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, stmt, "INT", None); // reference is annotated with type and hint - assert_type_and_hint!(&annotations, &index, reference.as_ref(), "__foo_arr", Some("__arr_vla_1_int")); + assert_type_and_hint!(&annotations, &index, base.as_ref(), "__foo_arr", Some("__arr_vla_1_int")); } else { panic!("expected an array access, got none") } @@ -3781,7 +4127,10 @@ fn vla_write_access_is_annotated_correctly() { let stmt = &unit.implementations[0].statements[0]; if let AstStatement::Assignment { left, .. } = stmt { - if let AstStatement::ArrayAccess { reference, .. } = left.as_ref() { + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Index(_), base: Some(reference), .. + } = left.as_ref() + { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, left.as_ref(), "INT", None); @@ -3821,7 +4170,11 @@ fn writing_value_read_from_vla_to_vla() { // both VLA references should receive the same type hint if let AstStatement::Assignment { left, right, .. } = stmt { - if let AstStatement::ArrayAccess { reference, .. } = left.as_ref() { + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Index(_), base: Some(reference), .. + } = left.as_ref() + { + // if let AstStatement::ArrayAccess { reference, .. } = left.as_ref() { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, left.as_ref(), "INT", None); @@ -3837,7 +4190,10 @@ fn writing_value_read_from_vla_to_vla() { panic!("expected an array access, got none") } - if let AstStatement::ArrayAccess { reference, .. } = right.as_ref() { + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Index(_), base: Some(reference), .. + } = right.as_ref() + { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, right.as_ref(), "INT", Some("INT")); @@ -3876,7 +4232,10 @@ fn address_of_works_on_vla() { let stmt = &unit.implementations[0].statements[0]; if let AstStatement::Assignment { right, .. } = stmt { - if let AstStatement::UnaryExpression { value, .. } = right.as_ref() { + if let AstStatement::ReferenceExpr { + access: ReferenceAccess::Address, base: Some(reference), .. + } = right.as_ref() + { // rhs of assignment resolves to LWORD assert_type_and_hint!( &annotations, @@ -3886,16 +4245,14 @@ fn address_of_works_on_vla() { Some("LWORD") ); - if let AstStatement::PointerAccess { reference, .. } = value.as_ref() { - // unary expression resolves to pointer access to array of INT - assert_type_and_hint!( - &annotations, - &index, - reference.as_ref(), - "__foo_arr", - Some("__arr_vla_1_int") - ); - } + // unary expression resolves to pointer access to array of INT + assert_type_and_hint!( + &annotations, + &index, + reference.as_ref(), + "__foo_arr", + Some("__arr_vla_1_int") + ); } else { panic!("expected an array access, got none") } @@ -3925,7 +4282,8 @@ fn by_ref_vla_access_is_annotated_correctly() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let stmt = &unit.implementations[0].statements[0]; - if let AstStatement::ArrayAccess { reference, .. } = stmt { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), base: Some(reference), .. } = stmt + { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, stmt, "INT", None); @@ -3937,7 +4295,8 @@ fn by_ref_vla_access_is_annotated_correctly() { let stmt = &unit.implementations[0].statements[1]; - if let AstStatement::ArrayAccess { reference, .. } = stmt { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), base: Some(reference), .. } = stmt + { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, stmt, "INT", None); @@ -4043,7 +4402,8 @@ fn multi_dimensional_vla_access_is_annotated_correctly() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let stmt = &unit.implementations[0].statements[0]; - if let AstStatement::ArrayAccess { reference, .. } = stmt { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), base: Some(reference), .. } = stmt + { // entire statement resolves to INT assert_type_and_hint!(&annotations, &index, stmt, "INT", None); @@ -4143,15 +4503,16 @@ fn override_is_resolved() { let (unit, index) = index_with_ids( " CLASS cls - METHOD foo : INT - END_METHOD - METHOD bar : INT - END_METHOD + METHOD foo : INT + END_METHOD + + METHOD bar : INT + END_METHOD END_CLASS CLASS cls2 EXTENDS cls - METHOD OVERRIDE foo : INT - END_METHOD + METHOD OVERRIDE foo : INT + END_METHOD END_CLASS FUNCTION_BLOCK fb diff --git a/src/resolver/tests/resolve_generic_calls.rs b/src/resolver/tests/resolve_generic_calls.rs index eb841c40d4..7b59173b13 100644 --- a/src/resolver/tests/resolve_generic_calls.rs +++ b/src/resolver/tests/resolve_generic_calls.rs @@ -290,7 +290,7 @@ fn call_order_of_parameters_does_not_change_annotations() { .iter() .find(|it| { matches!(it, AstStatement::Assignment { left, .. } - if { matches!(&**left, AstStatement::Reference{name, ..} if {name == expected_name})}) + if { matches!(&**left, AstStatement::ReferenceExpr {..} if { left.get_flat_reference_name() == Some(expected_name)})}) }) .unwrap() } @@ -364,7 +364,7 @@ fn call_order_of_generic_parameters_does_not_change_annotations() { .iter() .find(|it| { matches!(it, AstStatement::Assignment { left, .. } - if { matches!(&**left, AstStatement::Reference{name, ..} if {name == expected_name})}) + if { matches!(&**left, AstStatement::ReferenceExpr{ ..} if {left.get_flat_reference_name() == Some(expected_name)})}) }) .unwrap() } diff --git a/src/resolver/tests/resolve_literals_tests.rs b/src/resolver/tests/resolve_literals_tests.rs index aa798cc823..864e3a52cd 100644 --- a/src/resolver/tests/resolve_literals_tests.rs +++ b/src/resolver/tests/resolve_literals_tests.rs @@ -1,12 +1,12 @@ use plc_ast::{ - ast::{AstStatement, TypeNature}, + ast::{AstStatement, ReferenceAccess, TypeNature}, provider::IdProvider, }; use crate::{ assert_type_and_hint, - index::symbol::SymbolLocation, - resolver::{AnnotationMap, TypeAnnotator}, + index::{symbol::SymbolLocation, ArgumentType}, + resolver::{AnnotationMap, StatementAnnotation, TypeAnnotator}, test_utils::tests::{annotate_with_ids, index_with_ids}, typesystem::{DataType, DataTypeInformation, StringEncoding, TypeSize, DINT_TYPE}, }; @@ -230,17 +230,17 @@ fn enum_literals_are_annotated() { Green; //Color Dog; //Animal - Yellow; // local variable - Color#Yellow; //Animal + Yellow; //BYTE (local variable) + Color#Yellow; //Color - Cat; //global variable + Cat; //BOOL (global variable) Animal#Cat; //Animal // make sure these dont accidentally resolve to wrong enum - Animal#Green; //invalid (VOID) - Color#Dog; //invalid (VOID) + Animal#Green; //INVALID (invalid cast, validation must handle this) + Color#Dog; //INVALID (invalid cast, validation must handle this) invalid#Dog; //invalid (VOID) - Animal.Dog; //invalid (VOID) + Animal.Dog; //Dog (invalid access, validation must handle this) PRG.Cat; //invalid (VOID) END_PROGRAM", @@ -252,7 +252,7 @@ fn enum_literals_are_annotated() { let actual_resolves: Vec<&str> = statements.iter().map(|it| annotations.get_type_or_void(it, &index).get_name()).collect(); assert_eq!( - vec!["Color", "Animal", "BYTE", "Color", "BOOL", "Animal", "VOID", "VOID", "VOID", "VOID", "VOID"], + vec!["Color", "Animal", "BYTE", "Color", "BOOL", "Animal", "VOID", "VOID", "VOID", "Animal", "VOID"], actual_resolves ) } @@ -262,10 +262,11 @@ fn enum_literals_target_are_annotated() { let id_provider = IdProvider::default(); let (unit, index) = index_with_ids( " - TYPE Color: (Green, Yellow, Red); END_TYPE + TYPE Color: (Green, Yellow, Red) := 0; END_TYPE PROGRAM PRG - Color#Red; + VAR Red: BYTE; END_VAR + Color#Red; //we should resolve to the enum, not the local! END_PROGRAM", id_provider.clone(), ); @@ -281,7 +282,8 @@ fn enum_literals_target_are_annotated() { annotations.get_type_or_void(color_red, &index).get_type_information() ); - if let AstStatement::CastStatement { target, .. } = color_red { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), .. } = color_red { + // right type gets annotated assert_eq!( &DataTypeInformation::Enum { name: "Color".into(), @@ -290,6 +292,17 @@ fn enum_literals_target_are_annotated() { }, annotations.get_type_or_void(target, &index).get_type_information() ); + // Red gets annoatted to the declared variable, not only the type + assert_eq!( + Some(&StatementAnnotation::Variable { + resulting_type: "Color".into(), + qualified_name: "Color.Red".into(), + constant: true, + argument_type: ArgumentType::ByVal(crate::index::VariableType::Global), + is_auto_deref: false + }), + annotations.get(target) + ); } else { panic!("no cast statement") } @@ -313,19 +326,16 @@ fn casted_inner_literals_are_annotated() { ); let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); let statements = &unit.implementations[0].statements; - let expected_types = vec!["SINT", "INT", "DINT", "LINT", "REAL", "LREAL", "BOOL", "BOOL"]; let actual_types: Vec<&str> = statements .iter() - .map( - |it| { - if let AstStatement::CastStatement { target, .. } = it { - target - } else { - panic!("no cast") - } - }, - ) + .map(|it| { + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), .. } = it { + target.as_ref() + } else { + panic!("no cast") + } + }) .map(|it| annotations.get_type_or_void(it, &index).get_name()) .collect(); @@ -352,8 +362,8 @@ fn casted_literals_enums_are_annotated_correctly() { let actual_types: Vec<&str> = statements .iter() .map(|it| { - if let AstStatement::CastStatement { target, .. } = it { - target + if let AstStatement::ReferenceExpr { access: ReferenceAccess::Cast(target), .. } = it { + target.as_ref() } else { unreachable!(); } diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-2.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-2.snap index 6fa85f0c55..c16be0c98b 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-2.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-2.snap @@ -1,5 +1,40 @@ --- source: src/resolver/tests/resolve_expressions_tests.rs -expression: "format!(\"{annotation:?}\")" +expression: "format!(\"{annotation:#?}\")" --- -Some(ReplacementAst { statement: CallStatement { operator: Reference { name: "STRING_LESS" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) } }) +Some( + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_LESS", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-3.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-3.snap index 8290eca4ec..4c67352fc0 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-3.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-3.snap @@ -1,5 +1,75 @@ --- source: src/resolver/tests/resolve_expressions_tests.rs -expression: "format!(\"{annotation:?}\")" +expression: "format!(\"{annotation:#?}\")" --- -Some(ReplacementAst { statement: BinaryExpression { operator: Or, left: CallStatement { operator: Reference { name: "STRING_EQUAL" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) }, right: CallStatement { operator: Reference { name: "STRING_LESS" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) } } }) +Some( + ReplacementAst { + statement: BinaryExpression { + operator: Or, + left: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_EQUAL", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + right: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_LESS", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-4.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-4.snap index 9782ce624a..c9007a5327 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-4.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-4.snap @@ -1,5 +1,40 @@ --- source: src/resolver/tests/resolve_expressions_tests.rs -expression: "format!(\"{annotation:?}\")" +expression: "format!(\"{annotation:#?}\")" --- -Some(ReplacementAst { statement: CallStatement { operator: Reference { name: "STRING_GREATER" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) } }) +Some( + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_GREATER", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-5.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-5.snap index 8f4be6adfb..671ea9478c 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-5.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call-5.snap @@ -1,5 +1,75 @@ --- source: src/resolver/tests/resolve_expressions_tests.rs -expression: "format!(\"{annotation:?}\")" +expression: "format!(\"{annotation:#?}\")" --- -Some(ReplacementAst { statement: BinaryExpression { operator: Or, left: CallStatement { operator: Reference { name: "STRING_EQUAL" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) }, right: CallStatement { operator: Reference { name: "STRING_GREATER" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) } } }) +Some( + ReplacementAst { + statement: BinaryExpression { + operator: Or, + left: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_EQUAL", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + right: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_GREATER", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap index fb6140a92d..8e8efc4968 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap @@ -1,5 +1,40 @@ --- source: src/resolver/tests/resolve_expressions_tests.rs -expression: "format!(\"{annotation:?}\")" +expression: "format!(\"{annotation:#?}\")" --- -Some(ReplacementAst { statement: CallStatement { operator: Reference { name: "STRING_EQUAL" }, parameters: Some(ExpressionList { expressions: [Reference { name: "a" }, Reference { name: "b" }] }) } }) +Some( + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_EQUAL", + }, + ), + base: None, + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ], + }, + ), + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap index 543e9c0a89..247b36e907 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap @@ -4,17 +4,32 @@ expression: annotations.get(a_eq_b).unwrap() --- ReplacementAst { statement: CallStatement { - operator: Reference { - name: "STRING_EQUAL", + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "STRING_EQUAL", + }, + ), + base: None, }, parameters: Some( ExpressionList { expressions: [ - Reference { - name: "a", + ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, }, - Reference { - name: "b", + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, }, ], }, diff --git a/src/tests/adr.rs b/src/tests/adr.rs index ea80375959..ca544d65c9 100644 --- a/src/tests/adr.rs +++ b/src/tests/adr.rs @@ -1,8 +1,9 @@ -mod annotated_ast; -mod arrays_adr; /// tests for Architecture Design Records (ADR) +mod annotated_ast_adr; +mod arrays_adr; mod enum_adr; mod pou_adr; +mod reference_expressions_adr; mod strings_adr; mod structs_adr; mod util_macros; diff --git a/src/tests/adr/annotated_ast.rs b/src/tests/adr/annotated_ast_adr.rs similarity index 93% rename from src/tests/adr/annotated_ast.rs rename to src/tests/adr/annotated_ast_adr.rs index 4560fea01e..46a0ab4d88 100644 --- a/src/tests/adr/annotated_ast.rs +++ b/src/tests/adr/annotated_ast_adr.rs @@ -1,3 +1,5 @@ +use plc_ast::ast::{AstStatement, ReferenceAccess}; + use crate::{ index::{ArgumentType, VariableType}, resolver::{AnnotationMap, StatementAnnotation}, @@ -6,7 +8,6 @@ use crate::{ use super::util_macros::{ annotate, deconstruct_assignment, deconstruct_binary_expression, deconstruct_call_statement, - deconstruct_qualified_reference, }; /// # Architecture Design Record: Annotated AST @@ -132,15 +133,28 @@ fn different_types_of_annotations() { ); // Main.in - let segments = deconstruct_qualified_reference!(&statements[3]); - // Main resolves to a Program + let qualified_reference = &statements[3]; + let AstStatement::ReferenceExpr { access: ReferenceAccess::Member(member) ,base: Some(qualifier) , ..} = qualified_reference else {unreachable!()}; + // // Main resolves to a Program assert_eq!( - annotations.get(&segments[0]), + annotations.get(qualifier), Some(&StatementAnnotation::Program { qualified_name: "Main".into() }) ); - //in resolves to the variable in + // in resolves to the member variable + assert_eq!( + annotations.get(member), + Some(&StatementAnnotation::Variable { + qualified_name: "Main.in".into(), + resulting_type: "INT".into(), + constant: false, + is_auto_deref: false, + argument_type: ArgumentType::ByVal(VariableType::Input), + }) + ); + + // the whole Main.in also resolves to the member variable assert_eq!( - annotations.get(&segments[1]), + annotations.get(qualified_reference), Some(&StatementAnnotation::Variable { qualified_name: "Main.in".into(), resulting_type: "INT".into(), @@ -149,8 +163,6 @@ fn different_types_of_annotations() { argument_type: ArgumentType::ByVal(VariableType::Input), }) ); - // the qualified statement gets the annotation of the last segment (in this case "Main.in" of type INT) - assert_eq!(annotations.get(&statements[3]), annotations.get(&segments[1])); } /// The resolver (the component that annotates the AST) not only annnotates the real datatype diff --git a/src/tests/adr/reference_expressions_adr.rs b/src/tests/adr/reference_expressions_adr.rs new file mode 100644 index 0000000000..60281717c4 --- /dev/null +++ b/src/tests/adr/reference_expressions_adr.rs @@ -0,0 +1,269 @@ +use crate::test_utils::tests::parse_and_preprocess; + +/// # Architecture Design Records: Representation of Reference Expressions +/// +/// Reference Experssions are expressions that reference variables, POUs, +/// datatypes, etc. There are traditional references that point to a value of +/// a local (`i`), global (`PI`), or even a member-variable (`point.x`). Furthermore +/// there are reference-expressions that point to an element of an array (`x[3]`), +/// dereference a pointer (`pNext^`) or references that access the address of a variable +/// (`&next`). +/// +/// There is only one AST-Statement-Variant representing all different kinds of references. It +/// contains an access-member which represents the different kinds of accessing an element: +/// ReferenceExpression +/// - Member-Access (accessing a member of a struct, pou, etc.) +/// - Index-Access (accessing an element of an array) +/// - Casting a value +/// - Dereferencing a pointer +/// - accessing the Address of a variable + +/// A flat reference is treated as a Qualified-Reference with no qualifier. +/// This means that a flat reference (a) and a qualified reference (a.b) +/// are represented using the same AST-Structure. One has no qualifier (None), +/// the other one has. +#[test] +fn representation_of_a_flat_reference() { + let (unit, _) = parse_and_preprocess( + " + PROGRAM prg + point; + END_PROGRAM + ", + ); + + let statement = &unit.implementations[0].statements[0]; + // Note that `point` is a Member-AST strcture where base=None + insta::assert_debug_snapshot!(statement, @r###" + ReferenceExpr { + kind: Member( + Identifier { + name: "point", + }, + ), + base: None, + } + "###); +} + +/// A qualified reference makes use of the recursive characteristics of +/// a Member-Access. Note that the sequence of the reference elements +/// (obj, position, x) is actually stored in reverse order in the recursive +/// composite pattern (x -> position -> obj). While the other approach feels more +/// intuitive on first sight, this representation has its benefits when recursively +/// visiting the AST since every element has direct access to elements that define +/// its context (e.g. when visiting `x`, you have full access to `position`). +#[test] +fn representation_of_a_qualified_reference() { + let (unit, _) = parse_and_preprocess( + " + PROGRAM prg + obj.position.x; + END_PROGRAM + ", + ); + + let statement = &unit.implementations[0].statements[0]; + // Note that the expression is a recursive datastructure. + // it is stored backwards! (x -> position -> obj). This representation + // helps during type-resolving, validation and code-generation + insta::assert_debug_snapshot!(statement, @r###" + ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "position", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "obj", + }, + ), + base: None, + }, + ), + }, + ), + } + "###); +} + +/// Accessing an element of an array simply stores the index-statement within the +/// Index-Variant and holds the array-reference in its base. Note that the index-Reference +/// may be a fully fletched Reference-Expression itself. +#[test] +fn representation_of_an_array_expression_reference() { + let (unit, _) = parse_and_preprocess( + " + PROGRAM prg + obj.pos[2]; + END_PROGRAM + ", + ); + + let statement = &unit.implementations[0].statements[0]; + // Note that the root of this expression is an Index-Access with a base-expression (again reversed order) + insta::assert_debug_snapshot!(statement, @r###" + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 2, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "pos", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "obj", + }, + ), + base: None, + }, + ), + }, + ), + } + "###); +} + +/// Address-of and Deref expressions are stateless ReferenceAccess-Variants. They simply indicate +/// the accessing operation and offer the operator as their base. Note that the address-of and +/// deref operation act on the whole (qualified) reference expression, not just on the +/// segment next to it +#[test] +fn representation_of_a_pointer_expression_reference() { + let (unit, _) = parse_and_preprocess( + " + PROGRAM prg + &obj.pos; + obj.pos^; + END_PROGRAM + ", + ); + + let address_of = &unit.implementations[0].statements[0]; + insta::assert_debug_snapshot!(address_of, @r###" + ReferenceExpr { + kind: Address, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "pos", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "obj", + }, + ), + base: None, + }, + ), + }, + ), + } + "###); + + let deref = &unit.implementations[0].statements[1]; + insta::assert_debug_snapshot!(deref, @r###" + ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "pos", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "obj", + }, + ), + base: None, + }, + ), + }, + ), + } + "###); +} + +/// A cast statement is also represented as a ReferenceExpression +/// With its dedicated variant. We can think of it as a if the `#` +/// acts as the delimiter, very much like the dot does for the member +/// access. +#[test] +fn representation_of_an_cast_expression_reference() { + let (unit, _) = parse_and_preprocess( + " + PROGRAM prg + INT#3; + REAL#a; + END_PROGRAM + ", + ); + + let address_of = &unit.implementations[0].statements[0]; + insta::assert_debug_snapshot!(address_of, @r###" + ReferenceExpr { + kind: Cast( + LiteralInteger { + value: 3, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "INT", + }, + ), + base: None, + }, + ), + } + "###); + + let deref = &unit.implementations[0].statements[1]; + insta::assert_debug_snapshot!(deref, @r###" + ReferenceExpr { + kind: Cast( + Identifier { + name: "a", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "REAL", + }, + ), + base: None, + }, + ), + } + "###); +} diff --git a/src/tests/adr/structs_adr.rs b/src/tests/adr/structs_adr.rs index 75b5a1d7b0..540638763e 100644 --- a/src/tests/adr/structs_adr.rs +++ b/src/tests/adr/structs_adr.rs @@ -212,8 +212,8 @@ fn accessing_struct_members() { %x = getelementptr inbounds %Point, %Point* %topLeft, i32 0, i32 0 %bottomRight = getelementptr inbounds %Rect, %Rect* %rect2, i32 0, i32 1 %x1 = getelementptr inbounds %Point, %Point* %bottomRight, i32 0, i32 0 - %load_ = load i16, i16* %x1, align 2 - store i16 %load_, i16* %x, align 2 + %load_x = load i16, i16* %x1, align 2 + store i16 %load_x, i16* %x, align 2 ret void } "###); diff --git a/src/tests/adr/util_macros.rs b/src/tests/adr/util_macros.rs index 08915f8e02..eb767c9f09 100644 --- a/src/tests/adr/util_macros.rs +++ b/src/tests/adr/util_macros.rs @@ -34,17 +34,6 @@ macro_rules! deconstruct_call_statement { } pub(crate) use deconstruct_call_statement; -macro_rules! deconstruct_qualified_reference { - ($src:expr) => {{ - if let plc_ast::ast::AstStatement::QualifiedReference { elements, .. } = &$src { - elements - } else { - unreachable!(); - } - }}; -} -pub(crate) use deconstruct_qualified_reference; - macro_rules! deconstruct_binary_expression { ($src:expr) => {{ if let plc_ast::ast::AstStatement::BinaryExpression { left, right, .. } = &$src { diff --git a/src/typesystem.rs b/src/typesystem.rs index 5bdb9f29ee..8ae6e532d1 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -161,6 +161,11 @@ impl DataType { self.get_type_information().is_array() } + /// returns true if this type is an enum + pub fn is_enum(&self) -> bool { + self.get_type_information().is_enum() + } + pub fn is_vla(&self) -> bool { self.get_type_information().is_vla() } @@ -644,6 +649,17 @@ impl DataTypeInformation { pub fn get_inner_array_type_name(&self) -> Option<&str> { match self { DataTypeInformation::Array { inner_type_name, .. } => Some(inner_type_name), + DataTypeInformation::Struct { + source: StructSource::Internal(InternalType::VariableLengthArray { inner_type_name, .. }), + .. + } => Some(inner_type_name), + _ => None, + } + } + + pub fn get_inner_pointer_type_name(&self) -> Option<&str> { + match self { + DataTypeInformation::Pointer { inner_type_name, .. } => Some(inner_type_name), _ => None, } } diff --git a/src/validation.rs b/src/validation.rs index 1af3bd0866..3bc56b4a18 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -49,14 +49,7 @@ impl<'s, T: AnnotationMap> ValidationContext<'s, T> { } fn find_pou(&self, stmt: &AstStatement) -> Option<&PouIndexEntry> { - match stmt { - AstStatement::Reference { name, .. } => Some(name.as_str()), - AstStatement::QualifiedReference { elements, .. } => { - elements.last().and_then(|it| self.annotations.get_call_name(it)) - } - _ => None, - } - .and_then(|pou_name| { + self.annotations.get_call_name(stmt).and_then(|pou_name| { self.index // check if this is an instance of a function block and get the type name .find_callable_instance_variable(self.qualifier, &[pou_name]) diff --git a/src/validation/statement.rs b/src/validation/statement.rs index bd47f669dc..ebca650c21 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, mem::discriminant}; use plc_ast::{ - ast::{flatten_expression_list, AstStatement, DirectAccessType, Operator, SourceRange}, + ast::{flatten_expression_list, AstStatement, DirectAccessType, Operator, ReferenceAccess, SourceRange}, control_statements::{AstControlStatement, ConditionalBlock}, literals::{Array, AstLiteral, StringValue}, }; @@ -60,27 +60,19 @@ pub fn visit_statement( AstStatement::MultipliedStatement { element, .. } => { visit_statement(validator, element, context); } - AstStatement::QualifiedReference { elements, .. } => { - elements.iter().for_each(|element| visit_statement(validator, element, context)); - validate_qualified_reference(validator, elements, context); - } - AstStatement::Reference { name, location, .. } => { - validate_reference(validator, statement, name, location, context); - } - AstStatement::ArrayAccess { reference, access, .. } => { - visit_all_statements!(validator, context, reference, access); - visit_array_access(validator, reference, access, context); + AstStatement::ReferenceExpr { access, base, .. } => { + if let Some(base) = base { + visit_statement(validator, base, context); + } + + validate_reference_expression(access, validator, context, statement, base); } - // AstStatement::PointerAccess { reference, id } => (), - // AstStatement::DirectAccess { access, index, location, id } => (), - // AstStatement::HardwareAccess { direction, access, address, location, id } => (), AstStatement::BinaryExpression { operator, left, right, .. } => { visit_all_statements!(validator, context, left, right); visit_binary_expression(validator, statement, operator, left, right, context); } - AstStatement::UnaryExpression { operator, value, location, .. } => { + AstStatement::UnaryExpression { value, .. } => { visit_statement(validator, value, context); - validate_unary_expression(validator, operator, value, location); } AstStatement::ExpressionList { expressions, .. } => { expressions.iter().for_each(|element| visit_statement(validator, element, context)) @@ -123,6 +115,131 @@ pub fn visit_statement( validate_type_nature(validator, statement, context); } +fn validate_reference_expression( + access: &ReferenceAccess, + validator: &mut Validator, + context: &ValidationContext, + statement: &AstStatement, + base: &Option>, +) { + match access { + ReferenceAccess::Member(m) => { + visit_statement(validator, m.as_ref(), context); + + if let Some(reference_name) = statement.get_flat_reference_name() { + validate_reference( + validator, + statement, + base.as_deref(), + reference_name, + &m.get_location(), + context, + ); + } + validate_direct_access(m, base.as_deref(), context, validator); + } + ReferenceAccess::Index(i) => { + if let Some(base) = base { + visit_array_access(validator, base, i, context) + } else { + validator.diagnostics.push(Diagnostic::invalid_operation( + "Index-Access requires an array-value.", + statement.get_location(), + )); + } + } + ReferenceAccess::Cast(c) => { + visit_statement(validator, c.as_ref(), context); + + // see if we try to cast a literal + if let ( + AstStatement::Literal { kind: literal, .. }, + Some(StatementAnnotation::Type { type_name }), + ) = (c.as_ref(), base.as_ref().and_then(|it| context.annotations.get(it))) + { + validate_cast_literal( + validator, + literal, + c.as_ref(), + type_name.as_str(), + &statement.get_location(), + context, + ); + } + } + ReferenceAccess::Deref => { + if base.is_none() { + validator.diagnostics.push(Diagnostic::invalid_operation( + "Dereferencing requires a pointer-value.", + statement.get_location(), + )); + } + } + ReferenceAccess::Address => { + if let Some(base) = base { + validate_address_of_expression(validator, base, statement.get_location(), context); + } else { + validator.diagnostics.push(Diagnostic::invalid_operation( + "Address-of requires a value.", + statement.get_location(), + )); + } + } + } +} + +fn validate_address_of_expression( + validator: &mut Validator, + target: &AstStatement, + location: SourceRange, + context: &ValidationContext, +) { + let a = context.annotations.get(target); + //TODO: resolver should also annotate information whether this results in an LValue or RValue + // array-access results in a value, but it is an LValue :-( + if !matches!(a, Some(StatementAnnotation::Variable { .. })) + && !matches!(target, AstStatement::ReferenceExpr { access: ReferenceAccess::Index(_), .. }) + { + validator.push_diagnostic(Diagnostic::invalid_operation("Invalid address-of operation", location)); + } +} + +fn validate_direct_access( + m: &AstStatement, + base: Option<&AstStatement>, + context: &ValidationContext, + validator: &mut Validator, +) { + if let (AstStatement::DirectAccess { access, index, .. }, Some(base_annotation)) = ( + m, + // FIXME: should we consider the hint if one is available? + base.and_then(|base| context.annotations.get(base)), + ) { + let base_type = context + .annotations + .get_type_for_annotation(context.index, base_annotation) + .unwrap_or(context.index.get_void_type()) + .get_type_information(); + if base_type.is_int() { + if !helper::is_compatible(access, base_type, context.index) { + validator.push_diagnostic(Diagnostic::incompatible_directaccess( + &format!("{access:?}"), + access.get_bit_width(), + m.get_location(), + )) + } else { + validate_access_index(validator, context, index, access, base_type, &m.get_location()); + } + } else { + validator.push_diagnostic(Diagnostic::incompatible_directaccess( + &format!("{access:?}"), + access.get_bit_width(), + m.get_location(), + )) + } + } +} + fn validate_control_statement( validator: &mut Validator, control_statement: &AstControlStatement, @@ -154,7 +271,7 @@ fn validate_control_statement( } /// validates a literal statement with a dedicated type-prefix (e.g. INT#3) -/// checks whether the type-prefix is valid +/// checks whether the type-prefix is valid and if the target is a literal fn validate_cast_literal( // TODO: i feel like literal is misleading here. can be a reference aswell (INT#x) validator: &mut Validator, @@ -212,54 +329,6 @@ fn validate_cast_literal( } } -fn validate_qualified_reference( - validator: &mut Validator, - elements: &[AstStatement], - context: &ValidationContext, -) { - let mut iter = elements.iter().rev(); - let access = iter.next().zip(iter.next()); - if let Some((AstStatement::DirectAccess { access, index, location, .. }, reference)) = access { - let target_type = - context.annotations.get_type_or_void(reference, context.index).get_type_information(); - if target_type.is_int() { - if !helper::is_compatible(access, target_type, context.index) { - validator.push_diagnostic(Diagnostic::incompatible_directaccess( - &format!("{access:?}"), - access.get_bit_width(), - location.clone(), - )) - } else { - validate_access_index(validator, context, index, access, target_type, location); - } - } else { - validator.push_diagnostic(Diagnostic::incompatible_directaccess( - &format!("{access:?}"), - access.get_bit_width(), - location.clone(), - )) - } - } else { - let Some((reference, accessed)) = access else { - return - }; - if !context.annotations.has_type_annotation(reference) { - let AstStatement::Reference { name, location, .. } = reference else { - return - }; - - if context.annotations.get_type(accessed, context.index).is_none() { - return; - } - - validator.push_diagnostic(Diagnostic::ImprovementSuggestion { - message: format!("If you meant to access a bit, use %X{name} instead.",), - range: vec![location.clone()], - }); - } - } -} - fn validate_access_index( validator: &mut Validator, context: &ValidationContext, @@ -284,7 +353,7 @@ fn validate_access_index( )) } } - AstStatement::Reference { .. } => { + AstStatement::ReferenceExpr { .. } => { let ref_type = context.annotations.get_type_or_void(access_index, context.index); if !ref_type.get_type_information().is_int() { validator.push_diagnostic(Diagnostic::incompatible_directaccess_variable( @@ -300,6 +369,7 @@ fn validate_access_index( fn validate_reference( validator: &mut Validator, statement: &AstStatement, + base: Option<&AstStatement>, ref_name: &str, location: &SourceRange, context: &ValidationContext, @@ -307,6 +377,25 @@ fn validate_reference( // unresolved reference if !context.annotations.has_type_annotation(statement) { validator.push_diagnostic(Diagnostic::unresolved_reference(ref_name, location.clone())); + + // was this meant as a direct access? + // TODO: find a way to solve this without re-resolving this name + if let Some(alternative_target_type) = + context.index.find_variable(context.qualifier, &[ref_name]).and_then(|alternative_target| { + context.index.find_effective_type_by_name(alternative_target.get_type_name()) + }) + { + if base.is_some() && (alternative_target_type.is_numerical() || alternative_target_type.is_enum()) + { + // we accessed a member that does not exist, but we could find a global/local variable that fits + validator.push_diagnostic(Diagnostic::ImprovementSuggestion { + message: format!( + "If you meant to directly access a bit/byte/word/.., use %X/%B/%W{ref_name} instead.", + ), + range: vec![location.clone()], + }); + } + } } else if let Some(StatementAnnotation::Variable { qualified_name, argument_type, .. }) = context.annotations.get(statement) { @@ -517,26 +606,6 @@ fn compare_function_exists( false } -fn validate_unary_expression( - validator: &mut Validator, - operator: &Operator, - value: &AstStatement, - location: &SourceRange, -) { - if operator == &Operator::Address { - match value { - AstStatement::Reference { .. } - | AstStatement::QualifiedReference { .. } - | AstStatement::ArrayAccess { .. } => (), - - _ => validator.push_diagnostic(Diagnostic::invalid_operation( - "Invalid address-of operation", - location.to_owned(), - )), - } - } -} - /// Validates if an argument can be passed to a function with [`VariableType::Output`] and /// [`VariableType::InOut`] parameter types by checking if the argument is a reference (e.g. `foo(x)`) or /// an assignment (e.g. `foo(x := y)`, `foo(x => y)`). If neither is the case a diagnostic is generated. @@ -636,7 +705,7 @@ fn validate_assignment( location.clone(), )); } else if !matches!(right, AstStatement::Literal { .. }) { - // FIXME: See https://github.com/PLC-lang/rusty/issues/857 + // TODO: See https://github.com/PLC-lang/rusty/issues/857 // validate_assignment_type_sizes(validator, left_type, right_type, location, context) } } diff --git a/src/validation/tests/literals_validation_tests.rs b/src/validation/tests/literals_validation_tests.rs index f0562022f1..7d2346df3f 100644 --- a/src/validation/tests/literals_validation_tests.rs +++ b/src/validation/tests/literals_validation_tests.rs @@ -100,7 +100,9 @@ fn literal_cast_with_non_literal() { let diagnostics = parse_and_validate( "PROGRAM exp INT#[x]; - END_PROGRAM", + END_PROGRAM + + VAR_GLOBAL x : INT; END_VAR", ); assert_validation_snapshot!(&diagnostics); } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__array_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__array_assignment_validation.snap index 8b806e209a..62ab4ca031 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__array_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__array_assignment_validation.snap @@ -16,7 +16,7 @@ SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", ra SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 1477..1503 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'INT'", range: [SourceRange { range: 1520..1544 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'INT'", range: [SourceRange { range: 1561..1587 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 1647..1677 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 1647..1678 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'DINT' to '__main_v_arr_int_3'", range: [SourceRange { range: 1695..1716 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign '__main_v_arr_int_3' to 'DINT'", range: [SourceRange { range: 1733..1754 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__bit_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__bit_assignment_validation.snap index 8e83840d2d..7bc8f03881 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__bit_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__bit_assignment_validation.snap @@ -7,6 +7,6 @@ SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", r SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", range: [SourceRange { range: 884..902 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'BYTE'", range: [SourceRange { range: 919..935 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'BYTE'", range: [SourceRange { range: 952..970 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", range: [SourceRange { range: 1089..1111 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", range: [SourceRange { range: 1168..1194 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", range: [SourceRange { range: 1089..1112 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'BYTE'", range: [SourceRange { range: 1168..1195 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__char_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__char_assignment_validation.snap index d47f51d058..7dc10d14a1 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__char_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__char_assignment_validation.snap @@ -1,6 +1,6 @@ --- source: src/validation/tests/assignment_validation_tests.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "Invalid assignment: cannot assign 'LREAL' to 'CHAR'", range: [SourceRange { range: 571..588 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'REAL' to 'CHAR'", range: [SourceRange { range: 605..623 }], err_no: var__invalid_assignment } @@ -23,8 +23,8 @@ SyntaxError { message: "Invalid assignment: cannot assign 'WCHAR' to 'CHAR'", ra SyntaxError { message: "Invalid assignment: cannot assign 'WCHAR' to 'CHAR'", range: [SourceRange { range: 1293..1312 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'TIME_OF_DAY' to 'CHAR'", range: [SourceRange { range: 1329..1344 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'TIME_OF_DAY' to 'CHAR'", range: [SourceRange { range: 1361..1383 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'CHAR'", range: [SourceRange { range: 1400..1419 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'CHAR'", range: [SourceRange { range: 1437..1459 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'CHAR'", range: [SourceRange { range: 1477..1500 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'CHAR'", range: [SourceRange { range: 1518..1544 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'CHAR'", range: [SourceRange { range: 1400..1420 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'CHAR'", range: [SourceRange { range: 1437..1460 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'CHAR'", range: [SourceRange { range: 1477..1501 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'CHAR'", range: [SourceRange { range: 1518..1545 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__date_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__date_assignment_validation.snap index 46a3bfe399..9773758082 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__date_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__date_assignment_validation.snap @@ -1,12 +1,12 @@ --- source: src/validation/tests/assignment_validation_tests.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 812..830 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 847..872 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 889..907 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'DATE'", range: [SourceRange { range: 924..940 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'DATE'", range: [SourceRange { range: 957..975 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 1094..1116 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 1173..1199 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 1094..1117 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DATE'", range: [SourceRange { range: 1173..1200 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__duration_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__duration_assignment_validation.snap index 2b2ba54ebc..b810c0a646 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__duration_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__duration_assignment_validation.snap @@ -1,12 +1,12 @@ --- source: src/validation/tests/assignment_validation_tests.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 816..834 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 851..876 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 893..911 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'TIME'", range: [SourceRange { range: 928..944 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'TIME'", range: [SourceRange { range: 961..979 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 1098..1120 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 1177..1203 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 1098..1121 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'TIME'", range: [SourceRange { range: 1177..1204 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__int_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__int_assignment_validation.snap index 4a172ecfc3..5652b8c180 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__int_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__int_assignment_validation.snap @@ -7,13 +7,13 @@ SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", range: [SourceRange { range: 959..978 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'UDINT'", range: [SourceRange { range: 995..1012 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'UDINT'", range: [SourceRange { range: 1029..1048 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", range: [SourceRange { range: 1170..1193 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", range: [SourceRange { range: 1251..1278 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", range: [SourceRange { range: 1170..1194 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'UDINT'", range: [SourceRange { range: 1251..1279 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 1646..1664 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 1681..1706 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 1723..1741 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'DINT'", range: [SourceRange { range: 1758..1774 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'DINT'", range: [SourceRange { range: 1791..1809 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 1928..1950 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 2007..2033 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 1928..1951 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'DINT'", range: [SourceRange { range: 2007..2034 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap index 3d670a2a12..77371bb553 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap @@ -4,5 +4,5 @@ expression: res --- SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 973..995 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'INT'", range: [SourceRange { range: 1077..1097 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 1227..1257 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'INT'", range: [SourceRange { range: 1227..1258 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__real_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__real_assignment_validation.snap index ee2bff1351..c9993ddda7 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__real_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__real_assignment_validation.snap @@ -7,6 +7,6 @@ SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", r SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", range: [SourceRange { range: 893..911 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'REAL'", range: [SourceRange { range: 928..944 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'REAL'", range: [SourceRange { range: 961..979 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", range: [SourceRange { range: 1098..1120 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", range: [SourceRange { range: 1177..1203 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", range: [SourceRange { range: 1098..1121 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'STRING' to 'REAL'", range: [SourceRange { range: 1177..1204 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__string_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__string_assignment_validation.snap index 7d452a4c0e..c3ad23d2dc 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__string_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__string_assignment_validation.snap @@ -1,6 +1,6 @@ --- source: src/validation/tests/assignment_validation_tests.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "Invalid assignment: cannot assign 'LREAL' to 'STRING'", range: [SourceRange { range: 548..567 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'REAL' to 'STRING'", range: [SourceRange { range: 584..604 }], err_no: var__invalid_assignment } @@ -19,6 +19,6 @@ SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'STRING'", r SyntaxError { message: "Invalid assignment: cannot assign 'CHAR' to 'STRING'", range: [SourceRange { range: 1185..1205 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'TIME_OF_DAY' to 'STRING'", range: [SourceRange { range: 1222..1239 }], err_no: var__invalid_assignment } SyntaxError { message: "Invalid assignment: cannot assign 'TIME_OF_DAY' to 'STRING'", range: [SourceRange { range: 1256..1280 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'STRING'", range: [SourceRange { range: 1297..1318 }], err_no: var__invalid_assignment } -SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'STRING'", range: [SourceRange { range: 1376..1401 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'STRING'", range: [SourceRange { range: 1297..1319 }], err_no: var__invalid_assignment } +SyntaxError { message: "Invalid assignment: cannot assign 'INT' to 'STRING'", range: [SourceRange { range: 1376..1402 }], err_no: var__invalid_assignment } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resole_struct_member_access.snap b/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resole_struct_member_access.snap index 953c96446a..f98f9c28b9 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resole_struct_member_access.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resole_struct_member_access.snap @@ -3,15 +3,9 @@ source: src/validation/tests/reference_resolve_tests.rs expression: res --- SyntaxError { message: "Could not resolve reference to field10", range: [SourceRange { range: 694..701 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xfield10 instead.", range: [SourceRange { range: 694..701 }] } SyntaxError { message: "Could not resolve reference to field20", range: [SourceRange { range: 721..728 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xfield20 instead.", range: [SourceRange { range: 721..728 }] } SyntaxError { message: "Could not resolve reference to field30", range: [SourceRange { range: 748..755 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xfield30 instead.", range: [SourceRange { range: 748..755 }] } SyntaxError { message: "Could not resolve reference to subfield10", range: [SourceRange { range: 955..965 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xsubfield10 instead.", range: [SourceRange { range: 955..965 }] } SyntaxError { message: "Could not resolve reference to subfield20", range: [SourceRange { range: 989..999 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xsubfield20 instead.", range: [SourceRange { range: 989..999 }] } SyntaxError { message: "Could not resolve reference to subfield30", range: [SourceRange { range: 1023..1033 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xsubfield30 instead.", range: [SourceRange { range: 1023..1033 }] } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__bit_access_with_incorrect_operator_causes_warning.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__bit_access_with_incorrect_operator_causes_warning.snap index a0aa50e9a1..c896f309d7 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__bit_access_with_incorrect_operator_causes_warning.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__bit_access_with_incorrect_operator_causes_warning.snap @@ -3,5 +3,5 @@ source: src/validation/tests/statement_validation_tests.rs expression: res --- SyntaxError { message: "Could not resolve reference to n1", range: [SourceRange { range: 287..289 }], err_no: reference__unresolved } -ImprovementSuggestion { message: "If you meant to access a bit, use %Xn1 instead.", range: [SourceRange { range: 287..289 }] } +ImprovementSuggestion { message: "If you meant to directly access a bit/byte/word/.., use %X/%B/%Wn1 instead.", range: [SourceRange { range: 287..289 }] } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__invalid_cast_statement_causes_error.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__invalid_cast_statement_causes_error.snap new file mode 100644 index 0000000000..77916a0f38 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__invalid_cast_statement_causes_error.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/statement_validation_tests.rs +expression: res +--- +SyntaxError { message: "Could not resolve reference to var1", range: [SourceRange { range: 141..145 }], err_no: reference__unresolved } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__switch_case_invalid_case_conditions.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__switch_case_invalid_case_conditions.snap index 82c6703ddb..6ef5891765 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__switch_case_invalid_case_conditions.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__switch_case_invalid_case_conditions.snap @@ -1,8 +1,8 @@ --- source: src/validation/tests/statement_validation_tests.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "Invalid case condition!", range: [SourceRange { range: 120..126 }], err_no: case__case_condition_outside_case_statement } -SyntaxError { message: "Cannot resolve constant: CallStatement {\n operator: Reference {\n name: \"foo\",\n },\n parameters: None,\n}. Non constant variables are not supported in case conditions", range: [SourceRange { range: 120..126 }], err_no: type__invalid_type } +SyntaxError { message: "Cannot resolve constant: CallStatement {\n operator: ReferenceExpr {\n kind: Member(\n Identifier {\n name: \"foo\",\n },\n ),\n base: None,\n },\n parameters: None,\n}. Non constant variables are not supported in case conditions", range: [SourceRange { range: 120..126 }], err_no: type__invalid_type } SyntaxError { message: "Invalid case condition!", range: [SourceRange { range: 146..154 }], err_no: case__case_condition_outside_case_statement } diff --git a/src/validation/tests/statement_validation_tests.rs b/src/validation/tests/statement_validation_tests.rs index 1c087144a6..3ea334552e 100644 --- a/src/validation/tests/statement_validation_tests.rs +++ b/src/validation/tests/statement_validation_tests.rs @@ -1323,3 +1323,28 @@ fn bit_access_with_incorrect_operator_causes_warning() { assert_validation_snapshot!(diagnostics); } + +#[test] +fn invalid_cast_statement_causes_error() { + let diagnostics = parse_and_validate( + "PROGRAM mainProg + VAR_INPUT + s : STRUCT1; + i : INT; + END_VAR + + i := INT#s.var1; // illegal, var1 cannot be found in INT + i := INT#i; // ok + i := INT#4; // ok + END_PROGRAM + + TYPE STRUCT1 : + STRUCT + var1 : DWORD; + END_STRUCT + END_TYPE + ", + ); + + assert_validation_snapshot!(diagnostics); +}