From cf34cf60e0ea434d2f89097138ae2202161c217c Mon Sep 17 00:00:00 2001 From: Michael <78988079+mhasel@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:12:41 +0100 Subject: [PATCH] feat(builtins): Symbols as builtins (#1012) * variadic add builtin function * index on builtin-arithmetics: * move arith func correctness tests in own folder * add tests, fix sized variadic test naming conflict * - adds comparison operators as builtin-functions. - annotates call-statements with `ReplacementAst` statements - temporary name-conflict fix for string-comparison overloads * fix comparison function logic. (GT(a, b, c) == a > b && b > c && c > d) add test to make sure AST is the same whether using symbols or calls * fix function overloads for string comparison * refactor part of `visit_call_statement` moved to its own function so it can be redirected to * refactor builtin annotations to reduce necessary ast-replacement checks during codegen. fix type-hints for nested binary-expr. with builtin-calls * fix st-header formatting * fix string header signature mismatch * param count validation --- Cargo.lock | 38 +- compiler/plc_driver/src/runner.rs | 11 + .../date_time_numeric_functions.st | 224 +++---- libs/stdlib/iec61131-st/string_functions.st | 599 ++++++++---------- libs/stdlib/src/string_functions.rs | 212 +------ libs/stdlib/tests/common/mod.rs | 18 +- .../date_time_numeric_functions_tests.rs | 52 ++ src/builtins.rs | 409 +++++++++++- .../generators/expression_generator.rs | 8 + .../tests/compare_instructions_tests.rs | 79 +++ src/codegen/tests/expression_tests.rs | 206 ++++++ ...uctions_tests__compare_datetime_types.snap | 44 ++ ...uction_functions_with_different_types.snap | 128 ++++ ...__expression_tests__builtin_add_float.snap | 31 + ...s__expression_tests__builtin_add_ints.snap | 36 ++ ...__expression_tests__builtin_add_mixed.snap | 32 + ...__expression_tests__builtin_div_float.snap | 23 + ...s__expression_tests__builtin_div_ints.snap | 23 + ...__expression_tests__builtin_div_mixed.snap | 24 + ...__expression_tests__builtin_mul_float.snap | 31 + ...s__expression_tests__builtin_mul_ints.snap | 36 ++ ...__expression_tests__builtin_mul_mixed.snap | 32 + ...__expression_tests__builtin_sub_float.snap | 23 + ...s__expression_tests__builtin_sub_ints.snap | 23 + ...__expression_tests__builtin_sub_mixed.snap | 24 + src/resolver.rs | 238 +++---- .../tests/resolve_expressions_tests.rs | 152 +++++ src/resolver/tests/resolve_generic_calls.rs | 15 +- ...ns_tests__builtin_add_replacement_ast.snap | 49 ++ ...ons_tests__builtin_eq_replacement_ast.snap | 69 ++ ...ons_tests__builtin_ge_replacement_ast.snap | 69 ++ ...ons_tests__builtin_gt_replacement_ast.snap | 69 ++ ...ons_tests__builtin_le_replacement_ast.snap | 69 ++ ...ons_tests__builtin_lt_replacement_ast.snap | 69 ++ ...ons_tests__builtin_ne_replacement_ast.snap | 69 ++ ...tion_sharing_a_datatype_name_resolves.snap | 19 + src/validation/array.rs | 28 +- src/validation/statement.rs | 12 +- src/validation/tests.rs | 1 + .../tests/builtin_validation_tests.rs | 105 +++ ...ltins_called_with_invalid_param_count.snap | 10 + ...ltins_called_with_invalid_param_count.snap | 8 + .../arithmetic_functions/addition.rs | 51 ++ .../arithmetic_functions/division.rs | 58 ++ .../arithmetic_functions/multiplication.rs | 53 ++ .../arithmetic_functions/substraction.rs | 50 ++ .../correctness/comparison_functions/equal.rs | 102 +++ .../comparison_functions/greater_than.rs | 102 +++ .../greater_than_or_equal.rs | 102 +++ .../comparison_functions/less_than.rs | 102 +++ .../less_than_or_equal.rs | 102 +++ .../comparison_functions/not_equal.rs | 102 +++ tests/correctness/expressions.rs | 36 ++ tests/correctness/external_functions.rs | 8 +- tests/correctness/math_operators/addition.rs | 3 +- tests/tests.rs | 16 +- 56 files changed, 3462 insertions(+), 842 deletions(-) create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_datetime_types.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_instruction_functions_with_different_types.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_float.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_ints.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_mixed.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_float.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_ints.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_mixed.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_float.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_ints.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_mixed.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_float.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_ints.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_mixed.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap create mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_generic_calls__generic_function_sharing_a_datatype_name_resolves.snap create mode 100644 src/validation/tests/builtin_validation_tests.rs create mode 100644 src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap create mode 100644 src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap create mode 100644 tests/correctness/arithmetic_functions/addition.rs create mode 100644 tests/correctness/arithmetic_functions/division.rs create mode 100644 tests/correctness/arithmetic_functions/multiplication.rs create mode 100644 tests/correctness/arithmetic_functions/substraction.rs create mode 100644 tests/correctness/comparison_functions/equal.rs create mode 100644 tests/correctness/comparison_functions/greater_than.rs create mode 100644 tests/correctness/comparison_functions/greater_than_or_equal.rs create mode 100644 tests/correctness/comparison_functions/less_than.rs create mode 100644 tests/correctness/comparison_functions/less_than_or_equal.rs create mode 100644 tests/correctness/comparison_functions/not_equal.rs diff --git a/Cargo.lock b/Cargo.lock index 8e252c885d..9ac7b9a128 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,9 +342,9 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecount" -version = "0.6.4" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" @@ -638,9 +638,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "diff" @@ -1300,9 +1303,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -1996,6 +1999,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2876,11 +2885,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", + "powerfmt", "serde", "time-core", "time-macros", @@ -2918,9 +2928,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -2934,9 +2944,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -3076,9 +3086,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" [[package]] name = "value-bag" diff --git a/compiler/plc_driver/src/runner.rs b/compiler/plc_driver/src/runner.rs index 88a2876827..4a20a18d4d 100644 --- a/compiler/plc_driver/src/runner.rs +++ b/compiler/plc_driver/src/runner.rs @@ -50,3 +50,14 @@ pub fn compile_and_run(source: S, params: &mut T) -> U { module.print_to_stderr(); module.run::("main", params) } + +/// +/// A Convenience method to compile and then run the given source +/// without external parameters +/// +pub fn compile_and_run_no_params(source: S) -> U { + let context: CodegenContext = CodegenContext::create(); + let module = compile(&context, source); + module.print_to_stderr(); + module.run_no_param::("main") +} diff --git a/libs/stdlib/iec61131-st/date_time_numeric_functions.st b/libs/stdlib/iec61131-st/date_time_numeric_functions.st index 9dc5586280..89a52d0b2f 100644 --- a/libs/stdlib/iec61131-st/date_time_numeric_functions.st +++ b/libs/stdlib/iec61131-st/date_time_numeric_functions.st @@ -1,20 +1,21 @@ (******************** * * This operator returns the value of adding up the operands. +* It overloads the variadic builtin implementation of ADD, which is impemented for ANY_NUM * *********************) -FUNCTION ADD : T1 +FUNCTION ADD < T1: ANY, T2: ANY >: T1 VAR_INPUT - IN1 : T1; - IN2 : T2; + IN1: T1; + IN2: T2; END_VAR END_FUNCTION (* Specialized implementation of ADD for TIME *) -FUNCTION ADD__TIME__TIME : TIME +FUNCTION ADD__TIME__TIME: TIME VAR_INPUT - IN1 : TIME; - IN2 : TIME; + IN1: TIME; + IN2: TIME; END_VAR ADD__TIME__TIME := ADD_TIME(IN1, IN2); END_FUNCTION @@ -25,10 +26,10 @@ END_FUNCTION * *********************) {external} -FUNCTION ADD_TIME : TIME +FUNCTION ADD_TIME: TIME VAR_INPUT - IN1 : TIME; - IN2 : TIME; + IN1: TIME; + IN2: TIME; END_VAR END_FUNCTION @@ -37,19 +38,19 @@ END_FUNCTION * This operator returns the value of adding up two LTIME operands. * *********************) -FUNCTION ADD_LTIME : LTIME +FUNCTION ADD_LTIME: LTIME VAR_INPUT - IN1 : LTIME; - IN2 : LTIME; + IN1: LTIME; + IN2: LTIME; END_VAR ADD_LTIME := ADD_TIME(IN1, IN2); END_FUNCTION (* Specialized implementation of ADD for TOD *) -FUNCTION ADD__TIME_OF_DAY__TIME : TOD +FUNCTION ADD__TIME_OF_DAY__TIME: TOD VAR_INPUT - IN1 : TOD; - IN2 : TIME; + IN1: TOD; + IN2: TIME; END_VAR ADD__TIME_OF_DAY__TIME := ADD_TOD_TIME(IN1, IN2); END_FUNCTION @@ -61,10 +62,10 @@ END_FUNCTION * *********************) {external} -FUNCTION ADD_TOD_TIME : TOD +FUNCTION ADD_TOD_TIME: TOD VAR_INPUT - IN1 : TOD; - IN2 : TIME; + IN1: TOD; + IN2: TIME; END_VAR END_FUNCTION @@ -74,19 +75,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION ADD_LTOD_LTIME : LTOD +FUNCTION ADD_LTOD_LTIME: LTOD VAR_INPUT - IN1 : LTOD; - IN2 : LTIME; + IN1: LTOD; + IN2: LTIME; END_VAR ADD_LTOD_LTIME := ADD_TOD_TIME(IN1, IN2); END_FUNCTION (* Specialized implementation of ADD for DT *) -FUNCTION ADD__DATE_AND_TIME__TIME : DT +FUNCTION ADD__DATE_AND_TIME__TIME: DT VAR_INPUT - IN1 : DT; - IN2 : TIME; + IN1: DT; + IN2: TIME; END_VAR ADD__DATE_AND_TIME__TIME := ADD_DT_TIME(IN1, IN2); END_FUNCTION @@ -98,10 +99,10 @@ END_FUNCTION * *********************) {external} -FUNCTION ADD_DT_TIME : DT +FUNCTION ADD_DT_TIME: DT VAR_INPUT - IN1 : DT; - IN2 : TIME; + IN1: DT; + IN2: TIME; END_VAR END_FUNCTION @@ -111,31 +112,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION ADD_LDT_LTIME : LDT +FUNCTION ADD_LDT_LTIME: LDT VAR_INPUT - IN1 : LDT; - IN2 : LTIME; + IN1: LDT; + IN2: LTIME; END_VAR ADD_LDT_LTIME := ADD_DT_TIME(IN1, IN2); END_FUNCTION -(******************** -* -* This operator produces the subtraction of the operands. -* -*********************) -FUNCTION SUB : T1 -VAR_INPUT - IN1 : T1; - IN2 : T2; -END_VAR -END_FUNCTION - (* Specialized implementation of SUB for TIME *) -FUNCTION SUB__TIME__TIME : TIME +FUNCTION SUB__TIME__TIME: TIME VAR_INPUT - IN1 : TIME; - IN2 : TIME; + IN1: TIME; + IN2: TIME; END_VAR SUB__TIME__TIME := SUB_TIME(IN1, IN2); END_FUNCTION @@ -146,10 +135,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_TIME : TIME +FUNCTION SUB_TIME: TIME VAR_INPUT - IN1 : TIME; - IN2 : TIME; + IN1: TIME; + IN2: TIME; END_VAR END_FUNCTION @@ -158,19 +147,19 @@ END_FUNCTION * This operator produces the subtraction of two LTIME operands * *********************) -FUNCTION SUB_LTIME : LTIME +FUNCTION SUB_LTIME: LTIME VAR_INPUT - IN1 : LTIME; - IN2 : LTIME; + IN1: LTIME; + IN2: LTIME; END_VAR SUB_LTIME := SUB_TIME(IN1, IN2); END_FUNCTION (* Specialized implementation of SUB for DATE *) -FUNCTION SUB__DATE__DATE : TIME +FUNCTION SUB__DATE__DATE: TIME VAR_INPUT - IN1 : DATE; - IN2 : DATE; + IN1: DATE; + IN2: DATE; END_VAR SUB__DATE__DATE := SUB_DATE_DATE(IN1, IN2); END_FUNCTION @@ -182,10 +171,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_DATE_DATE : TIME +FUNCTION SUB_DATE_DATE: TIME VAR_INPUT - IN1 : DATE; - IN2 : DATE; + IN1: DATE; + IN2: DATE; END_VAR END_FUNCTION @@ -195,19 +184,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION SUB_LDATE_LDATE : LTIME +FUNCTION SUB_LDATE_LDATE: LTIME VAR_INPUT - IN1 : LDATE; - IN2 : LDATE; + IN1: LDATE; + IN2: LDATE; END_VAR SUB_LDATE_LDATE := SUB_DATE_DATE(IN1, IN2); END_FUNCTION (* Specialized implementation of SUB for TOD and TIME *) -FUNCTION SUB__TIME_OF_DAY__TIME : TOD +FUNCTION SUB__TIME_OF_DAY__TIME: TOD VAR_INPUT - IN1 : TOD; - IN2 : TIME; + IN1: TOD; + IN2: TIME; END_VAR SUB__TIME_OF_DAY__TIME := SUB_TOD_TIME(IN1, IN2); END_FUNCTION @@ -219,10 +208,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_TOD_TIME : TOD +FUNCTION SUB_TOD_TIME: TOD VAR_INPUT - IN1 : TOD; - IN2 : TIME; + IN1: TOD; + IN2: TIME; END_VAR END_FUNCTION @@ -232,19 +221,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION SUB_LTOD_LTIME : LTOD +FUNCTION SUB_LTOD_LTIME: LTOD VAR_INPUT - IN1 : LTOD; - IN2 : LTIME; + IN1: LTOD; + IN2: LTIME; END_VAR SUB_LTOD_LTIME := SUB_TOD_TIME(IN1, IN2); END_FUNCTION (* Specialized implementation of SUB for TOD *) -FUNCTION SUB__TIME_OF_DAY__TIME_OF_DAY : TIME +FUNCTION SUB__TIME_OF_DAY__TIME_OF_DAY: TIME VAR_INPUT - IN1 : TOD; - IN2 : TOD; + IN1: TOD; + IN2: TOD; END_VAR SUB__TIME_OF_DAY__TIME_OF_DAY := SUB_TOD_TOD(IN1, IN2); END_FUNCTION @@ -256,10 +245,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_TOD_TOD : TIME +FUNCTION SUB_TOD_TOD: TIME VAR_INPUT - IN1 : TOD; - IN2 : TOD; + IN1: TOD; + IN2: TOD; END_VAR END_FUNCTION @@ -269,19 +258,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION SUB_LTOD_LTOD : LTIME +FUNCTION SUB_LTOD_LTOD: LTIME VAR_INPUT - IN1 : LTOD; - IN2 : LTOD; + IN1: LTOD; + IN2: LTOD; END_VAR SUB_LTOD_LTOD := SUB_TOD_TOD(IN1, IN2); END_FUNCTION (* Specialized implementation of SUB for DT and TIME *) -FUNCTION SUB__DATE_AND_TIME__TIME : DT +FUNCTION SUB__DATE_AND_TIME__TIME: DT VAR_INPUT - IN1 : DT; - IN2 : TIME; + IN1: DT; + IN2: TIME; END_VAR SUB__DATE_AND_TIME__TIME := SUB_DT_TIME(IN1, IN2); END_FUNCTION @@ -293,10 +282,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_DT_TIME : DT +FUNCTION SUB_DT_TIME: DT VAR_INPUT - IN1 : DT; - IN2 : TIME; + IN1: DT; + IN2: TIME; END_VAR END_FUNCTION @@ -306,19 +295,19 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION SUB_LDT_LTIME : LDT +FUNCTION SUB_LDT_LTIME: LDT VAR_INPUT - IN1 : LDT; - IN2 : LTIME; + IN1: LDT; + IN2: LTIME; END_VAR SUB_LDT_LTIME := SUB_DT_TIME(IN1, IN2); END_FUNCTION (* Specialized implementation of SUB for DT *) -FUNCTION SUB__DATE_AND_TIME__DATE_AND_TIME : TIME +FUNCTION SUB__DATE_AND_TIME__DATE_AND_TIME: TIME VAR_INPUT - IN1 : DT; - IN2 : DT; + IN1: DT; + IN2: DT; END_VAR SUB__DATE_AND_TIME__DATE_AND_TIME := SUB_DT_DT(IN1, IN2); END_FUNCTION @@ -330,10 +319,10 @@ END_FUNCTION * *********************) {external} -FUNCTION SUB_DT_DT : TIME +FUNCTION SUB_DT_DT: TIME VAR_INPUT - IN1 : DT; - IN2 : DT; + IN1: DT; + IN2: DT; END_VAR END_FUNCTION @@ -343,10 +332,10 @@ END_FUNCTION * Panic on overflow * *********************) -FUNCTION SUB_LDT_LDT : LTIME +FUNCTION SUB_LDT_LDT: LTIME VAR_INPUT - IN1 : LDT; - IN2 : LDT; + IN1: LDT; + IN2: LDT; END_VAR SUB_LDT_LDT := SUB_DT_DT(IN1, IN2); END_FUNCTION @@ -354,12 +343,13 @@ END_FUNCTION (******************** * * This operator produces the multiplication of the operands. +* It overloads the variadic builtin implementation of MUL, which is impemented for ANY_NUM * *********************) -FUNCTION MUL : T1 +FUNCTION MUL < T1: ANY, T2: ANY >: T1 VAR_INPUT - IN1 : T1; - IN2 : T2; + IN1: T1; + IN2: T2; END_VAR END_FUNCTION @@ -368,10 +358,10 @@ END_FUNCTION * This operator produces the multiplication of TIME and ANY_NUM. * *********************) -FUNCTION MUL_TIME : TIME +FUNCTION MUL_TIME < T: ANY_NUM >: TIME VAR_INPUT - IN1 : TIME; - IN2 : T; + IN1: TIME; + IN2: T; END_VAR END_FUNCTION @@ -380,22 +370,10 @@ END_FUNCTION * This operator produces the multiplication of LTIME and ANY_NUM. * *********************) -FUNCTION MUL_LTIME : LTIME -VAR_INPUT - IN1 : LTIME; - IN2 : T; -END_VAR -END_FUNCTION - -(******************** -* -* This operator produces the division of the operands. -* -*********************) -FUNCTION DIV : T1 +FUNCTION MUL_LTIME < T: ANY_NUM >: LTIME VAR_INPUT - IN1 : T1; - IN2 : T2; + IN1: LTIME; + IN2: T; END_VAR END_FUNCTION @@ -404,10 +382,10 @@ END_FUNCTION * This operator produces the division of TIME and ANY_NUM. * *********************) -FUNCTION DIV_TIME : TIME +FUNCTION DIV_TIME < T: ANY_NUM >: TIME VAR_INPUT - IN1 : TIME; - IN2 : T; + IN1: TIME; + IN2: T; END_VAR END_FUNCTION @@ -416,9 +394,9 @@ END_FUNCTION * This operator produces the division of LTIME and ANY_NUM. * *********************) -FUNCTION DIV_LTIME : LTIME +FUNCTION DIV_LTIME < T: ANY_NUM >: LTIME VAR_INPUT - IN1 : LTIME; - IN2 : T; + IN1: LTIME; + IN2: T; END_VAR END_FUNCTION diff --git a/libs/stdlib/iec61131-st/string_functions.st b/libs/stdlib/iec61131-st/string_functions.st index 01af032984..4a546c943b 100644 --- a/libs/stdlib/iec61131-st/string_functions.st +++ b/libs/stdlib/iec61131-st/string_functions.st @@ -1,756 +1,665 @@ VAR_GLOBAL CONSTANT - __STRING_LENGTH : DINT := 2048; + __STRING_LENGTH: DINT := 2048; END_VAR -(****************************************************************************** +(* ***************************************************************************** Description: String character length Input: - - IN: A character string + - IN: A character string Return: String length -******************************************************************************) +***************************************************************************** *) {external} -FUNCTION LEN : DINT +FUNCTION LEN < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN : T; + IN: T; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Left Input: - - IN: A character string - - L: The length of the substring + - IN: A character string + - L: The length of the substring Return: A substring consisting of the leftmost L characters of IN -******************************************************************************) -FUNCTION LEFT : T +***************************************************************************** *) +FUNCTION LEFT < T: ANY_STRING >: T VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR END_FUNCTION {external} -FUNCTION LEFT_EXT : DINT +FUNCTION LEFT_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION LEFT__STRING : STRING[__STRING_LENGTH] +FUNCTION LEFT__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR LEFT_EXT(IN, L, LEFT__STRING); END_FUNCTION -FUNCTION LEFT__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION LEFT__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR LEFT_EXT(IN, L, LEFT__WSTRING); END_FUNCTION {external} -FUNCTION LEFT_EXT__STRING : DINT +FUNCTION LEFT_EXT__STRING: DINT VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION LEFT_EXT__WSTRING : DINT +FUNCTION LEFT_EXT__WSTRING: DINT VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Right Input: - - IN: A character string - - L: The length of the substring + - IN: A character string + - L: The length of the substring Return: A substring consisting of the rightmost L characters of IN -******************************************************************************) -FUNCTION RIGHT : T +***************************************************************************** *) +FUNCTION RIGHT < T: ANY_STRING >: T VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR END_FUNCTION {external} -FUNCTION RIGHT_EXT : DINT +FUNCTION RIGHT_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION RIGHT__STRING : STRING[__STRING_LENGTH] +FUNCTION RIGHT__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR RIGHT_EXT(IN, L, RIGHT__STRING); END_FUNCTION -FUNCTION RIGHT__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION RIGHT__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR RIGHT_EXT(IN, L, RIGHT__WSTRING); END_FUNCTION {external} -FUNCTION RIGHT_EXT__STRING : DINT +FUNCTION RIGHT_EXT__STRING: DINT VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION RIGHT_EXT__WSTRING : DINT +FUNCTION RIGHT_EXT__WSTRING: DINT VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; + L: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Middle Input: - - IN: A character string - - L: The length of the substring - - P: The starting index position + - IN: A character string + - L: The length of the substring + - P: The starting index position Return: A substring that contains L characters starting from position P in a string. -******************************************************************************) -FUNCTION MID : T +***************************************************************************** *) +FUNCTION MID < T: ANY_STRING >: T VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR END_FUNCTION {external} -FUNCTION MID_EXT : DINT +FUNCTION MID_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION MID__STRING : STRING[__STRING_LENGTH] +FUNCTION MID__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR MID_EXT(IN, L, P, MID__STRING); END_FUNCTION -FUNCTION MID__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION MID__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR MID_EXT(IN, L, P, MID__WSTRING); END_FUNCTION {external} -FUNCTION MID_EXT__STRING : DINT +FUNCTION MID_EXT__STRING: DINT VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION MID_EXT__WSTRING : DINT +FUNCTION MID_EXT__WSTRING: DINT VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Extensible concatenation Input: - - IN: Two or more comma-separated strings + - IN: Two or more comma-separated strings Return: A string combining all given input strings in the same order as the given string parameters. -******************************************************************************) +***************************************************************************** *) {external} -FUNCTION CONCAT__STRING : STRING[2048] +FUNCTION CONCAT__STRING: STRING[2048] VAR_INPUT {ref} - args : {sized} STRING...; + args: {sized} STRING...; END_VAR END_FUNCTION {external} -FUNCTION CONCAT__WSTRING : WSTRING[2048] +FUNCTION CONCAT__WSTRING: WSTRING[2048] VAR_INPUT {ref} - args : {sized} WSTRING...; + args: {sized} WSTRING...; END_VAR END_FUNCTION -FUNCTION CONCAT : T +FUNCTION CONCAT < T: ANY_STRING >: T VAR_INPUT {ref} - args : {sized} T...; + args: {sized} T...; END_VAR END_FUNCTION {external} -FUNCTION CONCAT_EXT : DINT +FUNCTION CONCAT_EXT < T: ANY_STRING >: DINT VAR_IN_OUT - OUT : T; + OUT: T; END_VAR VAR_INPUT {ref} - args : {sized} T...; + args: {sized} T...; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Insert Input: - IN1: The string to insert into - IN2: The string to insert - P: The position at which to insert + IN1: The string to insert into + IN2: The string to insert + P: The position at which to insert Return: A string consisting of IN2 inserted at position P into string IN1 -******************************************************************************) -FUNCTION INSERT : T +***************************************************************************** *) +FUNCTION INSERT < T: ANY_STRING >: T VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN1: T; + IN2: T; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR END_FUNCTION {external} -FUNCTION INSERT_EXT : DINT +FUNCTION INSERT_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN1: T; + IN2: T; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION INSERT__STRING : STRING[__STRING_LENGTH] +FUNCTION INSERT__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN1 : STRING[__STRING_LENGTH]; - IN2 : STRING[__STRING_LENGTH]; + IN1: STRING[__STRING_LENGTH]; + IN2: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR INSERT_EXT(IN1, IN2, P, INSERT__STRING); END_FUNCTION -FUNCTION INSERT__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION INSERT__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN1 : WSTRING[__STRING_LENGTH]; - IN2 : WSTRING[__STRING_LENGTH]; + IN1: WSTRING[__STRING_LENGTH]; + IN2: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR INSERT_EXT(IN1, IN2, P, INSERT__WSTRING); END_FUNCTION {external} -FUNCTION INSERT_EXT__STRING : DINT +FUNCTION INSERT_EXT__STRING: DINT VAR_INPUT {ref} - IN1 : STRING[__STRING_LENGTH]; - IN2 : STRING[__STRING_LENGTH]; + IN1: STRING[__STRING_LENGTH]; + IN2: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION INSERT_EXT__WSTRING : DINT +FUNCTION INSERT_EXT__WSTRING: DINT VAR_INPUT {ref} - IN1 : WSTRING[__STRING_LENGTH]; - IN2 : WSTRING[__STRING_LENGTH]; + IN1: WSTRING[__STRING_LENGTH]; + IN2: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - P : DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Delete Input: IN: The string to delete characters from - L: The amount of characters to delete - P: The position at which to start deleting + L: The amount of characters to delete + P: The position at which to start deleting Return: A new string with L characters deleted from P onwards -******************************************************************************) -FUNCTION DELETE : T +***************************************************************************** *) +FUNCTION DELETE < T: ANY_STRING >: T VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR END_FUNCTION {external} -FUNCTION DELETE_EXT : DINT +FUNCTION DELETE_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN : T; + IN: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION DELETE__STRING : STRING[__STRING_LENGTH] +FUNCTION DELETE__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR DELETE_EXT(IN, L, P, DELETE__STRING); END_FUNCTION -FUNCTION DELETE__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION DELETE__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR DELETE_EXT(IN, L, P, DELETE__WSTRING); END_FUNCTION {external} -FUNCTION DELETE_EXT__STRING : DINT +FUNCTION DELETE_EXT__STRING: DINT VAR_INPUT {ref} - IN : STRING[__STRING_LENGTH]; + IN: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION DELETE_EXT__WSTRING : DINT +FUNCTION DELETE_EXT__WSTRING: DINT VAR_INPUT {ref} - IN : WSTRING[__STRING_LENGTH]; + IN: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Replace Input: - IN1: The string to replace characters from - IN2: The replacement string to be inserted - L: The amount of characters to delete - P: The position at which to start deleting + IN1: The string to replace characters from + IN2: The replacement string to be inserted + L: The amount of characters to delete + P: The position at which to start deleting Return: A new string which has L characters replaced by IN2 from position P onwards -******************************************************************************) -FUNCTION REPLACE : T +***************************************************************************** *) +FUNCTION REPLACE < T: ANY_STRING >: T VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN1: T; + IN2: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR END_FUNCTION {external} -FUNCTION REPLACE_EXT : DINT +FUNCTION REPLACE_EXT < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN1: T; + IN2: T; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : T; + OUT: T; END_VAR END_FUNCTION -FUNCTION REPLACE__STRING : STRING[__STRING_LENGTH] +FUNCTION REPLACE__STRING: STRING[__STRING_LENGTH] VAR_INPUT {ref} - IN1 : STRING[__STRING_LENGTH]; - IN2 : STRING[__STRING_LENGTH]; + IN1: STRING[__STRING_LENGTH]; + IN2: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR REPLACE_EXT(IN1, IN2, L, P, REPLACE__STRING); END_FUNCTION -FUNCTION REPLACE__WSTRING : WSTRING[__STRING_LENGTH] +FUNCTION REPLACE__WSTRING: WSTRING[__STRING_LENGTH] VAR_INPUT {ref} - IN1 : WSTRING[__STRING_LENGTH]; - IN2 : WSTRING[__STRING_LENGTH]; + IN1: WSTRING[__STRING_LENGTH]; + IN2: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR REPLACE_EXT(IN1, IN2, L, P, REPLACE__WSTRING); END_FUNCTION {external} -FUNCTION REPLACE_EXT__STRING : DINT +FUNCTION REPLACE_EXT__STRING: DINT VAR_INPUT {ref} - IN1 : STRING[__STRING_LENGTH]; - IN2 : STRING[__STRING_LENGTH]; + IN1: STRING[__STRING_LENGTH]; + IN2: STRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : STRING[__STRING_LENGTH]; + OUT: STRING[__STRING_LENGTH]; END_VAR END_FUNCTION {external} -FUNCTION REPLACE_EXT__WSTRING : DINT +FUNCTION REPLACE_EXT__WSTRING: DINT VAR_INPUT {ref} - IN1 : WSTRING[__STRING_LENGTH]; - IN2 : WSTRING[__STRING_LENGTH]; + IN1: WSTRING[__STRING_LENGTH]; + IN2: WSTRING[__STRING_LENGTH]; END_VAR VAR_INPUT - L : DINT; - P : DINT; + L: DINT; + P: DINT; END_VAR VAR_IN_OUT - OUT : WSTRING[__STRING_LENGTH]; + OUT: WSTRING[__STRING_LENGTH]; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Find Input: - IN1: The string in which to search in - IN2: The substring to search for + IN1: The string in which to search in + IN2: The substring to search for Return: The character index of the first match. 0 if there are no matches. -******************************************************************************) +***************************************************************************** *) {external} -FUNCTION FIND : DINT +FUNCTION FIND < T: ANY_STRING >: DINT VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN1: T; + IN2: T; END_VAR END_FUNCTION -(****************************************************************************** +(* ***************************************************************************** Description: Decreasing sequence -Input: The strings to compare, in order. ((IN1>IN2) & (IN2>IN3) & .. & (INn-1>INn)) +Input: The strings to compare, in order. ((IN1>IN2) & (IN2>IN3) & .. & (INn-1>INn)) Return: TRUE if codepoints are in decreasing order (alphabetical order: ZYX > YXW > XWV ..) FALSE otherwise -******************************************************************************) -{external} -FUNCTION GT : BOOL -VAR_INPUT {ref} - IN1 : {sized} T...; -END_VAR -END_FUNCTION - -{external} -FUNCTION GT__STRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} STRING...; -END_VAR -END_FUNCTION - -{external} -FUNCTION GT__WSTRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} WSTRING...; -END_VAR -END_FUNCTION - +***************************************************************************** *) // passing strings by ref causes an error/warning. code still compiles and works correctly -FUNCTION STRING_GREATER : BOOL +FUNCTION STRING_GREATER: BOOL VAR_INPUT - a, b : STRING; + a, b: STRING; END_VAR - STRING_GREATER := GT(a, b); + STRING_GREATER := __STRING_GREATER(a, b); END_FUNCTION -FUNCTION WSTRING_GREATER : BOOL +FUNCTION WSTRING_GREATER: BOOL VAR_INPUT - a, b : WSTRING; -END_VAR - WSTRING_GREATER := GT(a, b); -END_FUNCTION - -(****************************************************************************** -Description: Monotonic sequence -Input: The strings to compare, in order. ((IN1>=IN2) & (IN2>=IN3) & .. & (INn-1>=INn)) -Return: - TRUE if codepoints are in decreasing order or are equal to adjacent codepoints (alphabetical order: ZYX >= ZYX >= YXW ..) - FALSE otherwise -******************************************************************************) -{external} -FUNCTION GE : BOOL -VAR_INPUT {ref} - IN1 : {sized} T...; + a, b: WSTRING; END_VAR + WSTRING_GREATER := __WSTRING_GREATER(a, b); END_FUNCTION -{external} -FUNCTION GE__STRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} STRING...; -END_VAR -END_FUNCTION - -{external} -FUNCTION GE__WSTRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} WSTRING...; -END_VAR -END_FUNCTION - -(****************************************************************************** +(* ***************************************************************************** Description: Equality Input: The strings to compare, in order. ((IN1=IN2) & (IN2=IN3) & .. & (INn-1=INn)) Return: TRUE if codepoints are equal to each other FALSE otherwise. -******************************************************************************) -{external} -FUNCTION EQ : BOOL -VAR_INPUT {ref} - IN1 : {sized} T...; -END_VAR -END_FUNCTION - -{external} -FUNCTION EQ__STRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} STRING...; -END_VAR -END_FUNCTION - -{external} -FUNCTION EQ__WSTRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} WSTRING...; -END_VAR -END_FUNCTION - -FUNCTION STRING_EQUAL : BOOL +***************************************************************************** *) +FUNCTION STRING_EQUAL: BOOL VAR_INPUT - a, b : STRING; + a, b: STRING; END_VAR - STRING_EQUAL := EQ(a, b); + STRING_EQUAL := __STRING_EQUAL(a, b); END_FUNCTION -FUNCTION WSTRING_EQUAL : BOOL +FUNCTION WSTRING_EQUAL: BOOL VAR_INPUT - a, b : WSTRING; + a, b: WSTRING; END_VAR - WSTRING_EQUAL := EQ(a, b); + WSTRING_EQUAL := __WSTRING_EQUAL(a, b); END_FUNCTION -(****************************************************************************** -Description: Monotonic sequence -Input: The strings to compare, in order. ((IN1<=IN2) & (IN2<=IN3) & .. & (INn-1<=INn)) +(* ***************************************************************************** +Description: Increasing sequence +Input: The strings to compare, in order. ((IN1 : BOOL -VAR_INPUT {ref} - IN1 : {sized} T...; +***************************************************************************** *) +FUNCTION STRING_LESS: BOOL +VAR_INPUT + a, b: STRING; END_VAR + STRING_LESS := __STRING_LESS(a, b); END_FUNCTION -{external} -FUNCTION LE__STRING : BOOL -VAR_INPUT {ref} - IN1 : {sized} STRING...; +FUNCTION WSTRING_LESS: BOOL +VAR_INPUT + a, b: WSTRING; END_VAR + WSTRING_LESS := __WSTRING_LESS(a, b); END_FUNCTION +// internal functions {external} -FUNCTION LE__WSTRING : BOOL +FUNCTION __STRING_GREATER: BOOL VAR_INPUT {ref} - IN1 : {sized} WSTRING...; + IN: {sized} STRING...; END_VAR END_FUNCTION -(****************************************************************************** -Description: Increasing sequence -Input: The strings to compare, in order. ((IN1 : BOOL +FUNCTION __STRING_EQUAL: BOOL VAR_INPUT {ref} - IN1 : {sized} T...; + IN: {sized} STRING...; END_VAR END_FUNCTION {external} -FUNCTION LT__STRING : BOOL +FUNCTION __STRING_LESS: BOOL VAR_INPUT {ref} - IN1 : {sized} STRING...; + IN: {sized} STRING...; END_VAR END_FUNCTION {external} -FUNCTION LT__WSTRING : BOOL +FUNCTION __WSTRING_GREATER: BOOL VAR_INPUT {ref} - IN1 : {sized} WSTRING...; + IN: {sized} WSTRING...; END_VAR END_FUNCTION -FUNCTION STRING_LESS : BOOL -VAR_INPUT - a, b : STRING; -END_VAR - STRING_LESS := LT(a, b); -END_FUNCTION - -FUNCTION WSTRING_LESS : BOOL -VAR_INPUT - a, b : WSTRING; +{external} +FUNCTION __WSTRING_EQUAL: BOOL +VAR_INPUT {ref} + IN: {sized} WSTRING...; END_VAR - WSTRING_LESS := LT(a, b); END_FUNCTION -(****************************************************************************** -Description: Inequality -Input: The strings to compare. (IN1<>IN2) -Return: - TRUE if strings do not match - FALSE otherwise -******************************************************************************) {external} -FUNCTION NE : BOOL +FUNCTION __WSTRING_LESS: BOOL VAR_INPUT {ref} - IN1 : T; - IN2 : T; + IN: {sized} WSTRING...; END_VAR END_FUNCTION diff --git a/libs/stdlib/src/string_functions.rs b/libs/stdlib/src/string_functions.rs index a2cc27d001..d83eeb06f4 100644 --- a/libs/stdlib/src/string_functions.rs +++ b/libs/stdlib/src/string_functions.rs @@ -760,7 +760,7 @@ where /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn GT__STRING(argc: i32, argv: *const *const u8) -> bool { +pub unsafe extern "C" fn __STRING_GREATER(argc: i32, argv: *const *const u8) -> bool { compare(argc, argv, Ordering::is_gt) } @@ -772,34 +772,10 @@ pub unsafe extern "C" fn GT__STRING(argc: i32, argv: *const *const u8) -> bool { /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn GT__WSTRING(argc: i32, argv: *const *const u16) -> bool { +pub unsafe extern "C" fn __WSTRING_GREATER(argc: i32, argv: *const *const u16) -> bool { compare(argc, argv, Ordering::is_gt) } -/// Extensible "greater or equal" comparison function. -/// Encoding: UTF-8 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn GE__STRING(argc: i32, argv: *const *const u8) -> bool { - compare(argc, argv, Ordering::is_ge) -} - -/// Extensible "greater or equal" comparison function. -/// Encoding: UTF-16 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn GE__WSTRING(argc: i32, argv: *const *const u16) -> bool { - compare(argc, argv, Ordering::is_ge) -} - /// Extensible "equal" comparison function. /// Encoding: UTF-8 /// @@ -808,7 +784,7 @@ pub unsafe extern "C" fn GE__WSTRING(argc: i32, argv: *const *const u16) -> bool /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn EQ__STRING(argc: i32, argv: *const *const u8) -> bool { +pub unsafe extern "C" fn __STRING_EQUAL(argc: i32, argv: *const *const u8) -> bool { compare(argc, argv, Ordering::is_eq) } @@ -820,34 +796,10 @@ pub unsafe extern "C" fn EQ__STRING(argc: i32, argv: *const *const u8) -> bool { /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn EQ__WSTRING(argc: i32, argv: *const *const u16) -> bool { +pub unsafe extern "C" fn __WSTRING_EQUAL(argc: i32, argv: *const *const u16) -> bool { compare(argc, argv, Ordering::is_eq) } -/// Extensible "less or equal" comparison function. -/// Encoding: UTF-8 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn LE__STRING(argc: i32, argv: *const *const u8) -> bool { - compare(argc, argv, Ordering::is_le) -} - -/// Extensible "less or equal" comparison function. -/// Encoding: UTF-16 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn LE__WSTRING(argc: i32, argv: *const *const u16) -> bool { - compare(argc, argv, Ordering::is_le) -} - /// Extensible "less than" comparison function. /// Encoding: UTF-8 /// @@ -856,7 +808,7 @@ pub unsafe extern "C" fn LE__WSTRING(argc: i32, argv: *const *const u16) -> bool /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn LT__STRING(argc: i32, argv: *const *const u8) -> bool { +pub unsafe extern "C" fn __STRING_LESS(argc: i32, argv: *const *const u8) -> bool { compare(argc, argv, Ordering::is_lt) } @@ -868,34 +820,10 @@ pub unsafe extern "C" fn LT__STRING(argc: i32, argv: *const *const u8) -> bool { /// Works on raw pointers, inherently unsafe. #[allow(non_snake_case)] #[no_mangle] -pub unsafe extern "C" fn LT__WSTRING(argc: i32, argv: *const *const u16) -> bool { +pub unsafe extern "C" fn __WSTRING_LESS(argc: i32, argv: *const *const u16) -> bool { compare(argc, argv, Ordering::is_lt) } -/// "Not equal" comparison function. -/// Encoding: UTF-8 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn NE__STRING(string1: *const u8, string2: *const u8) -> bool { - ptr_to_slice(string1).cmp(ptr_to_slice(string2)).is_ne() -} - -/// "Not equal" comparison function. -/// Encoding: UTF-16 -/// -/// # Safety -/// -/// Works on raw pointers, inherently unsafe. -#[allow(non_snake_case)] -#[no_mangle] -pub unsafe extern "C" fn NE__WSTRING(string1: *const u16, string2: *const u16) -> bool { - ptr_to_slice(string1).cmp(ptr_to_slice(string2)).is_ne() -} - // -------------------------------------------------unit tests----------------------------------------- #[cfg(test)] mod test { @@ -1329,49 +1257,35 @@ mod test { fn test_greater_than_string_is_false_for_equal_strings() { let argv = ["hællø wørlÞ\0".as_ptr(), "hællø wørlÞ\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(!GT__STRING(argc, argv.as_ptr())) } + unsafe { assert!(!__STRING_GREATER(argc, argv.as_ptr())) } } #[test] fn test_greater_than_string_is_true_for_decreasing_sequence() { let argv = ["zyxZabcdefghijklmn\0".as_ptr(), "zyxA\0".as_ptr(), "zyx\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(GT__STRING(argc, argv.as_ptr())) } + unsafe { assert!(__STRING_GREATER(argc, argv.as_ptr())) } } #[test] fn test_greater_than_string_is_false_for_increasing_sequence() { let argv = ["abc\0".as_ptr(), "bce\0".as_ptr(), "xyz\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(!GT__STRING(argc, argv.as_ptr())) } + unsafe { assert!(!__STRING_GREATER(argc, argv.as_ptr())) } } #[test] fn test_greater_than_string_works_correctly_for_two_params() { let argv = ["zyxAabcdefghijklmn\0".as_ptr(), "zyxZ".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(!GT__STRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_greater_or_equal_string() { - let argv = ["xyz\0".as_ptr(), "bcefghijkl\0".as_ptr(), "abc\0".as_ptr()]; - let argc = argv.len() as i32; - unsafe { assert!(GE__STRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_greater_or_equal_string_is_true_for_equal_strings() { - let argv = ["hællø wørlÞ\0".as_ptr(), "hællø wørlÞ\0".as_ptr()]; - let argc = argv.len() as i32; - unsafe { assert!(GE__STRING(argc, argv.as_ptr())) } + unsafe { assert!(!__STRING_GREATER(argc, argv.as_ptr())) } } #[test] fn test_equal_string() { let argv = ["hællø wørlÞ\0".as_ptr(), "hællø wørlÞ\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(EQ__STRING(argc, argv.as_ptr())) } + unsafe { assert!(__STRING_EQUAL(argc, argv.as_ptr())) } } #[test] @@ -1383,69 +1297,21 @@ mod test { "hællø wørlÞZZc\0".as_ptr(), ]; let argc = argv.len() as i32; - unsafe { assert!(!EQ__STRING(argc, argv.as_ptr())) } + unsafe { assert!(!__STRING_EQUAL(argc, argv.as_ptr())) } } #[test] fn test_lesser_than_string() { let argv = ["hællø wørlÞabc\0".as_ptr(), "hællø wørlÞz\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(LT__STRING(argc, argv.as_ptr())) } + unsafe { assert!(__STRING_LESS(argc, argv.as_ptr())) } } #[test] fn test_lesser_than_string_is_false() { let argv = ["z\0".as_ptr(), "hællø wørlÞzbc\0".as_ptr()]; let argc = argv.len() as i32; - unsafe { assert!(!LT__STRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_lesser_or_equal_string_is_true_for_increasing_sequence() { - let argv = [ - "a\0".as_ptr(), - "a\0".as_ptr(), - "b\0".as_ptr(), - "b\0".as_ptr(), - "b\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "q".as_ptr(), - ]; - let argc = argv.len() as i32; - unsafe { assert!(LE__STRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_lesser_or_equal_string_is_false_if_last_string_doesnt_match() { - let argv = [ - "a\0".as_ptr(), - "a\0".as_ptr(), - "b\0".as_ptr(), - "b\0".as_ptr(), - "b\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "hællø wørlÞzbc\0".as_ptr(), - "a".as_ptr(), - ]; - let argc = argv.len() as i32; - unsafe { assert!(!LE__STRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_not_equal_string_is_true_for_unequal_strings() { - let string1 = "these strings".as_ptr(); - let string2 = "are not equal".as_ptr(); - unsafe { assert!(NE__STRING(string1, string2)) } - } - - #[test] - fn test_not_equal_string_is_false_for_equal_strings() { - let string1 = "these strings are equal".as_ptr(); - let string2 = "these strings are equal".as_ptr(); - unsafe { assert!(!NE__STRING(string1, string2)) } + unsafe { assert!(!__STRING_LESS(argc, argv.as_ptr())) } } // -----------------------------------UTF16 @@ -1855,22 +1721,7 @@ mod test { argv[i] = arg.as_ptr(); } let argc = argv.len() as i32; - unsafe { assert!(GT__WSTRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_ge_wstring() { - let argvec: [Vec; 3] = [ - "hællø wørlÞ\0".encode_utf16().collect(), - "hello world\0".encode_utf16().collect(), - "hello world\0".encode_utf16().collect(), - ]; - let mut argv: [*const u16; 3] = [std::ptr::null(); 3]; - for (i, arg) in argvec.iter().enumerate() { - argv[i] = arg.as_ptr(); - } - let argc = argv.len() as i32; - unsafe { assert!(GE__WSTRING(argc, argv.as_ptr())) } + unsafe { assert!(__WSTRING_GREATER(argc, argv.as_ptr())) } } #[test] @@ -1885,7 +1736,7 @@ mod test { argv[i] = arg.as_ptr(); } let argc = argv.len() as i32; - unsafe { assert!(EQ__WSTRING(argc, argv.as_ptr())) } + unsafe { assert!(__WSTRING_EQUAL(argc, argv.as_ptr())) } } #[test] @@ -1900,35 +1751,6 @@ mod test { argv[i] = arg.as_ptr(); } let argc = argv.len() as i32; - unsafe { assert!(LT__WSTRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_le_wstring() { - let argvec: [Vec; 6] = [ - "hello\0".encode_utf16().collect(), - "hello worlb\0".encode_utf16().collect(), - "hello worlc\0".encode_utf16().collect(), - "hello world\0".encode_utf16().collect(), - "hællø wørlÞ\0".encode_utf16().collect(), - "hællø wørlÞ\0".encode_utf16().collect(), - ]; - let mut argv: [*const u16; 6] = [std::ptr::null(); 6]; - for (i, arg) in argvec.iter().enumerate() { - argv[i] = arg.as_ptr(); - } - let argc = argv.len() as i32; - unsafe { assert!(LE__WSTRING(argc, argv.as_ptr())) } - } - - #[test] - fn test_ne_wstring() { - let argvec: [Vec; 2] = - ["hællø wørlÞ\0".encode_utf16().collect(), "hello world\0".encode_utf16().collect()]; - let mut argv: [*const u16; 2] = [std::ptr::null(); 2]; - for (i, arg) in argvec.iter().enumerate() { - argv[i] = arg.as_ptr(); - } - unsafe { assert!(NE__WSTRING(argv[0], argv[1])) } + unsafe { assert!(__WSTRING_LESS(argc, argv.as_ptr())) } } } diff --git a/libs/stdlib/tests/common/mod.rs b/libs/stdlib/tests/common/mod.rs index 548ee6c86a..93eba4e5ba 100644 --- a/libs/stdlib/tests/common/mod.rs +++ b/libs/stdlib/tests/common/mod.rs @@ -331,18 +331,12 @@ pub fn compile_with_native(context: &CodegenContext, source: T) - ("CONCAT_EXT__STRING", iec61131std::string_functions::CONCAT_EXT__STRING as usize), ("CONCAT__WSTRING", iec61131std::string_functions::CONCAT__WSTRING as usize), ("CONCAT_EXT__WSTRING", iec61131std::string_functions::CONCAT_EXT__WSTRING as usize), - ("GT__STRING", iec61131std::string_functions::GT__STRING as usize), - ("GT__WSTRING", iec61131std::string_functions::GT__WSTRING as usize), - ("GE__STRING", iec61131std::string_functions::GE__STRING as usize), - ("GE__WSTRING", iec61131std::string_functions::GE__WSTRING as usize), - ("EQ__STRING", iec61131std::string_functions::EQ__STRING as usize), - ("EQ__WSTRING", iec61131std::string_functions::EQ__WSTRING as usize), - ("LE__STRING", iec61131std::string_functions::LE__STRING as usize), - ("LE__WSTRING", iec61131std::string_functions::LE__WSTRING as usize), - ("LT__STRING", iec61131std::string_functions::LT__STRING as usize), - ("LT__WSTRING", iec61131std::string_functions::LT__WSTRING as usize), - ("NE__STRING", iec61131std::string_functions::NE__STRING as usize), - ("NE__WSTRING", iec61131std::string_functions::NE__WSTRING as usize), + ("__STRING_GREATER", iec61131std::string_functions::__STRING_GREATER as usize), + ("__WSTRING_GREATER", iec61131std::string_functions::__WSTRING_GREATER as usize), + ("__STRING_EQUAL", iec61131std::string_functions::__STRING_EQUAL as usize), + ("__WSTRING_EQUAL", iec61131std::string_functions::__WSTRING_EQUAL as usize), + ("__STRING_LESS", iec61131std::string_functions::__STRING_LESS as usize), + ("__WSTRING_LESS", iec61131std::string_functions::__WSTRING_LESS as usize), ("EXPT__REAL__DINT", iec61131std::arithmetic_functions::EXPT__REAL__DINT as usize), ("EXPT__REAL__REAL", iec61131std::arithmetic_functions::EXPT__REAL__REAL as usize), ("EXPT__REAL__LREAL", iec61131std::arithmetic_functions::EXPT__REAL__LREAL as usize), diff --git a/libs/stdlib/tests/date_time_numeric_functions_tests.rs b/libs/stdlib/tests/date_time_numeric_functions_tests.rs index 2c40fdff06..0d3dc5b6ba 100644 --- a/libs/stdlib/tests/date_time_numeric_functions_tests.rs +++ b/libs/stdlib/tests/date_time_numeric_functions_tests.rs @@ -933,3 +933,55 @@ fn div_ltime() { chrono::Utc.timestamp_millis_opt(2_700).unwrap() // 2_700ms => 2s 700ms ); } + +#[test] +#[should_panic] +fn date_time_overloaded_add_function_called_with_too_many_params() { + let src = " + FUNCTION main : LINT + VAR + x1 : ARRAY[0..3] OF DATE; + x2 : DATE; + END_VAR + main := ADD(x1[0], x1[1], x1[2], x1[3], x2); + END_FUNCTION + "; + + let sources = add_std!(src, "date_time_numeric_functions.st"); + let mut maintype = MainType::default(); + let _: i64 = compile_and_run(sources, &mut maintype); +} + +#[test] +fn date_time_overloaded_add_and_numerical_add_compile_correctly() { + let src = " + PROGRAM main + VAR + a: LINT; + b: REAL; + END_VAR + VAR_TEMP + var_tod : TOD := TOD#23:00:01; + var_time : TIME := TIME#55m59s; + var_real : REAL := 1.0; + var_dint : DINT := 10; + END_VAR + a := ADD(var_tod, var_time); + b := ADD(var_real, var_dint, 3, 4); + END_PROGRAM + "; + + #[derive(Default)] + struct MainType { + a: i64, + b: f32, + } + + let sources = add_std!(src, "date_time_numeric_functions.st"); + let mut maintype = MainType::default(); + let _: i64 = compile_and_run(sources, &mut maintype); + let tod_23h_56m = get_time_from_hms(23, 56, 0).timestamp_nanos(); + + assert_eq!(tod_23h_56m, maintype.a); + assert_eq!(18.0, maintype.b); +} diff --git a/src/builtins.rs b/src/builtins.rs index 8703113e21..5ea50f91dc 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -8,8 +8,8 @@ use inkwell::{ use lazy_static::lazy_static; use plc_ast::{ ast::{ - self, flatten_expression_list, pre_process, AstNode, AstStatement, CompilationUnit, GenericBinding, - LinkageType, TypeNature, + self, flatten_expression_list, pre_process, AstFactory, AstNode, AstStatement, CompilationUnit, + GenericBinding, LinkageType, Operator, TypeNature, }, literals::AstLiteral, provider::IdProvider, @@ -23,10 +23,10 @@ use crate::{ lexer, parser, resolver::{ self, - generics::{no_generic_name_resolver, GenericType}, + generics::{generic_name_resolver, no_generic_name_resolver, GenericType}, AnnotationMap, StatementAnnotation, TypeAnnotator, VisitorContext, }, - typesystem::{self, get_literal_actual_signed_type_name}, + typesystem::{self, get_bigger_type, get_literal_actual_signed_type_name}, validation::{Validator, Validators}, }; @@ -68,7 +68,7 @@ lazy_static! { END_VAR END_FUNCTION ", - annotation: Some(|annotator, operator, parameters, _| { + annotation: Some(|annotator, _, operator, parameters, _| { // invalid amount of parameters is checked during validation let Some(params) = parameters else { return; }; // Get the input and annotate it with a pointer type @@ -288,8 +288,8 @@ lazy_static! { dim : T; END_VAR END_FUNCTION", - annotation: Some(|annotator, _, parameters, _| { - annotate_variable_length_array_bound_function(annotator, parameters) + annotation: Some(|annotator, _, _, parameters, _| { + annotate_variable_length_array_bound_function(annotator, parameters); }), validation: Some(|validator, operator, parameters, annotations, index| { validate_variable_length_array_bound_function(validator, operator, parameters, annotations, index) @@ -311,8 +311,8 @@ lazy_static! { dim : T; END_VAR END_FUNCTION", - annotation: Some(|annotator, _, parameters, _| { - annotate_variable_length_array_bound_function(annotator, parameters) + annotation: Some(|annotator, _, _, parameters, _| { + annotate_variable_length_array_bound_function(annotator, parameters); }), validation: Some(|validator, operator, parameters, annotations, index| { validate_variable_length_array_bound_function(validator, operator, parameters, annotations, index) @@ -323,9 +323,396 @@ lazy_static! { } } ), + // Arithmetic functions + ( + "ADD", + BuiltIn { + decl: "FUNCTION ADD : T + VAR_INPUT + args: {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + + annotate_arithmetic_function(annotator, statement, operator, params, ctx, Operator::Plus) + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Plus) + }), + generic_name_resolver, + code: |_, _, _| { + unreachable!("ADD is not generated as a function call"); + } + } + ), + ( + "MUL", + BuiltIn { + decl: "FUNCTION MUL : T + VAR_INPUT + args: {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + + annotate_arithmetic_function(annotator, statement, operator, params, ctx, Operator::Multiplication) + }), + validation: Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Multiplication) + }), + generic_name_resolver, + code: |_, _, _| { + unreachable!("MUL is not generated as a function call"); + } + } + ), + ( + "SUB", + BuiltIn { + decl: "FUNCTION SUB : T1 + VAR_INPUT + IN1 : T1; + IN2 : T2; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_arithmetic_function(annotator, statement, operator, params, ctx, Operator::Minus) + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Minus) + }), + generic_name_resolver, + code: |_, _, _| { + unreachable!("SUB is not generated as a function call"); + } + } + ), + ( + "DIV", + BuiltIn { + decl: "FUNCTION DIV : T1 + VAR_INPUT + IN1 : T1; + IN2 : T2; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_arithmetic_function(annotator, statement, operator, params, ctx, Operator::Division) + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Division) + }), + generic_name_resolver, + code: |_, _, _| { + unreachable!("DIV is not generated as a function call"); + } + } + ), + // TODO: MOD and AND/OR/XOR/NOT ANY_BIT ( NOT also supports boolean ) - FIXME: these are all keywords and therefore conflicting + ( + "GT", + BuiltIn { + decl: "FUNCTION GT : BOOL + VAR_INPUT + IN : {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::Greater); + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Greater) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("GT is not generated as a function call"); + } + } + ), + ( + "GE", + BuiltIn { + decl: "FUNCTION GE : BOOL + VAR_INPUT + IN : {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::GreaterOrEqual); + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::GreaterOrEqual) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("GE is not generated as a function call"); + } + } + ), + ( + "EQ", + BuiltIn { + decl: "FUNCTION EQ : BOOL + VAR_INPUT + IN : {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::Equal); + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Equal) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("EQ is not generated as a function call"); + } + } + ), + ( + "LE", + BuiltIn { + decl: "FUNCTION LE : BOOL + VAR_INPUT + IN : {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::LessOrEqual); + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::LessOrEqual) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("LE is not generated as a function call"); + } + } + ), + ( + "LT", + BuiltIn { + decl: "FUNCTION LT : BOOL + VAR_INPUT + IN : {sized} T...; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::Less); + }), + validation:Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::Less) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("LT is not generated as a function call"); + } + } + ), + ( + "NE", + BuiltIn { + decl: "FUNCTION NE : BOOL + VAR_INPUT + IN1 : T; + IN2 : T; + END_VAR + END_FUNCTION + ", + annotation: Some(|annotator, statement, operator, parameters, ctx| { + let Some(params) = parameters else { + return; + }; + annotate_comparison_function(annotator, statement, operator, params, ctx, Operator::NotEqual); + }), + validation: Some(|validator, operator, parameters, _, _| { + validate_builtin_symbol_parameter_count(validator, operator, parameters, Operator::NotEqual) + }), + generic_name_resolver: no_generic_name_resolver, + code : |_, _, _| { + unreachable!("NE is not generated as a function call"); + } + } + ), ]); } +fn validate_builtin_symbol_parameter_count( + validator: &mut Validator, + operator: &AstNode, + parameters: Option<&AstNode>, + operation: Operator, +) { + let Some(params) = parameters else { + validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 0, operator.get_location())); + return; + }; + + let count = flatten_expression_list(params).len(); + match operation { + // non-extensible operators + Operator::Minus | Operator::Division | Operator::NotEqual => { + if count != 2 { + validator.push_diagnostic(Diagnostic::invalid_parameter_count( + 2, + count, + operator.get_location(), + )); + } + } + _ => { + if count < 2 { + validator.push_diagnostic(Diagnostic::invalid_parameter_count( + 2, + count, + operator.get_location(), + )); + } + } + } +} + +// creates nested BinaryExpressions for each parameter, such that +// GT(a, b, c, d) ends up as (a > b) & (b > c) & (c > d) +fn annotate_comparison_function( + annotator: &mut TypeAnnotator, + statement: &AstNode, + operator: &AstNode, + parameters: &AstNode, + ctx: VisitorContext, + operation: Operator, +) { + let mut ctx = ctx; + let params_flattened = flatten_expression_list(parameters); + if params_flattened.iter().any(|it| { + !annotator + .annotation_map + .get_type_or_void(it, annotator.index) + .has_nature(TypeNature::Elementary, annotator.index) + }) { + // we are trying to call this function with a non-elementary type, so we redirect back to the resolver + annotator.annotate_call_statement(operator, Some(parameters), &ctx); + return; + } + + let comparisons = params_flattened + .windows(2) + .map(|window| { + AstFactory::create_binary_expression( + window[0].clone(), + operation, + window[1].clone(), + ctx.id_provider.next_id(), + ) + }) + .collect::>(); + let Some(new_statement) = comparisons.get(0) else { + // no windows => less than 2 parameters, caught during validation + return; + }; + let mut new_statement = new_statement.clone(); + comparisons.into_iter().skip(1).for_each(|right| { + new_statement = AstFactory::create_binary_expression( + new_statement.clone(), + Operator::And, + right, + ctx.id_provider.next_id(), + ) + }); + + annotator.visit_statement(&ctx, &new_statement); + annotator.update_expected_types(annotator.index.get_type_or_panic(typesystem::BOOL_TYPE), &new_statement); + annotator.annotate(statement, StatementAnnotation::ReplacementAst { statement: new_statement }); + annotator.update_expected_types(annotator.index.get_type_or_panic(typesystem::BOOL_TYPE), statement); +} + +fn annotate_arithmetic_function( + annotator: &mut TypeAnnotator, + statement: &AstNode, + operator: &AstNode, + parameters: &AstNode, + ctx: VisitorContext, + operation: Operator, +) { + let params_flattened = flatten_expression_list(parameters); + if params_flattened.iter().any(|it| { + !annotator + .annotation_map + .get_type_or_void(it, annotator.index) + .has_nature(TypeNature::Num, annotator.index) + }) { + // we are trying to call this function with a non-numerical type, so we redirect back to the resolver + annotator.annotate_call_statement(operator, Some(parameters), &ctx); + return; + } + + let mut ctx = ctx; + // find biggest type to later annotate it as type hint. this is done in a closure to avoid a borrow-checker tantrum later on due to + // mutable and immutable borrow of TypeAnnotator + let find_biggest_param_type_name = |annotator: &TypeAnnotator| { + let mut bigger = annotator + .annotation_map + .get_type_or_void(params_flattened.get(0).expect("must have this parameter"), annotator.index); + + for param in params_flattened.iter().skip(1) { + let right_type = annotator.annotation_map.get_type_or_void(param, annotator.index); + bigger = get_bigger_type(bigger, right_type, annotator.index); + } + + bigger.get_name().to_owned() + }; + + let bigger_type = find_biggest_param_type_name(annotator); + + // create nested AstStatement::BinaryExpression for each parameter, such that + // ADD(a, b, c, d) ends up as (((a + b) + c) + d) + let left = (*params_flattened.get(0).expect("Must exist")).clone(); + let new_statement = params_flattened.into_iter().skip(1).fold(left, |left, right| { + AstFactory::create_binary_expression(left, operation, right.clone(), ctx.id_provider.next_id()) + }); + + annotator.visit_statement(&ctx, &new_statement); + annotator.update_expected_types(annotator.index.get_type_or_panic(&bigger_type), &new_statement); + annotator.annotate(statement, StatementAnnotation::ReplacementAst { statement: new_statement }); + annotator.update_expected_types(annotator.index.get_type_or_panic(&bigger_type), statement); +} + fn annotate_variable_length_array_bound_function( annotator: &mut TypeAnnotator, parameters: Option<&AstNode>, @@ -497,7 +884,7 @@ fn generate_variable_length_array_bound_function<'ink>( Ok(ExpressionValue::RValue(bound)) } -type AnnotationFunction = fn(&mut TypeAnnotator, &AstNode, Option<&AstNode>, VisitorContext); +type AnnotationFunction = fn(&mut TypeAnnotator, &AstNode, &AstNode, Option<&AstNode>, VisitorContext); type GenericNameResolver = fn(&str, &[GenericBinding], &HashMap) -> String; type CodegenFunction = for<'ink, 'b> fn( &'b ExpressionCodeGenerator<'ink, 'b>, @@ -549,7 +936,7 @@ pub fn parse_built_ins(id_provider: IdProvider) -> CompilationUnit { unit } -/// Returns the requested functio from the builtin index or None +/// Returns the requested function from the builtin index or None pub fn get_builtin(name: &str) -> Option<&'static BuiltIn> { BUILTIN.get(name.to_uppercase().as_str()) } diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index cdde8a8afa..b0e279a18e 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -429,6 +429,14 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // if the function is builtin, generate a basic value enum for it if let Some(builtin) = self.index.get_builtin_function(implementation_name) { // adr, ref, etc. + // let parameters_list = if let Some(StatementAnnotation::ReplacementAst { statement }) = + // self.annotations.get(operator) + // { + // statement.get_as_list() + // } else { + // parameters_list + // }; + return builtin.codegen(self, parameters_list.as_slice(), operator.get_location()); } diff --git a/src/codegen/tests/compare_instructions_tests.rs b/src/codegen/tests/compare_instructions_tests.rs index 1e9cdba24c..f13e44eca7 100644 --- a/src/codegen/tests/compare_instructions_tests.rs +++ b/src/codegen/tests/compare_instructions_tests.rs @@ -319,3 +319,82 @@ fn compare_instructions_with_different_types() { ); insta::assert_snapshot!(result); } + +#[test] +fn compare_instruction_functions_with_different_types() { + let result = codegen( + " + TYPE MySubRangeInt: INT(0..500); END_TYPE + TYPE MyDint: DINT; END_TYPE + + FUNCTION foo : REAL + END_FUNCTION + + PROGRAM main + VAR + ptr_float : REF_TO REAL; + + a : MySubRangeInt; + b : MyDint; + + var_real : REAL; + var_lreal : LREAL; + + var_sint : SINT; + var_int : INT; + var_dint : DINT; + var_lint : LINT; + + var_usint : USINT; + var_uint : UINT; + var_udint : UDINT; + var_ulint : ULINT; + END_VAR + ptr_float := &(var_real); + + EQ(var_sint, var_dint) + GT(var_int, 30); + GT(10.5, var_lreal); + + NE(var_usint, var_udint); + LE(var_uint, UDINT#40); + GE(UDINT#10, var_ulint); + + EQ(var_sint, var_usint); + LE(var_uint, var_lint); + GE(var_dint, var_ulint); + + LT(var_lint, a); + GT(a, var_sint); + LT(b, var_lint); + NE(SINT#5, b, 17); + + LE(ptr_float, var_usint); + EQ(a, ptr_float); + + NE(foo(), 40.5); + LE(var_udint, foo()); + EQ(foo(), var_lint); + END_PROGRAM + ", + ); + insta::assert_snapshot!(result); +} + +#[test] +fn compare_datetime_types() { + let result = codegen( + " + PROGRAM main + VAR + var_time: TIME; + var_time_of_day: TIME_OF_DAY; + var_date: DATE; + var_date_and_time: DATE_AND_TIME; + END_VAR + GT(var_time, var_time_of_day, var_date, var_date_and_time); + END_PROGRAM + ", + ); + insta::assert_snapshot!(result); +} diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index d02831c8ae..ee284e98fc 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -569,3 +569,209 @@ fn allowed_assignable_types() { insta::assert_snapshot!(result); } + +#[test] +fn builtin_add_ints() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : DINT; + l1 : LINT; + s1 : SINT; + END_VAR + ADD(x1, x2, x3, l1, s1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_add_float() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : REAL; + l1 : LREAL; + END_VAR + ADD(x1, x2, x3, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_add_mixed() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : REAL; + l1 : LINT; + END_VAR + ADD(x1, x2, x3, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_mul_ints() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : DINT; + l1 : LINT; + s1 : SINT; + END_VAR + MUL(x1, x2, x3, l1, s1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_mul_float() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : REAL; + l1 : LREAL; + END_VAR + MUL(x1, x2, x3, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_mul_mixed() { + let src = r#" + FUNCTION main : DINT + VAR + x1, x2, x3 : REAL; + l1 : LINT; + END_VAR + MUL(x1, x2, x3, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_sub_ints() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : DINT; + l1 : LINT; + END_VAR + SUB(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_sub_float() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : REAL; + l1 : LREAL; + END_VAR + SUB(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_sub_mixed() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : REAL; + l1 : LINT; + END_VAR + SUB(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_div_ints() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : DINT; + l1 : LINT; + END_VAR + DIV(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_div_float() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : REAL; + l1 : LREAL; + END_VAR + DIV(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} + +#[test] +fn builtin_div_mixed() { + let src = r#" + FUNCTION main : DINT + VAR + x1 : REAL; + l1 : LINT; + END_VAR + DIV(x1, l1); + END_FUNCTION + "#; + + let res = codegen(src); + + insta::assert_snapshot!(res); +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_datetime_types.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_datetime_types.snap new file mode 100644 index 0000000000..1a1db62774 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_datetime_types.snap @@ -0,0 +1,44 @@ +--- +source: src/codegen/tests/compare_instructions_tests.rs +expression: result +--- +; ModuleID = 'main' +source_filename = "main" + +%main = type { i64, i64, i64, i64 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %var_time = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %var_time_of_day = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %var_date = getelementptr inbounds %main, %main* %0, i32 0, i32 2 + %var_date_and_time = getelementptr inbounds %main, %main* %0, i32 0, i32 3 + %load_var_time = load i64, i64* %var_time, align 4 + %load_var_time_of_day = load i64, i64* %var_time_of_day, align 4 + %tmpVar = icmp sgt i64 %load_var_time, %load_var_time_of_day + br i1 %tmpVar, label %1, label %2 + +1: ; preds = %entry + %load_var_time_of_day1 = load i64, i64* %var_time_of_day, align 4 + %load_var_date = load i64, i64* %var_date, align 4 + %tmpVar2 = icmp sgt i64 %load_var_time_of_day1, %load_var_date + br label %2 + +2: ; preds = %1, %entry + %3 = phi i1 [ %tmpVar, %entry ], [ %tmpVar2, %1 ] + br i1 %3, label %4, label %5 + +4: ; preds = %2 + %load_var_date3 = load i64, i64* %var_date, align 4 + %load_var_date_and_time = load i64, i64* %var_date_and_time, align 4 + %tmpVar4 = icmp sgt i64 %load_var_date3, %load_var_date_and_time + br label %5 + +5: ; preds = %4, %2 + %6 = phi i1 [ %3, %2 ], [ %tmpVar4, %4 ] + %7 = zext i1 %6 to i8 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_instruction_functions_with_different_types.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_instruction_functions_with_different_types.snap new file mode 100644 index 0000000000..94ec08efae --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__compare_instructions_tests__compare_instruction_functions_with_different_types.snap @@ -0,0 +1,128 @@ +--- +source: src/codegen/tests/compare_instructions_tests.rs +expression: result +--- +; ModuleID = 'main' +source_filename = "main" + +%main = type { float*, i16, i32, float, double, i8, i16, i32, i64, i8, i16, i32, i64 } + +@main_instance = global %main zeroinitializer + +define float @foo() { +entry: + %foo = alloca float, align 4 + store float 0.000000e+00, float* %foo, align 4 + %foo_ret = load float, float* %foo, align 4 + ret float %foo_ret +} + +define void @main(%main* %0) { +entry: + %ptr_float = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 2 + %var_real = getelementptr inbounds %main, %main* %0, i32 0, i32 3 + %var_lreal = getelementptr inbounds %main, %main* %0, i32 0, i32 4 + %var_sint = getelementptr inbounds %main, %main* %0, i32 0, i32 5 + %var_int = getelementptr inbounds %main, %main* %0, i32 0, i32 6 + %var_dint = getelementptr inbounds %main, %main* %0, i32 0, i32 7 + %var_lint = getelementptr inbounds %main, %main* %0, i32 0, i32 8 + %var_usint = getelementptr inbounds %main, %main* %0, i32 0, i32 9 + %var_uint = getelementptr inbounds %main, %main* %0, i32 0, i32 10 + %var_udint = getelementptr inbounds %main, %main* %0, i32 0, i32 11 + %var_ulint = getelementptr inbounds %main, %main* %0, i32 0, i32 12 + store float* %var_real, float** %ptr_float, align 8 + %load_var_sint = load i8, i8* %var_sint, align 1 + %1 = sext i8 %load_var_sint to i32 + %load_var_dint = load i32, i32* %var_dint, align 4 + %tmpVar = icmp eq i32 %1, %load_var_dint + %2 = zext i1 %tmpVar to i8 + %load_var_lreal = load double, double* %var_lreal, align 8 + %tmpVar1 = fcmp ogt double 1.050000e+01, %load_var_lreal + %3 = zext i1 %tmpVar1 to i8 + %load_var_usint = load i8, i8* %var_usint, align 1 + %4 = zext i8 %load_var_usint to i32 + %load_var_udint = load i32, i32* %var_udint, align 4 + %tmpVar2 = icmp ne i32 %4, %load_var_udint + %5 = zext i1 %tmpVar2 to i8 + %load_var_uint = load i16, i16* %var_uint, align 2 + %6 = zext i16 %load_var_uint to i32 + %tmpVar3 = icmp sle i32 %6, 40 + %7 = zext i1 %tmpVar3 to i8 + %load_var_ulint = load i64, i64* %var_ulint, align 4 + %tmpVar4 = icmp sge i64 10, %load_var_ulint + %8 = zext i1 %tmpVar4 to i8 + %load_var_sint5 = load i8, i8* %var_sint, align 1 + %9 = sext i8 %load_var_sint5 to i32 + %load_var_usint6 = load i8, i8* %var_usint, align 1 + %10 = zext i8 %load_var_usint6 to i32 + %tmpVar7 = icmp eq i32 %9, %10 + %11 = zext i1 %tmpVar7 to i8 + %load_var_uint8 = load i16, i16* %var_uint, align 2 + %12 = zext i16 %load_var_uint8 to i64 + %load_var_lint = load i64, i64* %var_lint, align 4 + %tmpVar9 = icmp sle i64 %12, %load_var_lint + %13 = zext i1 %tmpVar9 to i8 + %load_var_dint10 = load i32, i32* %var_dint, align 4 + %14 = sext i32 %load_var_dint10 to i64 + %load_var_ulint11 = load i64, i64* %var_ulint, align 4 + %tmpVar12 = icmp sge i64 %14, %load_var_ulint11 + %15 = zext i1 %tmpVar12 to i8 + %load_var_lint13 = load i64, i64* %var_lint, align 4 + %load_a = load i16, i16* %a, align 2 + %16 = sext i16 %load_a to i64 + %tmpVar14 = icmp slt i64 %load_var_lint13, %16 + %17 = zext i1 %tmpVar14 to i8 + %load_a15 = load i16, i16* %a, align 2 + %18 = sext i16 %load_a15 to i32 + %load_var_sint16 = load i8, i8* %var_sint, align 1 + %19 = sext i8 %load_var_sint16 to i32 + %tmpVar17 = icmp sgt i32 %18, %19 + %20 = zext i1 %tmpVar17 to i8 + %load_b = load i32, i32* %b, align 4 + %21 = sext i32 %load_b to i64 + %load_var_lint18 = load i64, i64* %var_lint, align 4 + %tmpVar19 = icmp slt i64 %21, %load_var_lint18 + %22 = zext i1 %tmpVar19 to i8 + %load_b20 = load i32, i32* %b, align 4 + %tmpVar21 = icmp ne i32 5, %load_b20 + br i1 %tmpVar21, label %23, label %24 + +23: ; preds = %entry + %load_b22 = load i32, i32* %b, align 4 + %tmpVar23 = icmp ne i32 %load_b22, 17 + br label %24 + +24: ; preds = %23, %entry + %25 = phi i1 [ %tmpVar21, %entry ], [ %tmpVar23, %23 ] + %26 = zext i1 %25 to i8 + %load_ptr_float = load float*, float** %ptr_float, align 8 + %load_var_usint24 = load i8, i8* %var_usint, align 1 + %27 = zext i8 %load_var_usint24 to i64 + %28 = ptrtoint float* %load_ptr_float to i64 + %tmpVar25 = icmp sle i64 %28, %27 + %29 = zext i1 %tmpVar25 to i8 + %load_a26 = load i16, i16* %a, align 2 + %30 = sext i16 %load_a26 to i64 + %load_ptr_float27 = load float*, float** %ptr_float, align 8 + %31 = ptrtoint float* %load_ptr_float27 to i64 + %tmpVar28 = icmp eq i64 %30, %31 + %32 = zext i1 %tmpVar28 to i8 + %call = call float @foo() + %tmpVar29 = fcmp one float %call, 4.050000e+01 + %33 = zext i1 %tmpVar29 to i8 + %load_var_udint30 = load i32, i32* %var_udint, align 4 + %34 = uitofp i32 %load_var_udint30 to float + %call31 = call float @foo() + %tmpVar32 = fcmp ole float %34, %call31 + %35 = zext i1 %tmpVar32 to i8 + %call33 = call float @foo() + %36 = fpext float %call33 to double + %load_var_lint34 = load i64, i64* %var_lint, align 4 + %37 = sitofp i64 %load_var_lint34 to double + %tmpVar35 = fcmp oeq double %36, %37 + %38 = zext i1 %tmpVar35 to i8 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_float.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_float.snap new file mode 100644 index 0000000000..753ff39f42 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_float.snap @@ -0,0 +1,31 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %x2 = alloca float, align 4 + %x3 = alloca float, align 4 + %l1 = alloca double, align 8 + store float 0.000000e+00, float* %x1, align 4 + store float 0.000000e+00, float* %x2, align 4 + store float 0.000000e+00, float* %x3, align 4 + store double 0.000000e+00, double* %l1, align 8 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %load_x2 = load float, float* %x2, align 4 + %tmpVar = fadd float %load_x1, %load_x2 + %load_x3 = load float, float* %x3, align 4 + %tmpVar1 = fadd float %tmpVar, %load_x3 + %0 = fpext float %tmpVar1 to double + %load_l1 = load double, double* %l1, align 8 + %tmpVar2 = fadd double %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_ints.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_ints.snap new file mode 100644 index 0000000000..a34337745d --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_ints.snap @@ -0,0 +1,36 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca i32, align 4 + %x2 = alloca i32, align 4 + %x3 = alloca i32, align 4 + %l1 = alloca i64, align 8 + %s1 = alloca i8, align 1 + store i32 0, i32* %x1, align 4 + store i32 0, i32* %x2, align 4 + store i32 0, i32* %x3, align 4 + store i64 0, i64* %l1, align 4 + store i8 0, i8* %s1, align 1 + store i32 0, i32* %main, align 4 + %load_x1 = load i32, i32* %x1, align 4 + %load_x2 = load i32, i32* %x2, align 4 + %tmpVar = add i32 %load_x1, %load_x2 + %load_x3 = load i32, i32* %x3, align 4 + %tmpVar1 = add i32 %tmpVar, %load_x3 + %0 = sext i32 %tmpVar1 to i64 + %load_l1 = load i64, i64* %l1, align 4 + %tmpVar2 = add i64 %0, %load_l1 + %load_s1 = load i8, i8* %s1, align 1 + %1 = sext i8 %load_s1 to i64 + %tmpVar3 = add i64 %tmpVar2, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_mixed.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_mixed.snap new file mode 100644 index 0000000000..df13579370 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_add_mixed.snap @@ -0,0 +1,32 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %x2 = alloca float, align 4 + %x3 = alloca float, align 4 + %l1 = alloca i64, align 8 + store float 0.000000e+00, float* %x1, align 4 + store float 0.000000e+00, float* %x2, align 4 + store float 0.000000e+00, float* %x3, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %load_x2 = load float, float* %x2, align 4 + %tmpVar = fadd float %load_x1, %load_x2 + %load_x3 = load float, float* %x3, align 4 + %tmpVar1 = fadd float %tmpVar, %load_x3 + %0 = fpext float %tmpVar1 to double + %load_l1 = load i64, i64* %l1, align 4 + %1 = sitofp i64 %load_l1 to double + %tmpVar2 = fadd double %0, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_float.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_float.snap new file mode 100644 index 0000000000..834715e7a4 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_float.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %l1 = alloca double, align 8 + store float 0.000000e+00, float* %x1, align 4 + store double 0.000000e+00, double* %l1, align 8 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %0 = fpext float %load_x1 to double + %load_l1 = load double, double* %l1, align 8 + %tmpVar = fdiv double %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_ints.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_ints.snap new file mode 100644 index 0000000000..4261e8474d --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_ints.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca i32, align 4 + %l1 = alloca i64, align 8 + store i32 0, i32* %x1, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load i32, i32* %x1, align 4 + %0 = sext i32 %load_x1 to i64 + %load_l1 = load i64, i64* %l1, align 4 + %tmpVar = sdiv i64 %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_mixed.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_mixed.snap new file mode 100644 index 0000000000..4b763a2ff0 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_mixed.snap @@ -0,0 +1,24 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %l1 = alloca i64, align 8 + store float 0.000000e+00, float* %x1, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %0 = fpext float %load_x1 to double + %load_l1 = load i64, i64* %l1, align 4 + %1 = sitofp i64 %load_l1 to double + %tmpVar = fdiv double %0, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_float.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_float.snap new file mode 100644 index 0000000000..8d0e2f5417 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_float.snap @@ -0,0 +1,31 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %x2 = alloca float, align 4 + %x3 = alloca float, align 4 + %l1 = alloca double, align 8 + store float 0.000000e+00, float* %x1, align 4 + store float 0.000000e+00, float* %x2, align 4 + store float 0.000000e+00, float* %x3, align 4 + store double 0.000000e+00, double* %l1, align 8 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %load_x2 = load float, float* %x2, align 4 + %tmpVar = fmul float %load_x1, %load_x2 + %load_x3 = load float, float* %x3, align 4 + %tmpVar1 = fmul float %tmpVar, %load_x3 + %0 = fpext float %tmpVar1 to double + %load_l1 = load double, double* %l1, align 8 + %tmpVar2 = fmul double %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_ints.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_ints.snap new file mode 100644 index 0000000000..66a2f3ef86 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_ints.snap @@ -0,0 +1,36 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca i32, align 4 + %x2 = alloca i32, align 4 + %x3 = alloca i32, align 4 + %l1 = alloca i64, align 8 + %s1 = alloca i8, align 1 + store i32 0, i32* %x1, align 4 + store i32 0, i32* %x2, align 4 + store i32 0, i32* %x3, align 4 + store i64 0, i64* %l1, align 4 + store i8 0, i8* %s1, align 1 + store i32 0, i32* %main, align 4 + %load_x1 = load i32, i32* %x1, align 4 + %load_x2 = load i32, i32* %x2, align 4 + %tmpVar = mul i32 %load_x1, %load_x2 + %load_x3 = load i32, i32* %x3, align 4 + %tmpVar1 = mul i32 %tmpVar, %load_x3 + %0 = sext i32 %tmpVar1 to i64 + %load_l1 = load i64, i64* %l1, align 4 + %tmpVar2 = mul i64 %0, %load_l1 + %load_s1 = load i8, i8* %s1, align 1 + %1 = sext i8 %load_s1 to i64 + %tmpVar3 = mul i64 %tmpVar2, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_mixed.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_mixed.snap new file mode 100644 index 0000000000..e1b5769b95 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_mul_mixed.snap @@ -0,0 +1,32 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %x2 = alloca float, align 4 + %x3 = alloca float, align 4 + %l1 = alloca i64, align 8 + store float 0.000000e+00, float* %x1, align 4 + store float 0.000000e+00, float* %x2, align 4 + store float 0.000000e+00, float* %x3, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %load_x2 = load float, float* %x2, align 4 + %tmpVar = fmul float %load_x1, %load_x2 + %load_x3 = load float, float* %x3, align 4 + %tmpVar1 = fmul float %tmpVar, %load_x3 + %0 = fpext float %tmpVar1 to double + %load_l1 = load i64, i64* %l1, align 4 + %1 = sitofp i64 %load_l1 to double + %tmpVar2 = fmul double %0, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_float.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_float.snap new file mode 100644 index 0000000000..5d25f72be2 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_float.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %l1 = alloca double, align 8 + store float 0.000000e+00, float* %x1, align 4 + store double 0.000000e+00, double* %l1, align 8 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %0 = fpext float %load_x1 to double + %load_l1 = load double, double* %l1, align 8 + %tmpVar = fsub double %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_ints.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_ints.snap new file mode 100644 index 0000000000..1fb0aa6c64 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_ints.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca i32, align 4 + %l1 = alloca i64, align 8 + store i32 0, i32* %x1, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load i32, i32* %x1, align 4 + %0 = sext i32 %load_x1 to i64 + %load_l1 = load i64, i64* %l1, align 4 + %tmpVar = sub i64 %0, %load_l1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_mixed.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_mixed.snap new file mode 100644 index 0000000000..32df16613a --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_mixed.snap @@ -0,0 +1,24 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x1 = alloca float, align 4 + %l1 = alloca i64, align 8 + store float 0.000000e+00, float* %x1, align 4 + store i64 0, i64* %l1, align 4 + store i32 0, i32* %main, align 4 + %load_x1 = load float, float* %x1, align 4 + %0 = fpext float %load_x1 to double + %load_l1 = load i64, i64* %l1, align 4 + %1 = sitofp i64 %load_l1 to double + %tmpVar = fsub double %0, %1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} + diff --git a/src/resolver.rs b/src/resolver.rs index f2f8bcd980..d20f2c5606 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -78,7 +78,7 @@ pub struct VisitorContext<'s> { /// true the visitor entered a body (so no declarations) in_body: bool, - id_provider: IdProvider, + pub id_provider: IdProvider, // what's the current strategy for resolving resolve_strategy: Vec, @@ -282,6 +282,120 @@ impl TypeAnnotator<'_> { ctx.id_provider.next_id(), )) } + + pub fn annotate_call_statement( + &mut self, + operator: &AstNode, + parameters_stmt: Option<&AstNode>, + ctx: &VisitorContext, + ) { + let parameters = if let Some(parameters) = parameters_stmt { + self.visit_statement(ctx, parameters); + flatten_expression_list(parameters) + } else { + vec![] + }; + let operator_qualifier = &self.get_call_name(operator); + + let mut generics_candidates: HashMap> = HashMap::new(); + let mut params = vec![]; + let mut parameters = parameters.into_iter(); + + // If we are dealing with an action call statement, we need to get the declared parameters from the parent POU in order + // to annotate them with the correct type hint. + let operator_qualifier = self + .index + .find_implementation_by_name(operator_qualifier) + .map(|it| it.get_type_name()) + .unwrap_or(operator_qualifier); + + for m in self.index.get_declared_parameters(operator_qualifier).into_iter() { + if let Some(p) = parameters.next() { + let type_name = m.get_type_name(); + if let Some((key, candidate)) = + TypeAnnotator::get_generic_candidate(self.index, &self.annotation_map, type_name, p) + { + generics_candidates.entry(key.to_string()).or_default().push(candidate.to_string()) + } else { + params.push((p, type_name.to_string())) + } + } + } + + //We possibly did not consume all parameters, see if the variadic arguments are derivable + match self.index.find_pou(operator_qualifier) { + Some(pou) if pou.is_variadic() => { + //get variadic argument type, if it is generic, update the generic candidates + if let Some(type_name) = + self.index.get_variadic_member(pou.get_name()).map(VariableIndexEntry::get_type_name) + { + for parameter in parameters { + if let Some((key, candidate)) = TypeAnnotator::get_generic_candidate( + self.index, + &self.annotation_map, + type_name, + parameter, + ) { + generics_candidates + .entry(key.to_string()) + .or_default() + .push(candidate.to_string()) + } else { + // intrinsic type promotion for variadics in order to be compatible with the C standard. + // see ISO/IEC 9899:1999, 6.5.2.2 Function calls (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) + // or https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion + // for more about default argument promotion. + + // varargs without a type declaration will be annotated "VOID", so in order to check if a + // promotion is necessary, we need to first check the type of each parameter. in the case of numerical + // types, we promote if the type is smaller than double/i32 (except for booleans). + let type_name = if let Some(data_type) = + self.annotation_map.get_type(parameter, self.index) + { + match &data_type.information { + DataTypeInformation::Float { .. } => get_bigger_type( + data_type, + self.index.get_type_or_panic(LREAL_TYPE), + self.index, + ) + .get_name(), + DataTypeInformation::Integer { .. } + if !&data_type.information.is_bool() => + { + get_bigger_type( + data_type, + self.index.get_type_or_panic(DINT_TYPE), + self.index, + ) + .get_name() + } + _ => type_name, + } + } else { + // default to original type in case no type could be found + // and let the validator handle situations that might lead here + type_name + }; + + params.push((parameter, type_name.to_string())); + } + } + } + } + _ => {} + } + for (p, name) in params { + self.annotate_parameters(p, &name); + } + //Attempt to resolve the generic signature here + self.update_generic_call_statement( + generics_candidates, + operator_qualifier, + operator, + parameters_stmt, + ctx.to_owned(), + ); + } } #[derive(Debug, Clone, PartialEq)] @@ -791,7 +905,7 @@ impl<'i> TypeAnnotator<'i> { /// updates the expected types of statements on the right side of an assignment /// e.g. x : ARRAY [0..1] OF BYTE := [2,3]; - fn update_expected_types(&mut self, expected_type: &typesystem::DataType, statement: &AstNode) { + pub fn update_expected_types(&mut self, expected_type: &typesystem::DataType, statement: &AstNode) { //see if we need to dive into it match statement.get_stmt() { AstStatement::Literal(AstLiteral::Array(Array { elements: Some(elements) }), ..) => { @@ -1634,125 +1748,29 @@ impl<'i> TypeAnnotator<'i> { //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 { + if let Some(parameters) = parameters_stmt { self.visit_statement(&ctx, parameters); - flatten_expression_list(parameters) - } else { - vec![] }; + if let Some(annotation) = builtins::get_builtin(&operator_qualifier).and_then(BuiltIn::get_annotation) { - annotation(self, operator, parameters_stmt, ctx.to_owned()) + annotation(self, statement, operator, parameters_stmt, ctx.to_owned()); } else { - //If builtin, skip this - let mut generics_candidates: HashMap> = HashMap::new(); - let mut params = vec![]; - let mut parameters = parameters.into_iter(); - - // If we are dealing with an action call statement, we need to get the declared parameters from the parent POU in order - // to annotate them with the correct type hint. - let operator_qualifier = self - .index - .find_implementation_by_name(&operator_qualifier) - .map(|it| it.get_type_name()) - .unwrap_or(operator_qualifier.as_str()); - - for m in self.index.get_declared_parameters(operator_qualifier).into_iter() { - if let Some(p) = parameters.next() { - let type_name = m.get_type_name(); - if let Some((key, candidate)) = - TypeAnnotator::get_generic_candidate(self.index, &self.annotation_map, type_name, p) - { - generics_candidates - .entry(key.to_string()) - .or_insert_with(std::vec::Vec::new) - .push(candidate.to_string()) - } else { - params.push((p, type_name.to_string())) - } - } - } - //We possibly did not consume all parameters, see if the variadic arguments are derivable - - match self.index.find_pou(operator_qualifier) { - Some(pou) if pou.is_variadic() => { - //get variadic argument type, if it is generic, update the generic candidates - if let Some(type_name) = - self.index.get_variadic_member(pou.get_name()).map(VariableIndexEntry::get_type_name) - { - for parameter in parameters { - if let Some((key, candidate)) = TypeAnnotator::get_generic_candidate( - self.index, - &self.annotation_map, - type_name, - parameter, - ) { - generics_candidates - .entry(key.to_string()) - .or_insert_with(std::vec::Vec::new) - .push(candidate.to_string()) - } else { - // intrinsic type promotion for variadics in order to be compatible with the C standard. - // see ISO/IEC 9899:1999, 6.5.2.2 Function calls (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) - // or https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion - // for more about default argument promotion. - - // varargs without a type declaration will be annotated "VOID", so in order to check if a - // promotion is necessary, we need to first check the type of each parameter. in the case of numerical - // types, we promote if the type is smaller than double/i32 (except for booleans). - let type_name = if let Some(data_type) = - self.annotation_map.get_type(parameter, self.index) - { - match &data_type.information { - DataTypeInformation::Float { .. } => get_bigger_type( - data_type, - self.index.get_type_or_panic(LREAL_TYPE), - self.index, - ) - .get_name(), - DataTypeInformation::Integer { .. } - if !&data_type.information.is_bool() => - { - get_bigger_type( - data_type, - self.index.get_type_or_panic(DINT_TYPE), - self.index, - ) - .get_name() - } - _ => type_name, - } - } else { - // default to original type in case no type could be found - // and let the validator handle situations that might lead here - type_name - }; + //This is skipped for builtins that provide their own annotation-logic + self.annotate_call_statement(operator, parameters_stmt, &ctx); + }; - params.push((parameter, type_name.to_string())); - } - } - } - } - _ => {} - } - for (p, name) in params { - self.annotate_parameters(p, &name); - } - //Attempt to resolve the generic signature here - self.update_generic_call_statement( - generics_candidates, - operator_qualifier, - operator, - parameters_stmt, - ctx.to_owned(), - ); - } if let Some(StatementAnnotation::Function { return_type, .. }) = self.annotation_map.get(operator) { if let Some(return_type) = self .index .find_effective_type_by_name(return_type) .or_else(|| self.annotation_map.new_index.find_effective_type_by_name(return_type)) { + if let Some(StatementAnnotation::ReplacementAst { .. }) = self.annotation_map.get(statement) { + // if we have a replacement ast, we do not need to annotate the function return type as it would + // overwrite the replacement ast + return; + } self.annotate(statement, StatementAnnotation::value(return_type.get_name())); } } @@ -1990,7 +2008,7 @@ fn get_int_type_name_for(value: i128) -> &'static str { fn get_real_type_name_for(value: &str) -> &'static str { let parsed = value.parse::().unwrap_or(f32::INFINITY); - if parsed == f32::INFINITY || parsed == f32::NEG_INFINITY { + if parsed.is_infinite() { return LREAL_TYPE; } diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 9e40af983e..4997f48006 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -5313,3 +5313,155 @@ fn annotate_method_in_super() { ); } } + +// these test checks if builtin calls to GT, LT, GE, LE, EQ, NE are annotated correctly +#[test] +fn comparison_function_replacement_ast_is_identical_to_using_symbols() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + FUNCTION foo: DINT + VAR + a: DINT; + b: DINT; + c: REAL; + d: DATE; + END_VAR + a > b AND b > c AND c > d; + GT(a, b, c, d); + a <= b AND b <= c AND c <= d; + LE(a, b, c, d); + END_FUNCTION + ", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + // check if a > b AND b > c AND c < d produces the same AST to GT(a, b, c, d) + let stmt = &unit.implementations[0].statements[0]; + let AstNode { stmt: expected, .. } = stmt; + + let stmt = &unit.implementations[0].statements[1]; + let Some(StatementAnnotation::ReplacementAst { statement }) = annotations.get(stmt) else { + unreachable!() + }; + let AstNode { stmt: actual, .. } = statement; + + assert_eq!(format!("{:#?}", expected), format!("{:#?}", actual)); + + // check if a <= b AND b <= c AND c <= d produces the same AST to LE(a, b, c, d) + let stmt = &unit.implementations[0].statements[2]; + let AstNode { stmt: expected, .. } = stmt; + + let stmt = &unit.implementations[0].statements[3]; + let Some(StatementAnnotation::ReplacementAst { statement }) = annotations.get(stmt) else { + unreachable!() + }; + let AstNode { stmt: actual, .. } = statement; + + assert_eq!(format!("{:#?}", expected), format!("{:#?}", actual)) +} + +#[test] +fn builtin_gt_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("GT")); +} +#[test] +fn builtin_ge_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("GE")); +} + +#[test] +fn builtin_eq_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("EQ")); +} + +#[test] +fn builtin_lt_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("LT")); +} + +#[test] +fn builtin_le_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("LE")); +} + +#[test] +fn builtin_ne_replacement_ast() { + insta::assert_debug_snapshot!(generate_comparison_test("NE")); +} + +// Helper function to generate the comparison test +fn generate_comparison_test(operator: &str) -> StatementAnnotation { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + format!( + " + FUNCTION main : DINT + VAR + a : DINT; + b : INT; + c : LINT; + d : LWORD; + END_VAR + {}(a, b, c, d); + END_FUNCTION + ", + operator + ), + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + let stmt = &unit.implementations[0].statements[0]; + annotations.get(stmt).unwrap().clone() +} + +#[test] +fn builtin_add_replacement_ast() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + FUNCTION main : DINT + VAR + a : DINT; + b : INT; + c : LINT; + d : REAL; + END_VAR + ADD(a, b, c, d); + END_FUNCTION + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + let stmt = &unit.implementations[0].statements[0]; + insta::assert_debug_snapshot!(annotations.get(stmt)); +} + +#[test] +fn builtin_add_doesnt_annotate_replacement_ast_when_called_with_incorrect_type_nature() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + FUNCTION main : DINT + VAR + a : DINT; + b : INT; + c : LWORD; + d : TIME; + END_VAR + ADD(a, b, c, d); + END_FUNCTION + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + let stmt = &unit.implementations[0].statements[0]; + if let Some(StatementAnnotation::ReplacementAst { statement }) = annotations.get(stmt) { + panic!("Expected no replacement ast, got {:?}", statement) + } +} diff --git a/src/resolver/tests/resolve_generic_calls.rs b/src/resolver/tests/resolve_generic_calls.rs index cda9f0ac68..706f8e525c 100644 --- a/src/resolver/tests/resolve_generic_calls.rs +++ b/src/resolver/tests/resolve_generic_calls.rs @@ -1,3 +1,4 @@ +use insta::assert_debug_snapshot; use plc_ast::{ ast::{flatten_expression_list, Assignment, AstNode, AstStatement, CallStatement}, provider::IdProvider, @@ -1093,18 +1094,8 @@ fn generic_function_sharing_a_datatype_name_resolves() { let annotations = annotate_with_ids(&unit, &mut index, id_provider); let statement = &unit.implementations[1].statements[0]; - if let AstNode { stmt: AstStatement::CallStatement(CallStatement { operator, .. }, ..), .. } = statement { - assert_eq!( - annotations.get(operator).unwrap(), - &StatementAnnotation::Function { - return_type: "BOOL".to_string(), - qualified_name: "LT".to_string(), - call_name: Some("LT__STRING".to_string()), - } - ); - } else { - unreachable!("This should always be a call statement.") - } + // comparision function calls are resolved to replacement-AST expressions + assert_debug_snapshot!(annotations.get(statement)); } #[test] diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap new file mode 100644 index 0000000000..40d414dba3 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap @@ -0,0 +1,49 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: annotations.get(operator) +--- +Some( + ReplacementAst { + statement: BinaryExpression { + operator: Plus, + left: BinaryExpression { + operator: Plus, + left: BinaryExpression { + operator: Plus, + 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/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap new file mode 100644 index 0000000000..15041598cf --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"EQ\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap new file mode 100644 index 0000000000..547721a657 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"GE\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: GreaterOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: GreaterOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: GreaterOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap new file mode 100644 index 0000000000..70d1cdabee --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"GT\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: Greater, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap new file mode 100644 index 0000000000..716bcf500c --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"LE\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: LessOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: LessOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: LessOrEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap new file mode 100644 index 0000000000..46c4292cc0 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"LT\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: Less, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap new file mode 100644 index 0000000000..439e709eff --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap @@ -0,0 +1,69 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "generate_comparison_test(\"NE\")" +--- +ReplacementAst { + statement: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: And, + left: BinaryExpression { + operator: NotEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "a", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + }, + right: BinaryExpression { + operator: NotEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + }, + }, + right: BinaryExpression { + operator: NotEqual, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "c", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "d", + }, + ), + base: None, + }, + }, + }, +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_generic_calls__generic_function_sharing_a_datatype_name_resolves.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_generic_calls__generic_function_sharing_a_datatype_name_resolves.snap new file mode 100644 index 0000000000..60ce64f830 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_generic_calls__generic_function_sharing_a_datatype_name_resolves.snap @@ -0,0 +1,19 @@ +--- +source: src/resolver/tests/resolve_generic_calls.rs +expression: annotations.get(operator) +--- +Some( + ReplacementAst { + statement: BinaryExpression { + operator: Less, + left: LiteralString { + value: "hello", + is_wide: false, + }, + right: LiteralString { + value: "world", + is_wide: false, + }, + }, + }, +) diff --git a/src/validation/array.rs b/src/validation/array.rs index d96bf767c2..ab6f9ef523 100644 --- a/src/validation/array.rs +++ b/src/validation/array.rs @@ -31,8 +31,12 @@ pub(super) fn validate_array_assignment( context: &ValidationContext, wrapper: Wrapper, ) { - let Some(lhs_type) = wrapper.datatype_info_lhs(context) else { return; }; - let Some(rhs_stmt) = wrapper.get_rhs() else { return; }; + let Some(lhs_type) = wrapper.datatype_info_lhs(context) else { + return; + }; + let Some(rhs_stmt) = wrapper.get_rhs() else { + return; + }; if !lhs_type.is_array() { return; @@ -75,15 +79,23 @@ fn validate_array_of_structs( lhs_type: &DataTypeInformation, rhs_stmt: &AstNode, ) { - let Some(array_type_name) = lhs_type.get_inner_array_type_name() else { return; }; - let Some(dti) = context.index.find_effective_type_by_name(array_type_name) else { return; }; + let Some(array_type_name) = lhs_type.get_inner_array_type_name() else { + return; + }; + let Some(dti) = context.index.find_effective_type_by_name(array_type_name) else { + return; + }; if !dti.is_struct() { return; } - let AstStatement::Literal(AstLiteral::Array(array)) = rhs_stmt.get_stmt() else { return; }; - let Some(elements) = array.elements().map(AstNode::get_stmt) else { return; }; + let AstStatement::Literal(AstLiteral::Array(array)) = rhs_stmt.get_stmt() else { + return; + }; + let Some(elements) = array.elements().map(AstNode::get_stmt) else { + return; + }; match elements { AstStatement::ExpressionList(expressions) => { @@ -143,7 +155,9 @@ impl<'a> Wrapper<'a> { { match self { Wrapper::Statement(statement) => { - let AstNode { stmt: AstStatement::Assignment(data), .. } = statement else { return None; }; + let AstNode { stmt: AstStatement::Assignment(data), .. } = statement else { + return None; + }; context.annotations.get_type(&data.left, context.index).map(|it| it.get_type_information()) } diff --git a/src/validation/statement.rs b/src/validation/statement.rs index d45b93f307..a1b1d83b17 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -907,13 +907,13 @@ fn validate_call( let mut variable_location_in_parent = vec![]; // validate parameters - for (i, p) in passed_parameters.iter().enumerate() { + for (i, param) in passed_parameters.iter().enumerate() { if let Ok((parameter_location_in_parent, right, is_implicit)) = - get_implicit_call_parameter(p, &declared_parameters, i) + get_implicit_call_parameter(param, &declared_parameters, i) { let left = declared_parameters.get(parameter_location_in_parent); if let Some(left) = left { - validate_call_by_ref(validator, left, p); + validate_call_by_ref(validator, left, param); // 'parameter location in parent' and 'variable location in parent' are not the same (e.g VAR blocks are not counted as param). // save actual location in parent for InOut validation variable_location_in_parent.push(left.get_location_in_parent()); @@ -922,7 +922,7 @@ fn validate_call( // explicit call parameter assignments will be handled by // `visit_statement()` via `Assignment` and `OutputAssignment` if is_implicit { - validate_assignment(validator, right, None, &p.get_location(), context); + validate_assignment(validator, right, None, ¶m.get_location(), context); } // mixing implicit and explicit parameters is not allowed @@ -930,11 +930,11 @@ fn validate_call( if i == 0 { are_implicit_parameters = is_implicit; } else if are_implicit_parameters != is_implicit { - validator.push_diagnostic(Diagnostic::invalid_parameter_type(p.get_location())); + validator.push_diagnostic(Diagnostic::invalid_parameter_type(param.get_location())); } } - visit_statement(validator, p, context); + visit_statement(validator, param, context); } // for PROGRAM/FB we need special inout validation diff --git a/src/validation/tests.rs b/src/validation/tests.rs index 58a1b90635..2b27ea2eb0 100644 --- a/src/validation/tests.rs +++ b/src/validation/tests.rs @@ -2,6 +2,7 @@ mod array_validation_test; mod assignment_validation_tests; mod bitaccess_validation_test; +mod builtin_validation_tests; mod duplicates_validation_test; mod generic_validation_tests; mod literals_validation_tests; diff --git a/src/validation/tests/builtin_validation_tests.rs b/src/validation/tests/builtin_validation_tests.rs new file mode 100644 index 0000000000..4f61b1a0e3 --- /dev/null +++ b/src/validation/tests/builtin_validation_tests.rs @@ -0,0 +1,105 @@ +use crate::{assert_validation_snapshot, test_utils::tests::parse_and_validate}; + +#[test] +fn arithmetic_builtins_allow_mixing_of_fp_and_int_params() { + let diagnostics = parse_and_validate( + " + FUNCTION main : LINT + VAR + i1, i2 : DINT; + f1, f2 : LREAL; + res_i : DINT; + res_fp: LREAL; + END_VAR + res_i := ADD(i1, i2, f1, f2); + res_fp := MUL(i1, i2, f1, f2); + res_i := SUB(i1, f2); + res_fp := DIV(i1, f2); + END_FUNCTION + ", + ); + + assert!(diagnostics.is_empty()); +} + +#[test] +#[ignore = "FIXME: no validation for incompatible types for arithmetic operations"] +fn arithmetic_builtins_called_with_incompatible_types() { + let diagnostics = parse_and_validate( + " + FUNCTION main : DINT + VAR + x1 : ARRAY[0..2] OF TOD; + x2 : STRING; + END_VAR + x1 + x2; // will currently also validate without errors + ADD(x1, x1); + DIV(x1, x2); + SUB(x2, x2); + END_FUNCTION + ", + ); + + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn arithmetic_builtins_called_with_invalid_param_count() { + let diagnostics = parse_and_validate( + " + FUNCTION main : DINT + VAR + x1 : DINT; + x2 : REAL; + END_VAR + ADD(); + MUL(x1); + DIV(x2, x2, x1, x2); // DIV and SUB are not extensible + SUB(x2, x2, x1, x2); + END_FUNCTION + ", + ); + + assert_validation_snapshot!(&diagnostics); +} + +#[test] +#[ignore = "FIXME: no validation for incompatible type comparisons"] +fn comparison_builtins_called_with_incompatible_types() { + let diagnostics = parse_and_validate( + " + FUNCTION main : DINT + VAR + x1 : ARRAY[0..2] OF TOD; + x2 : STRING; + END_VAR + x1 > x2; + EQ(x1, x1); + GT(x1, x2); + NE(x2, x2); + END_FUNCTION + ", + ); + + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn comparison_builtins_called_with_invalid_param_count() { + let diagnostics = parse_and_validate( + " + FUNCTION main : DINT + VAR + x1 : DINT; + x2 : REAL; + END_VAR + EQ(); + GT(x1); + LE(x2, x2, x1, x2); // OK + NE(x2, x2, x1, x2); // NE is not extensible + END_FUNCTION + ", + ); + + assert_validation_snapshot!(&diagnostics); +} diff --git a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap new file mode 100644 index 0000000000..4e5c52b823 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap @@ -0,0 +1,10 @@ +--- +source: src/validation/tests/builtin_validation_tests.rs +expression: res +--- +SyntaxError { message: "Invalid parameter count. Received 0 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 6, column: 12, offset: 116 }..TextLocation { line: 6, column: 15, offset: 119 }) }], err_no: call__invalid_parameter_count } +SyntaxError { message: "Could not resolve generic type T with nature Num", range: [SourceLocation { span: Range(TextLocation { line: 6, column: 12, offset: 116 }..TextLocation { line: 6, column: 18, offset: 122 }) }], err_no: type__unresolved_generic } +SyntaxError { message: "Invalid parameter count. Received 1 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 7, column: 12, offset: 135 }..TextLocation { line: 7, column: 15, offset: 138 }) }], err_no: call__invalid_parameter_count } +SyntaxError { message: "Invalid parameter count. Received 4 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 8, column: 12, offset: 156 }..TextLocation { line: 8, column: 15, offset: 159 }) }], err_no: call__invalid_parameter_count } +SyntaxError { message: "Invalid parameter count. Received 4 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 9, column: 12, offset: 223 }..TextLocation { line: 9, column: 15, offset: 226 }) }], err_no: call__invalid_parameter_count } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap new file mode 100644 index 0000000000..db7017f941 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap @@ -0,0 +1,8 @@ +--- +source: src/validation/tests/builtin_validation_tests.rs +expression: res +--- +SyntaxError { message: "Invalid parameter count. Received 0 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 6, column: 12, offset: 116 }..TextLocation { line: 6, column: 14, offset: 118 }) }], err_no: call__invalid_parameter_count } +SyntaxError { message: "Invalid parameter count. Received 1 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 7, column: 12, offset: 134 }..TextLocation { line: 7, column: 14, offset: 136 }) }], err_no: call__invalid_parameter_count } +SyntaxError { message: "Invalid parameter count. Received 4 parameters while 2 parameters were expected.", range: [SourceLocation { span: Range(TextLocation { line: 9, column: 12, offset: 192 }..TextLocation { line: 9, column: 14, offset: 194 }) }], err_no: call__invalid_parameter_count } + diff --git a/tests/correctness/arithmetic_functions/addition.rs b/tests/correctness/arithmetic_functions/addition.rs new file mode 100644 index 0000000000..490c4b2df5 --- /dev/null +++ b/tests/correctness/arithmetic_functions/addition.rs @@ -0,0 +1,51 @@ +use crate::correctness::math_operators::addition::approx_equal; +use driver::runner::compile_and_run_no_params; + +#[test] +fn builtin_add_with_ints() { + let prog = r#" + FUNCTION main : LINT + VAR_TEMP + x1 : ARRAY[0..3] OF DINT := (1, 2, 3, 4); + l1 : LINT := 1000; + s1 : SINT := 1; + END_VAR + main := ADD(x1[0], x1[1], x1[2], x1[3], l1, s1); + END_FUNCTION + "#; + + let res: i64 = compile_and_run_no_params(prog.to_string()); + assert_eq!(res, 1011); +} + +#[test] +fn builtin_add_with_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR_TEMP + x1 : ARRAY[0..3] OF REAL := (1.0, 2.2, 3.4, 4.1); + x2 : LREAL := 1000.9; + END_VAR + main := ADD(x1[0], x1[1], x1[2], x1[3], x2); + END_FUNCTION + "#; + + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(1011.6, res, 1)); +} + +#[test] +fn builtin_add_with_ints_and_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR_TEMP + x1 : ARRAY[0..3] OF DINT := (1, 2, 3, 4); + x2 : LREAL := 1000.9; + END_VAR + main := ADD(x1[0], x1[1], x1[2], x1[3], x2); + END_FUNCTION + "#; + + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(1010.9, res, 1)); +} diff --git a/tests/correctness/arithmetic_functions/division.rs b/tests/correctness/arithmetic_functions/division.rs new file mode 100644 index 0000000000..dea9dfab55 --- /dev/null +++ b/tests/correctness/arithmetic_functions/division.rs @@ -0,0 +1,58 @@ +use crate::correctness::math_operators::addition::approx_equal; +use driver::runner::compile_and_run; + +struct MainType; + +#[test] +fn builtin_div_with_ints() { + let prog = r#" + FUNCTION main : LINT + VAR + x1 : DINT := 1000; + l1 : LINT := 333; + END_VAR + main := DIV(x1, l1); + END_FUNCTION + "#; + + let mut main = MainType {}; + + let res: i64 = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, 3); +} + +#[test] +fn builtin_div_with_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : REAL := 10.0; + x2 : LREAL := 1000.0; + END_VAR + main := DIV(x1, x2); + END_FUNCTION + "#; + + let mut main = MainType {}; + + let res: f64 = compile_and_run(prog.to_string(), &mut main); + assert!(approx_equal(0.01, res, 2)); +} + +#[test] +fn builtin_div_with_ints_and_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : DINT := 20; + x2 : LREAL := 1000.0; + END_VAR + main := DIV(x1, x2); + END_FUNCTION + "#; + + let mut main = MainType {}; + + let res: f64 = compile_and_run(prog.to_string(), &mut main); + assert!(approx_equal(0.02, res, 2)); +} diff --git a/tests/correctness/arithmetic_functions/multiplication.rs b/tests/correctness/arithmetic_functions/multiplication.rs new file mode 100644 index 0000000000..9e22ce2db6 --- /dev/null +++ b/tests/correctness/arithmetic_functions/multiplication.rs @@ -0,0 +1,53 @@ +use crate::correctness::math_operators::addition::approx_equal; +use driver::runner::compile_and_run_no_params; + +#[test] +fn mul_with_ints() { + let prog = r#" + FUNCTION main : LINT + VAR + x1 : ARRAY[0..3] OF DINT := (1, 2, 3, 4); + l1 : LINT := 1000; + s1 : SINT := 5; + END_VAR + main := MUL(x1[0], x1[1], x1[2], x1[3], l1, s1); + END_FUNCTION + "#; + + let expected = 1 * 2 * 3 * 4 * 1000 * 5; + let res: i64 = compile_and_run_no_params(prog.to_string()); + assert_eq!(expected, res); +} + +#[test] +fn mul_with_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : ARRAY[0..3] OF REAL := (1.0, 2.2, 3.4, 4.1); + x2 : LREAL := 1000.9; + END_VAR + main := MUL(x1[0], x1[1], x1[2], x1[3], x2); + END_FUNCTION + "#; + + let expected = 1.0 * 2.2 * 3.4 * 4.1 * 1000.9; + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(expected, res, 1)); +} + +#[test] +fn builtin_mul_with_ints_and_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : ARRAY[0..3] OF DINT := (1, 2, 3, 4); + x2 : LREAL := 0.5; + END_VAR + main := MUL(x1[0], x1[1], x1[2], x1[3], x2); + END_FUNCTION + "#; + + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(12.0, res, 1)); +} diff --git a/tests/correctness/arithmetic_functions/substraction.rs b/tests/correctness/arithmetic_functions/substraction.rs new file mode 100644 index 0000000000..4bf6472699 --- /dev/null +++ b/tests/correctness/arithmetic_functions/substraction.rs @@ -0,0 +1,50 @@ +use crate::correctness::math_operators::addition::approx_equal; +use driver::runner::compile_and_run_no_params; + +#[test] +fn builtin_sub_with_ints() { + let prog = r#" + FUNCTION main : LINT + VAR + x1 : DINT := 1000; + l1 : LINT := 333; + END_VAR + main := SUB(x1, l1); + END_FUNCTION + "#; + + let res: i64 = compile_and_run_no_params(prog.to_string()); + assert_eq!(res, 667); +} + +#[test] +fn builtin_sub_with_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : REAL := 10.0; + x2 : LREAL := 1000.0; + END_VAR + main := SUB(x1, x2); + END_FUNCTION + "#; + + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(-990.0, res, 2)); +} + +#[test] +fn builtin_sub_with_ints_and_floats() { + let prog = r#" + FUNCTION main : LREAL + VAR + x1 : DINT := 20; + x2 : LREAL := 19.9; + END_VAR + main := SUB(x1, x2); + END_FUNCTION + "#; + + let res: f64 = compile_and_run_no_params(prog.to_string()); + assert!(approx_equal(0.1, res, 1)); +} diff --git a/tests/correctness/comparison_functions/equal.rs b/tests/correctness/comparison_functions/equal.rs new file mode 100644 index 0000000000..f255fcdab7 --- /dev/null +++ b/tests/correctness/comparison_functions/equal.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_eq_with_ints_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 1; + i2 := 1; + i3 := 1; + main := EQ(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_eq_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; // not equal to i3, should return false + i3 := 3; + main := EQ(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_eq_with_floats_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 2.9; + r2 := 2.9; + r3 := 2.9; + main := EQ(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_eq_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; // not equal to r3, should return false + r3 := 3.2; + main := EQ(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_eq_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := EQ(i1, r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} diff --git a/tests/correctness/comparison_functions/greater_than.rs b/tests/correctness/comparison_functions/greater_than.rs new file mode 100644 index 0000000000..040a6cb71c --- /dev/null +++ b/tests/correctness/comparison_functions/greater_than.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_gt_with_ints_descending() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; + i3 := 1; + main := GT(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_gt_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; // not greater than i3, should return false + i3 := 3; + main := GT(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_gt_with_floats_descending() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; + r3 := 2.8; + main := GT(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_gt_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; // not greater than r3, should return false + r3 := 3.2; + main := GT(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_gt_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := GT(i1, r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} diff --git a/tests/correctness/comparison_functions/greater_than_or_equal.rs b/tests/correctness/comparison_functions/greater_than_or_equal.rs new file mode 100644 index 0000000000..6033a9bd57 --- /dev/null +++ b/tests/correctness/comparison_functions/greater_than_or_equal.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_ge_with_ints_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 1; + i2 := 1; + i3 := 1; + main := GE(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_ge_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; // not greater or equal to i3, should return false + i3 := 3; + main := GE(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_ge_with_floats_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 2.9; + r2 := 2.9; + r3 := 2.9; + main := GE(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_ge_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; // not greater or equal to r3, should return false + r3 := 3.2; + main := GE(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_ge_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := GE(i1, r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} diff --git a/tests/correctness/comparison_functions/less_than.rs b/tests/correctness/comparison_functions/less_than.rs new file mode 100644 index 0000000000..f09b86baec --- /dev/null +++ b/tests/correctness/comparison_functions/less_than.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_lt_with_ints_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 1; + i2 := 1; + i3 := 1; + main := LT(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_lt_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; + i3 := 2; // not less than i3, should return false + main := LT(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_lt_with_floats_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 2.9; + r2 := 2.9; + r3 := 2.9; + main := LT(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_lt_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; + r3 := 3.2; // not less than r3, should return false + main := LT(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_lt_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := LT(i1, r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} diff --git a/tests/correctness/comparison_functions/less_than_or_equal.rs b/tests/correctness/comparison_functions/less_than_or_equal.rs new file mode 100644 index 0000000000..c9b2af6e4a --- /dev/null +++ b/tests/correctness/comparison_functions/less_than_or_equal.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_le_with_ints_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 1; + i2 := 1; + i3 := 1; + main := LE(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_le_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; + i3 := 3; // not less or equal to i2, should return false + main := LE(i1, i2, i3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_le_with_floats_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 2.9; + r2 := 2.9; + r3 := 2.9; + main := LE(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_le_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; + r3 := 3.2; // not less or equal to r3, should return false + main := LE(r1, r2, r3); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_le_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := LE(i1, r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} diff --git a/tests/correctness/comparison_functions/not_equal.rs b/tests/correctness/comparison_functions/not_equal.rs new file mode 100644 index 0000000000..bf9b243f1a --- /dev/null +++ b/tests/correctness/comparison_functions/not_equal.rs @@ -0,0 +1,102 @@ +use driver::runner::{compile_and_run, MainType}; + +#[test] +fn builtin_ne_with_ints_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 1; + i2 := 1; + i3 := 1; + main := NE(i1, i2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_ne_with_ints() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1, i2, i3 : DINT; + END_VAR + i1 := 3; + i2 := 2; + i3 := 3; + main := NE(i1, i2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_ne_with_floats_monotonic() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 2.9; + r2 := 2.9; + r3 := 2.9; + main := NE(r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, false); +} + +#[test] +fn builtin_ne_with_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + r1, r2, r3 : REAL; + END_VAR + r1 := 3.0; + r2 := 2.9; + r3 := 3.2; + main := NE(r1, r2); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} + +#[test] +fn builtin_ne_with_mixed_ints_and_floats() { + let prog = r#" + FUNCTION main : BOOL + VAR + i1 : DINT; + r1, r2 : REAL; + END_VAR + i1 := 5; + r1 := 4.5; + r2 := 3.2; + main := NE(i1, r1); + END_FUNCTION + "#; + + let mut main = MainType::default(); + + let res: bool = compile_and_run(prog.to_string(), &mut main); + assert_eq!(res, true); +} diff --git a/tests/correctness/expressions.rs b/tests/correctness/expressions.rs index ebd02c985d..e7a5a19e8a 100644 --- a/tests/correctness/expressions.rs +++ b/tests/correctness/expressions.rs @@ -345,3 +345,39 @@ fn casting_of_floating_point_types_lreal() { let _: i32 = compile_and_run(src, &mut main); assert_eq!([main.a, main.b, main.c, main.d], [3.0, 3.5, 3.5, 3.5]) } + +#[test] +fn aliased_ranged_numbers_can_be_compared_with_builtins() { + #[derive(Default)] + #[repr(C)] + struct Main { + a: bool, + b: bool, + c: bool, + d: bool, + e: bool, + f: bool, + } + + let mut main = Main::default(); + + let src = r#" + TYPE MyInt: INT(0..500); END_TYPE + PROGRAM main + VAR + a, b, c, d, e, f : BOOL; + END_VAR + VAR_TEMP + x,y : MyInt; + END_VAR + a := LT(x, y); + b := LE(y, 0); + c := EQ(x, 3); + d := EQ(y, 500); + e := GE(x, 0) AND LE(x, 500); + f := LT(x, 0) OR GT(x, 500); + END_PROGRAM + "#; + let _: i32 = compile_and_run(src, &mut main); + assert_eq!([main.a, main.b, main.c, main.d, main.e, main.f], [false, true, false, false, true, false]); +} diff --git a/tests/correctness/external_functions.rs b/tests/correctness/external_functions.rs index 7e151fd58b..82f9f6c6e3 100644 --- a/tests/correctness/external_functions.rs +++ b/tests/correctness/external_functions.rs @@ -36,7 +36,7 @@ fn test_external_function_called() { //Test the function's result is executed } -extern "C" fn add(size: i32, ptr: *const i32) -> i32 { +extern "C" fn add_local(size: i32, ptr: *const i32) -> i32 { let mut result = 0; let mut ptr = ptr; for _ in 0..size { @@ -64,14 +64,14 @@ extern "C" fn add_ref(size: i32, ptr: *const *const i32) -> i32 { fn sized_variadic_call() { let src = " {external} - FUNCTION add : DINT + FUNCTION add_local : DINT VAR_INPUT args : {sized} DINT...; END_VAR END_FUNCTION FUNCTION main : DINT - main := add(1, 2, 3); + main := add_local(1, 2, 3); END_FUNCTION "; @@ -79,7 +79,7 @@ fn sized_variadic_call() { let source = SourceCode::new(src, "external_test.st"); let context = CodegenContext::create(); let module = compile(&context, source); - module.add_global_function_mapping("add", add as usize); + module.add_global_function_mapping("add_local", add_local as usize); let res: i32 = module.run_no_param("main"); assert_eq!(res, 6) diff --git a/tests/correctness/math_operators/addition.rs b/tests/correctness/math_operators/addition.rs index 79fb3f5d61..17d55b6cae 100644 --- a/tests/correctness/math_operators/addition.rs +++ b/tests/correctness/math_operators/addition.rs @@ -272,8 +272,7 @@ fn adds_array_basic() { } //-------------------------- - -fn approx_equal(a: T, b: T, decimal_places: u16) -> bool { +pub fn approx_equal(a: T, b: T, decimal_places: u16) -> bool { let factor: T = NumCast::from(10.0.powi(decimal_places as i32)).unwrap(); let a = (a * factor).round(); let b = (b * factor).round(); diff --git a/tests/tests.rs b/tests/tests.rs index 7a92b2b8e1..6351bf03f5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -27,13 +27,27 @@ mod correctness { mod strings; mod sub_range_types; mod math_operators { - mod addition; + pub(super) mod addition; mod division; mod mixed; mod multiplication; mod substraction; } + mod arithmetic_functions { + mod addition; + mod division; + mod multiplication; + mod substraction; + } mod vla; + mod comparison_functions { + mod equal; + mod greater_than; + mod greater_than_or_equal; + mod less_than; + mod less_than_or_equal; + mod not_equal; + } } mod integration { mod build_description_tests;