From 98fe5e062a20e7bc29c90612a3be4486f4b512ec Mon Sep 17 00:00:00 2001 From: Michael <78988079+mhasel@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:45:22 +0200 Subject: [PATCH] CFC Support - XML deserializer & parser (#883) Implemnted a XML serializer for CFC support XML files will now be treated as CFC files, and be read using the `xml_parser` crate. Basic blocks are supported, including function calls and assignments. Co-authored-by: Volkan Sagcan Co-authored-by: Michael Co-authored-by: Ghaith Hachem --- .vscode/settings.json | 25 - Cargo.lock | 547 ++++++++++-------- Cargo.toml | 7 +- compiler/plc_ast/src/ast.rs | 5 + .../plc_derive}/Cargo.toml | 2 +- .../plc_derive}/src/lib.rs | 0 .../plc_derive}/tests/rusty_derive_tests.rs | 2 +- compiler/plc_driver/Cargo.toml | 1 + compiler/plc_driver/src/pipelines.rs | 8 +- compiler/plc_source/src/lib.rs | 9 +- compiler/plc_xml/Cargo.toml | 20 + compiler/plc_xml/src/error.rs | 62 ++ compiler/plc_xml/src/extensions.rs | 41 ++ compiler/plc_xml/src/lib.rs | 20 + compiler/plc_xml/src/model/action.rs | 5 + compiler/plc_xml/src/model/block.rs | 89 +++ compiler/plc_xml/src/model/body.rs | 90 +++ compiler/plc_xml/src/model/connector.rs | 73 +++ compiler/plc_xml/src/model/control.rs | 85 +++ compiler/plc_xml/src/model/fbd.rs | 174 ++++++ compiler/plc_xml/src/model/interface.rs | 59 ++ compiler/plc_xml/src/model/pou.rs | 197 +++++++ compiler/plc_xml/src/model/project.rs | 32 + ...c_cfc__model__block__tests__add_block.snap | 46 ++ .../plc_cfc__model__body__tests__body.snap | 9 + ...riables__tests__block_output_variable.snap | 17 + ...del__variables__tests__input_variable.snap | 17 + ...ts__variable_with_connection_point_in.snap | 10 + ...s__variable_with_connection_point_out.snap | 10 + ...c_xml__model__block__tests__add_block.snap | 45 ++ .../plc_xml__model__body__tests__empty.snap | 7 + ...odel__body__tests__fbd_with_add_block.snap | 55 ++ ...plc_xml__model__fbd__tests__add_block.snap | 85 +++ .../plc_xml__model__pou__tests__empty.snap | 28 + ...__model__pou__tests__poutype_function.snap | 15 + ...l__pou__tests__poutype_function_block.snap | 15 + ...l__model__pou__tests__poutype_program.snap | 15 + ...ariables__tests__block_inout_variable.snap | 17 + ...ariables__tests__block_input_variable.snap | 17 + ...riables__tests__block_output_variable.snap | 17 + ...el__variables__tests__fbd_in_variable.snap | 14 + ...l__variables__tests__fbd_out_variable.snap | 14 + ...l__variables__tests__negated_variable.snap | 10 + ...ml__model__variables__tests__variable.snap | 10 + ...ts__variable_with_connection_point_in.snap | 10 + ...s__variable_with_connection_point_out.snap | 10 + compiler/plc_xml/src/model/variables.rs | 276 +++++++++ compiler/plc_xml/src/reader.rs | 130 +++++ compiler/plc_xml/src/serializer.rs | 379 ++++++++++++ ...s__model_is_sorted_by_execution_order.snap | 68 +++ ...fc_parser__tests__variable_assignment.snap | 40 ++ compiler/plc_xml/src/xml_parser.rs | 169 ++++++ compiler/plc_xml/src/xml_parser/action.rs | 28 + compiler/plc_xml/src/xml_parser/block.rs | 38 ++ compiler/plc_xml/src/xml_parser/fbd.rs | 77 +++ compiler/plc_xml/src/xml_parser/pou.rs | 40 ++ ...arser__tests__tests__function_returns.snap | 55 ++ ...s__model_is_sorted_by_execution_order.snap | 83 +++ ...l_parser__tests__tests__simple_return.snap | 138 +++++ ...er__tests__tests__variable_assignment.snap | 55 ++ compiler/plc_xml/src/xml_parser/tests.rs | 165 ++++++ compiler/plc_xml/src/xml_parser/variables.rs | 45 ++ src/lexer.rs | 7 +- src/parser.rs | 2 +- src/validation.rs | 2 +- tests/integration/cfc.rs | 82 +++ tests/integration/data/cfc/assigning.cfc | 21 + tests/integration/data/cfc/assigning.st | 10 + tests/integration/data/cfc/chained_calls.cfc | 127 ++++ tests/integration/data/cfc/chained_calls.st | 23 + .../data/cfc/chained_calls_galore.cfc | 301 ++++++++++ .../data/cfc/chained_calls_galore.st | 17 + .../integration/data/cfc/function_returns.cfc | 41 ++ .../integration/data/cfc/function_returns.st | 3 + tests/integration/data/cfc/my_add.cfc | 77 +++ tests/integration/data/cfc/my_add.st | 20 + tests/integration/data/cfc/select.cfc | 86 +++ tests/integration/data/cfc/select.st | 11 + .../data/cfc/variable_assignment.cfc | 37 ++ .../data/cfc/variable_assignment.st | 12 + tests/tests.rs | 1 + 81 files changed, 4419 insertions(+), 293 deletions(-) delete mode 100644 .vscode/settings.json rename {rusty-derive => compiler/plc_derive}/Cargo.toml (90%) rename {rusty-derive => compiler/plc_derive}/src/lib.rs (100%) rename {rusty-derive => compiler/plc_derive}/tests/rusty_derive_tests.rs (96%) create mode 100644 compiler/plc_xml/Cargo.toml create mode 100644 compiler/plc_xml/src/error.rs create mode 100644 compiler/plc_xml/src/extensions.rs create mode 100644 compiler/plc_xml/src/lib.rs create mode 100644 compiler/plc_xml/src/model/action.rs create mode 100644 compiler/plc_xml/src/model/block.rs create mode 100644 compiler/plc_xml/src/model/body.rs create mode 100644 compiler/plc_xml/src/model/connector.rs create mode 100644 compiler/plc_xml/src/model/control.rs create mode 100644 compiler/plc_xml/src/model/fbd.rs create mode 100644 compiler/plc_xml/src/model/interface.rs create mode 100644 compiler/plc_xml/src/model/pou.rs create mode 100644 compiler/plc_xml/src/model/project.rs create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__block__tests__add_block.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__body__tests__body.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__block_output_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__input_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_in.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_out.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__block__tests__add_block.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__empty.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__fbd_with_add_block.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__fbd__tests__add_block.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__empty.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function_block.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_program.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_inout_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_input_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_output_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_in_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_out_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__negated_variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_in.snap create mode 100644 compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_out.snap create mode 100644 compiler/plc_xml/src/model/variables.rs create mode 100644 compiler/plc_xml/src/reader.rs create mode 100644 compiler/plc_xml/src/serializer.rs create mode 100644 compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__model_is_sorted_by_execution_order.snap create mode 100644 compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__variable_assignment.snap create mode 100644 compiler/plc_xml/src/xml_parser.rs create mode 100644 compiler/plc_xml/src/xml_parser/action.rs create mode 100644 compiler/plc_xml/src/xml_parser/block.rs create mode 100644 compiler/plc_xml/src/xml_parser/fbd.rs create mode 100644 compiler/plc_xml/src/xml_parser/pou.rs create mode 100644 compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__function_returns.snap create mode 100644 compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__model_is_sorted_by_execution_order.snap create mode 100644 compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__simple_return.snap create mode 100644 compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__variable_assignment.snap create mode 100644 compiler/plc_xml/src/xml_parser/tests.rs create mode 100644 compiler/plc_xml/src/xml_parser/variables.rs create mode 100644 tests/integration/cfc.rs create mode 100644 tests/integration/data/cfc/assigning.cfc create mode 100644 tests/integration/data/cfc/assigning.st create mode 100644 tests/integration/data/cfc/chained_calls.cfc create mode 100644 tests/integration/data/cfc/chained_calls.st create mode 100644 tests/integration/data/cfc/chained_calls_galore.cfc create mode 100644 tests/integration/data/cfc/chained_calls_galore.st create mode 100644 tests/integration/data/cfc/function_returns.cfc create mode 100644 tests/integration/data/cfc/function_returns.st create mode 100644 tests/integration/data/cfc/my_add.cfc create mode 100644 tests/integration/data/cfc/my_add.st create mode 100644 tests/integration/data/cfc/select.cfc create mode 100644 tests/integration/data/cfc/select.st create mode 100644 tests/integration/data/cfc/variable_assignment.cfc create mode 100644 tests/integration/data/cfc/variable_assignment.st diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7a65d16d1a..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "remote.containers.dockerPath": "docker", - "restoreTerminals.terminals": [ - { - "splitTerminals": [ - { - "name": "cargo", - "commands": [ - "./cargo_watch.sh" - ] - }, - ] - }, - { - "splitTerminals": [ - { - "name": "git", - "commands": [ - "" - ], - }, - ] - } - ] -} diff --git a/Cargo.lock b/Cargo.lock index 957f4cd9a7..9d29c10be0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,18 +16,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-tzdata" @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -95,15 +95,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -119,7 +119,7 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand", + "fastrand 1.9.0", "futures-lite", "slab", ] @@ -153,7 +153,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.22", + "rustix 0.37.23", "slab", "socket2", "waker-fn", @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -258,9 +258,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" dependencies = [ "serde", ] @@ -284,7 +284,7 @@ dependencies = [ "async-lock", "async-task", "atomic-waker", - "fastrand", + "fastrand 1.9.0", "futures-lite", "log", ] @@ -309,9 +309,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -353,20 +356,20 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.10" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" dependencies = [ "clap_builder", - "clap_derive 4.3.2", + "clap_derive 4.3.12", "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.10" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" dependencies = [ "anstream", "anstyle", @@ -382,21 +385,21 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "syn 1.0.109", ] [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] @@ -453,9 +456,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "core-foundation" @@ -475,9 +478,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -560,24 +563,14 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote 1.0.29", - "syn 1.0.109", -] - [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core", @@ -585,9 +578,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "pem-rfc7468", @@ -620,9 +613,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" dependencies = [ "serde", ] @@ -666,15 +659,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -717,6 +710,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "flume" version = "0.10.14" @@ -824,7 +823,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -992,6 +991,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1069,7 +1077,7 @@ dependencies = [ [[package]] name = "inkwell" version = "0.2.0" -source = "git+https://github.com/TheDan64/inkwell?branch=master#ec963b95da169d0927112126a204a2e9d0bcaee0" +source = "git+https://github.com/TheDan64/inkwell?branch=master#bbe1f3d76c45fc137a125665861fc6382ab352d6" dependencies = [ "either", "inkwell_internals", @@ -1082,18 +1090,18 @@ dependencies = [ [[package]] name = "inkwell_internals" version = "0.8.0" -source = "git+https://github.com/TheDan64/inkwell?branch=master#ec963b95da169d0927112126a204a2e9d0bcaee0" +source = "git+https://github.com/TheDan64/inkwell?branch=master#bbe1f3d76c45fc137a125665861fc6382ab352d6" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] name = "insta" -version = "1.30.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" dependencies = [ "console", "lazy_static", @@ -1124,12 +1132,12 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", - "rustix 0.38.2", + "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -1144,9 +1152,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1212,9 +1220,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lld_rs" @@ -1231,9 +1239,9 @@ dependencies = [ [[package]] name = "llvm-sys" -version = "140.1.1" +version = "140.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe48b87b95ad1b3eeca563010aadbb427b3d3e89c1f78fe21acd39846be5c7d4" +checksum = "69b285f8682531b9b394dd9891977a2a28c47006e491bda944e1ca62ebab2664" dependencies = [ "cc", "lazy_static", @@ -1254,9 +1262,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ "value-bag", ] @@ -1278,8 +1286,8 @@ checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" dependencies = [ "beef", "fnv", - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "regex-syntax 0.6.29", "syn 1.0.109", ] @@ -1353,9 +1361,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", "num-complex", @@ -1367,9 +1375,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -1395,9 +1403,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", ] @@ -1437,9 +1445,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -1463,9 +1471,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -1482,9 +1490,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] @@ -1495,9 +1503,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -1511,15 +1519,6 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "parking" version = "2.1.0" @@ -1546,14 +1545,14 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem-rfc7468" @@ -1572,29 +1571,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -1638,6 +1637,14 @@ dependencies = [ "serde", ] +[[package]] +name = "plc_derive" +version = "0.1.0" +dependencies = [ + "quote 0.6.13", + "syn 0.15.44", +] + [[package]] name = "plc_diagnostics" version = "0.1.0" @@ -1661,6 +1668,7 @@ dependencies = [ "plc_diagnostics", "plc_project", "plc_source", + "plc_xml", "pretty_assertions", "rayon", "rusty", @@ -1696,6 +1704,20 @@ dependencies = [ name = "plc_util" version = "0.1.0" +[[package]] +name = "plc_xml" +version = "0.1.0" +dependencies = [ + "html-escape", + "indexmap 1.9.3", + "insta", + "logos", + "plc_ast", + "plc_diagnostics", + "quick-xml", + "rusty", +] + [[package]] name = "polling" version = "2.8.0" @@ -1720,13 +1742,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] @@ -1737,8 +1757,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "syn 1.0.109", "version_check", ] @@ -1749,8 +1769,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "version_check", ] @@ -1765,13 +1785,23 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "0.6.13" @@ -1783,11 +1813,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 1.0.63", + "proc-macro2 1.0.66", ] [[package]] @@ -1853,13 +1883,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-syntax 0.7.4", ] [[package]] @@ -1870,9 +1912,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rsa" @@ -1898,9 +1940,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.22" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", @@ -1912,14 +1954,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.2" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabcb0461ebd01d6b79945797c27f8529082226cb630a9865a71870ff63532a4" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] @@ -1942,6 +1984,7 @@ dependencies = [ "logos", "num", "plc_ast", + "plc_derive", "plc_diagnostics", "plc_driver", "plc_project", @@ -1949,7 +1992,6 @@ dependencies = [ "plc_util", "pretty_assertions", "regex", - "rusty-derive", "serde", "serde_json", "serial_test", @@ -1960,19 +2002,11 @@ dependencies = [ "which", ] -[[package]] -name = "rusty-derive" -version = "0.1.0" -dependencies = [ - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" @@ -1985,15 +2019,15 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2004,9 +2038,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2014,35 +2048,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.166" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.166" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" +checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -2069,9 +2103,9 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] @@ -2120,18 +2154,18 @@ checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -2181,9 +2215,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ef53c86d2066e04f0ac6b1364f16d13d82388e2d07f11a5c71782345555761" +checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2194,9 +2228,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a22fd81e9c1ad53c562edb869ff042b215d4eadefefc4784bacfbfd19835945" +checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" dependencies = [ "ahash", "async-io", @@ -2235,12 +2269,12 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bb7c096a202b8164c175614cbfb79fe0e1e0a3d50e0374526183ef2974e4a2" +checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "sqlx-core", "sqlx-macros-core", "syn 1.0.109", @@ -2248,9 +2282,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d644623ab9699014e5b3cb61a040d16caa50fd477008f63f1399ae35498a58" +checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc" dependencies = [ "async-std", "dotenvy", @@ -2258,8 +2292,8 @@ dependencies = [ "heck", "hex", "once_cell", - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "serde", "serde_json", "sha2", @@ -2273,13 +2307,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8264c59b28b6858796acfcedc660aa4c9075cc6e4ec8eb03cdca2a3e725726db" +checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" dependencies = [ "atoi", "base64", - "bitflags 2.3.3", + "bitflags 2.4.0", "byteorder", "bytes", "crc", @@ -2315,13 +2349,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab6147b81ca9213a7578f1b4c9d24c449a53953cd2222a7b5d7cd29a5c3139" +checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" dependencies = [ "atoi", "base64", - "bitflags 2.3.3", + "bitflags 2.4.0", "byteorder", "crc", "dotenvy", @@ -2354,9 +2388,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fba60afa64718104b71eec6984f8779d4caffff3b30cde91a75843c7efc126" +checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2" dependencies = [ "atoi", "flume", @@ -2376,9 +2410,9 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2413,19 +2447,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.23" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.33", "unicode-ident", ] @@ -2446,15 +2480,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "autocfg", "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall", - "rustix 0.37.22", + "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -2475,22 +2508,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.41" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.41" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] @@ -2547,9 +2580,9 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", ] [[package]] @@ -2575,9 +2608,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2623,6 +2656,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "utf8parse" version = "0.2.1" @@ -2684,9 +2723,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", "wasm-bindgen-shared", ] @@ -2708,7 +2747,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ - "quote 1.0.29", + "quote 1.0.33", "wasm-bindgen-macro-support", ] @@ -2718,9 +2757,9 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 2.0.23", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2795,7 +2834,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -2813,7 +2852,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -2833,17 +2872,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -2854,9 +2893,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -2866,9 +2905,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -2878,9 +2917,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -2890,9 +2929,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -2902,9 +2941,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -2914,9 +2953,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -2926,24 +2965,24 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "xshell" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" +checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" +checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" [[package]] name = "xtask" @@ -2951,7 +2990,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-std", - "clap 4.3.10", + "clap 4.3.23", "plc_ast", "rusty", "serde", diff --git a/Cargo.toml b/Cargo.toml index 449523393b..38c070f777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ toml = "0.5" lazy_static = "1.4.0" shell-words = "1.1.0" itertools = "0.10.5" -rusty-derive = { path = "rusty-derive" } +plc_derive = { path = "./compiler/plc_derive" } lld_rs = "140.0.0" which = "4.2.5" log.workspace = true @@ -65,9 +65,10 @@ members = [ "compiler/plc_project", "compiler/plc_source", "compiler/plc_util", - "rusty-derive", + "compiler/plc_xml", + "compiler/plc_derive", ] -default-members = [".", "compiler/plc_driver"] +default-members = [".", "compiler/plc_driver", "compiler/plc_xml"] [workspace.dependencies] inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = [ diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index beeeceed42..9718e07760 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -322,6 +322,11 @@ impl CompilationUnit { } } + pub fn with_implementations(mut self, implementations: Vec) -> Self { + self.implementations = implementations; + self + } + /// imports all elements of the other CompilationUnit into this CompilationUnit /// /// this will import all global_vars, units, implementations and types. The imported diff --git a/rusty-derive/Cargo.toml b/compiler/plc_derive/Cargo.toml similarity index 90% rename from rusty-derive/Cargo.toml rename to compiler/plc_derive/Cargo.toml index d64582498a..30e9aa1ded 100644 --- a/rusty-derive/Cargo.toml +++ b/compiler/plc_derive/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rusty-derive" +name = "plc_derive" version = "0.1.0" edition = "2021" diff --git a/rusty-derive/src/lib.rs b/compiler/plc_derive/src/lib.rs similarity index 100% rename from rusty-derive/src/lib.rs rename to compiler/plc_derive/src/lib.rs diff --git a/rusty-derive/tests/rusty_derive_tests.rs b/compiler/plc_derive/tests/rusty_derive_tests.rs similarity index 96% rename from rusty-derive/tests/rusty_derive_tests.rs rename to compiler/plc_derive/tests/rusty_derive_tests.rs index 54f0caf672..03f2250ed0 100644 --- a/rusty-derive/tests/rusty_derive_tests.rs +++ b/compiler/plc_derive/tests/rusty_derive_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use rusty_derive::Validators; + use plc_derive::Validators; #[derive(PartialEq, Eq, Debug, Clone)] pub enum Diagnostic { Error, diff --git a/compiler/plc_driver/Cargo.toml b/compiler/plc_driver/Cargo.toml index 670acf2dea..a2c6bd6852 100644 --- a/compiler/plc_driver/Cargo.toml +++ b/compiler/plc_driver/Cargo.toml @@ -10,6 +10,7 @@ plc = { path = "../..", package = "rusty" } ast = { path = "../plc_ast/", package = "plc_ast" } project = { path = "../plc_project/", package = "plc_project" } source_code = { path = "../plc_source/", package = "plc_source" } +cfc = { path = "../plc_xml/", package = "plc_xml" } plc_diagnostics = { path = "../plc_diagnostics/" } serde = { version = "1.0", features = ["derive"] } diff --git a/compiler/plc_driver/src/pipelines.rs b/compiler/plc_driver/src/pipelines.rs index 1ce85f7bcf..9ea479340b 100644 --- a/compiler/plc_driver/src/pipelines.rs +++ b/compiler/plc_driver/src/pipelines.rs @@ -57,7 +57,13 @@ impl ParsedProject { &err, ) })?; - Ok(parse_file( + + let parse_func = match loaded_source.get_type() { + source_code::SourceType::Text => parse_file, + source_code::SourceType::Xml => cfc::xml_parser::parse_file, + source_code::SourceType::Unknown => unreachable!(), + }; + Ok(parse_func( &loaded_source.source, loaded_source.get_location_str(), LinkageType::Internal, diff --git a/compiler/plc_source/src/lib.rs b/compiler/plc_source/src/lib.rs index 8d026c9667..cc3b53fcfa 100644 --- a/compiler/plc_source/src/lib.rs +++ b/compiler/plc_source/src/lib.rs @@ -8,10 +8,14 @@ use encoding_rs::Encoding; use encoding_rs_io::DecodeReaderBytesBuilder; /// Represents the type of source a SourceContainer holds +#[derive(Clone, Copy, Debug)] pub enum SourceType { /// A normal text file, the content of the file could be parsed Text, + /// An xml file, probably cfc + Xml, + /// Unknown type, probably a binary Unknown, } @@ -30,6 +34,8 @@ pub trait SourceContainer { if let Some(ext) = self.get_location().and_then(|it| it.extension()) { match ext.to_str() { Some("o") | Some("so") | Some("exe") => SourceType::Unknown, + //XXX: file ending vs first line? ( SourceType::Xml, _ => SourceType::Text, } } else { @@ -81,7 +87,8 @@ impl SourceCodeFactory for &str { impl> SourceContainer for T { fn load_source(&self, encoding: Option<&'static Encoding>) -> Result { - if matches!(self.get_type(), SourceType::Text) { + let source_type = self.get_type(); + if matches!(source_type, SourceType::Text | SourceType::Xml) { let mut file = File::open(self).map_err(|err| err.to_string())?; let source = create_source_code(&mut file, encoding)?; diff --git a/compiler/plc_xml/Cargo.toml b/compiler/plc_xml/Cargo.toml new file mode 100644 index 0000000000..5d2ee3f6dc --- /dev/null +++ b/compiler/plc_xml/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "plc_xml" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +logos = "0.12" +quick-xml = { version = "0.28", features = ["serialize"] } +insta = "1.20" +indexmap = "1.9" +html-escape = "0.2" +plc = { path = "../..", package = "rusty" } +ast = { path = "../plc_ast/", package = "plc_ast" } +plc_diagnostics = { path = "../plc_diagnostics"} + +[features] +default = [] +debug = [] diff --git a/compiler/plc_xml/src/error.rs b/compiler/plc_xml/src/error.rs new file mode 100644 index 0000000000..865ede5196 --- /dev/null +++ b/compiler/plc_xml/src/error.rs @@ -0,0 +1,62 @@ +use std::{num::ParseIntError, str::Utf8Error}; + +pub enum Error { + UnexpectedEndOfFile(Vec<&'static [u8]>), + + /// Indicates that the reader expected the new line to be ... + UnexpectedElement(String), + + /// Indicates that converting a `[u8]` to a String failed due to encoding issues. + Encoding(Utf8Error), + + /// Indicates that attributes of a XML element are missing. For example if we expect an element + /// `` to have an attribute `id` such that the element has the structure `` but + /// `id` does not exist. + MissingAttribute(String), + + /// Indicates that reading the next line of the current XML file failed. + ReadEvent(quick_xml::Error), + + /// Indicates that converting a string to a given type failed. + Parse(ParseErrorKind), +} + +#[derive(Debug)] +pub enum ParseErrorKind { + Integer(ParseIntError), +} + +impl From for Error { + fn from(value: ParseIntError) -> Self { + Error::Parse(ParseErrorKind::Integer(value)) + } +} + +impl std::error::Error for Error {} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:#?}") + } +} + +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnexpectedEndOfFile(tokens) => { + write!( + f, + "Expected token {:?} but reached end of file", + tokens + .iter() + .map(|it| std::str::from_utf8(it).unwrap().to_string()) + .collect::>() + ) + } + Self::MissingAttribute(key) => write!(f, "Failed to find attribute '{key}'"), + Self::ReadEvent(why) => write!(f, "Failed to read XML; {why}"), + Self::UnexpectedElement(element) => write!(f, "Found an unexpected element '{element}'"), + Self::Encoding(why) => write!(f, "{why:#?}"), + Self::Parse(why) => write!(f, "Failed to parse string to other type; {why:#?}"), + } + } +} diff --git a/compiler/plc_xml/src/extensions.rs b/compiler/plc_xml/src/extensions.rs new file mode 100644 index 0000000000..f1a3685937 --- /dev/null +++ b/compiler/plc_xml/src/extensions.rs @@ -0,0 +1,41 @@ +//! In this file extension traits are defined to be used within the `plc_xml` crate. + +use std::{borrow::Cow, collections::HashMap}; + +use quick_xml::name::QName; + +use crate::error::Error; + +/// Trait for [`quick_xml`]s tags defined in [`quick_xml::events`] +pub(crate) trait TryToString { + fn try_to_string(self) -> Result; +} + +/// Trait to extract attribute values from XML elements wrapped inside HashMaps +pub(crate) trait GetOrErr { + fn get_or_err(&self, key: &str) -> Result; +} + +impl<'a> TryToString for &'a [u8] { + fn try_to_string(self) -> Result { + String::from_utf8(self.as_ref().to_vec()).map_err(|err| Error::Encoding(err.utf8_error())) + } +} + +impl<'a> TryToString for QName<'a> { + fn try_to_string(self) -> Result { + String::from_utf8(self.into_inner().to_vec()).map_err(|err| Error::Encoding(err.utf8_error())) + } +} + +impl TryToString for Cow<'_, [u8]> { + fn try_to_string(self) -> Result { + String::from_utf8(self.to_vec()).map_err(|err| Error::Encoding(err.utf8_error())) + } +} + +impl GetOrErr for HashMap { + fn get_or_err(&self, key: &str) -> Result { + self.get(key).map(|it| it.to_owned()).ok_or(Error::MissingAttribute(key.to_string())) + } +} diff --git a/compiler/plc_xml/src/lib.rs b/compiler/plc_xml/src/lib.rs new file mode 100644 index 0000000000..4e1ff81915 --- /dev/null +++ b/compiler/plc_xml/src/lib.rs @@ -0,0 +1,20 @@ +// TODO: Remove +#![allow(dead_code)] + +pub mod error; +mod extensions; +pub mod xml_parser; +pub(crate) mod model { + pub mod action; + pub mod block; + pub mod body; + pub mod connector; + pub mod control; + pub mod fbd; + pub mod interface; + pub mod pou; + pub mod project; + pub mod variables; +} +mod reader; +mod serializer; diff --git a/compiler/plc_xml/src/model/action.rs b/compiler/plc_xml/src/model/action.rs new file mode 100644 index 0000000000..d5a9a0c502 --- /dev/null +++ b/compiler/plc_xml/src/model/action.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub(crate) struct Action { + pub name: String, + pub type_name: String, +} diff --git a/compiler/plc_xml/src/model/block.rs b/compiler/plc_xml/src/model/block.rs new file mode 100644 index 0000000000..42e176c310 --- /dev/null +++ b/compiler/plc_xml/src/model/block.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; + +use quick_xml::events::Event; + +use crate::{error::Error, extensions::GetOrErr, reader::PeekableReader, xml_parser::Parseable}; + +use super::variables::BlockVariable; + +#[derive(Debug, PartialEq)] +pub(crate) struct Block { + pub local_id: usize, + pub type_name: String, + pub instance_name: Option, + pub execution_order_id: Option, + pub variables: Vec, +} + +impl Block { + pub fn new(mut hm: HashMap, variables: Vec) -> Result { + Ok(Self { + local_id: hm.get_or_err("localId").map(|it| it.parse())??, + type_name: hm.get_or_err("typeName")?, + instance_name: hm.remove("instanceName"), + execution_order_id: hm.get("executionOrderId").map(|it| it.parse()).transpose()?, + variables, + }) + } +} + +impl Parseable for Block { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + let attributes = reader.attributes()?; + let mut variables = Vec::new(); + + loop { + match reader.peek()? { + Event::Start(tag) => match tag.name().as_ref() { + b"inputVariables" | b"outputVariables" | b"inOutVariables" => { + variables.extend(BlockVariable::visit(reader)?) + } + _ => reader.consume()?, + }, + + Event::End(tag) if tag.name().as_ref() == b"block" => { + reader.consume()?; + break; + } + + Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"block"])), + _ => reader.consume()?, + } + } + + Block::new(attributes, variables) + } +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::{ + model::block::Block, + reader::PeekableReader, + serializer::{XBlock, XInOutVariables, XInputVariables, XOutputVariables, XVariable}, + xml_parser::Parseable, + }; + + #[test] + fn add_block() { + let content = XBlock::init("1", "ADD", "0") + .with_input_variables( + XInputVariables::new() + .with_variable(XVariable::init("a", false).with_connection_in_initialized("1")) + .with_variable(XVariable::init("b", false).with_connection_in_initialized("2")), + ) + .with_inout_variables(XInOutVariables::new().close()) + .with_output_variables( + XOutputVariables::new() + .with_variable(XVariable::init("c", false).with_connection_out_initialized()), + ) + .serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Block::visit(&mut reader).unwrap()); + } +} diff --git a/compiler/plc_xml/src/model/body.rs b/compiler/plc_xml/src/model/body.rs new file mode 100644 index 0000000000..7cd92874f0 --- /dev/null +++ b/compiler/plc_xml/src/model/body.rs @@ -0,0 +1,90 @@ +use quick_xml::events::Event; + +use super::fbd::FunctionBlockDiagram; +use crate::{error::Error, reader::PeekableReader, xml_parser::Parseable}; + +#[derive(Debug, Default)] +pub(crate) struct Body { + pub function_block_diagram: Option, +} + +impl Body { + fn new(fbd: Option) -> Result { + Ok(Self { function_block_diagram: fbd }) + } + + fn empty() -> Result { + Ok(Self { function_block_diagram: None }) + } +} + +impl Parseable for Body { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + loop { + match reader.peek()? { + Event::Start(tag) => match tag.name().as_ref() { + b"FBD" => { + let fbd = FunctionBlockDiagram::visit(reader)?; + reader.consume_until(vec![b"body"])?; + + return Body::new(Some(fbd)); + } + _ => reader.consume()?, + }, + Event::Empty(tag) if tag.name().as_ref() == b"FBD" => return Body::empty(), + Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"body"])), + _ => reader.consume()?, + } + } + } +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::{ + model::body::Body, + reader::PeekableReader, + serializer::{XBlock, XBody, XFbd, XInOutVariables, XInputVariables, XOutputVariables, XVariable}, + xml_parser::Parseable, + }; + + #[test] + fn empty() { + let content = XBody::new().with_fbd(XFbd::new().close()).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Body::visit(&mut reader).unwrap()); + } + + #[test] + fn fbd_with_add_block() { + let content = XBody::new() + .with_fbd( + XFbd::new().with_block( + XBlock::init("1", "ADD", "0") + .with_input_variables( + XInputVariables::new() + .with_variable( + XVariable::init("a", false).with_connection_in_initialized("1"), + ) + .with_variable( + XVariable::init("b", false).with_connection_in_initialized("2"), + ), + ) + .with_inout_variables(XInOutVariables::new().close()) + .with_output_variables( + XOutputVariables::new() + .with_variable(XVariable::init("c", false).with_connection_out_initialized()), + ), + ), + ) + .serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Body::visit(&mut reader).unwrap()); + } +} diff --git a/compiler/plc_xml/src/model/connector.rs b/compiler/plc_xml/src/model/connector.rs new file mode 100644 index 0000000000..de385e8d9c --- /dev/null +++ b/compiler/plc_xml/src/model/connector.rs @@ -0,0 +1,73 @@ +use std::collections::HashMap; + +use quick_xml::events::Event; + +use crate::{ + error::Error, + extensions::{GetOrErr, TryToString}, + reader::PeekableReader, + xml_parser::Parseable, +}; + +#[derive(Debug, PartialEq)] +pub(crate) struct Connector { + pub kind: ConnectorKind, + pub name: String, + pub local_id: usize, + pub ref_local_id: Option, + pub formal_parameter: Option, +} + +impl Connector { + pub fn new(mut hm: HashMap, kind: ConnectorKind) -> Result { + Ok(Self { + kind, + name: hm.get_or_err("name")?, + local_id: hm.get_or_err("localId").map(|it| it.parse())??, + ref_local_id: hm.get("refLocalId").map(|it| it.parse()).transpose()?, + formal_parameter: hm.remove("formalParameter"), + }) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) enum ConnectorKind { + Source, + Sink, +} + +impl Parseable for Connector { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + let next = reader.peek()?; + let kind = match &next { + Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() { + b"connector" => ConnectorKind::Sink, + b"continuation" => ConnectorKind::Source, + _ => return Err(Error::UnexpectedElement(tag.name().try_to_string()?)), + }, + + _ => unreachable!(), + }; + + let mut attributes = reader.attributes()?; + loop { + match reader.peek()? { + Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() { + b"connection" => attributes.extend(reader.attributes()?), + _ => reader.consume()?, + }, + + Event::End(tag) if matches!(tag.name().as_ref(), b"connector" | b"continuation") => { + reader.consume()?; + break; + } + + _ => reader.consume()?, + } + } + + Connector::new(attributes, kind) + } +} diff --git a/compiler/plc_xml/src/model/control.rs b/compiler/plc_xml/src/model/control.rs new file mode 100644 index 0000000000..eb2785adcc --- /dev/null +++ b/compiler/plc_xml/src/model/control.rs @@ -0,0 +1,85 @@ +use std::{collections::HashMap, str::FromStr}; + +use quick_xml::events::Event; + +use crate::{ + error::Error, + extensions::{GetOrErr, TryToString}, + reader::PeekableReader, + xml_parser::Parseable, +}; + +#[derive(Debug, PartialEq)] +pub(crate) struct Control { + pub kind: ControlKind, + pub name: Option, + pub local_id: usize, + pub ref_local_id: Option, + pub execution_order_id: Option, + pub negated: bool, +} + +impl Control { + pub fn new(mut hm: HashMap, kind: ControlKind) -> Result { + Ok(Self { + kind, + name: hm.remove("label"), + local_id: hm.get_or_err("localId").map(|it| it.parse())??, + ref_local_id: hm.get("refLocalId").map(|it| it.parse()).transpose()?, + execution_order_id: hm.get("executionOrderId").map(|it| it.parse()).transpose()?, + negated: hm.get("negated").map(|it| it == "true").unwrap_or(false), + }) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) enum ControlKind { + Jump, + Label, + Return, +} + +impl FromStr for ControlKind { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "jump" => Ok(ControlKind::Jump), + "label" => Ok(ControlKind::Label), + "return" => Ok(ControlKind::Return), + _ => Err(Error::UnexpectedElement(s.to_string())), + } + } +} + +impl Parseable for Control { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + let kind = match reader.peek()? { + Event::Start(tag) | Event::Empty(tag) => ControlKind::from_str(&tag.name().try_to_string()?)?, + _ => unreachable!(), + }; + let mut attributes = reader.attributes()?; + + loop { + match reader.peek()? { + Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() { + b"negated" => attributes.extend(reader.attributes()?), + b"connection" => attributes.extend(reader.attributes()?), + _ => reader.consume()?, + }, + + Event::End(tag) if matches!(tag.name().as_ref(), b"jump" | b"label" | b"return") => { + reader.consume()?; + break; + } + + Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"block"])), + _ => reader.consume()?, + } + } + + Control::new(attributes, kind) + } +} diff --git a/compiler/plc_xml/src/model/fbd.rs b/compiler/plc_xml/src/model/fbd.rs new file mode 100644 index 0000000000..83bbb5e676 --- /dev/null +++ b/compiler/plc_xml/src/model/fbd.rs @@ -0,0 +1,174 @@ +use std::cmp::Ordering; + +use indexmap::IndexMap; +use quick_xml::events::Event; + +use crate::{error::Error, reader::PeekableReader, xml_parser::Parseable}; + +use super::{block::Block, connector::Connector, control::Control, variables::FunctionBlockVariable}; + +/// Represent either a `localId` or `refLocalId` +pub(crate) type NodeId = usize; +pub(crate) type NodeIndex = IndexMap; + +#[derive(Debug, Default)] +pub(crate) struct FunctionBlockDiagram { + pub nodes: NodeIndex, +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Node { + Block(Block), + FunctionBlockVariable(FunctionBlockVariable), + Control(Control), + Connector(Connector), +} + +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option { + let left = self.get_exec_id(); + let right = other.get_exec_id(); + + match (left, right) { + (None, None) => Some(Ordering::Equal), + (None, Some(_)) => Some(Ordering::Less), + (Some(_), None) => Some(Ordering::Greater), + (Some(left), Some(right)) => Some(left.cmp(&right)), + } + } +} + +impl Node { + pub(crate) fn get_exec_id(&self) -> Option { + match self { + Node::Block(val) => val.execution_order_id, + Node::FunctionBlockVariable(val) => val.execution_order_id, + Node::Control(val) => val.execution_order_id, + _ => None, + } + } + + fn get_id(&self) -> NodeId { + match self { + Node::Block(val) => val.local_id, + Node::FunctionBlockVariable(val) => val.local_id, + Node::Control(val) => val.local_id, + Node::Connector(val) => val.local_id, + } + } + + fn get_name(&self) -> String { + if let Node::Block(val) = self { + // TODO: check if the out variables are named after the type- or instance-name + val.type_name.clone() + } else { + "".into() + } + } +} + +impl Parseable for FunctionBlockDiagram { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + reader.consume()?; + let mut nodes = IndexMap::new(); + + loop { + match reader.peek()? { + Event::Start(tag) => match tag.name().as_ref() { + b"block" => { + let node = Block::visit(reader)?; + nodes.insert(node.local_id, Node::Block(node)); + } + b"jump" | b"label" | b"return" => { + let node = Control::visit(reader)?; + nodes.insert(node.local_id, Node::Control(node)); + } + b"inVariable" | b"outVariable" => { + let node = FunctionBlockVariable::visit(reader)?; + nodes.insert(node.local_id, Node::FunctionBlockVariable(node)); + } + b"continuation" | b"connector" => { + let node = Connector::visit(reader)?; + nodes.insert(node.local_id, Node::Connector(node)); + } + _ => reader.consume()?, + }, + + Event::End(tag) if tag.name().as_ref() == b"FBD" => { + reader.consume()?; + break; + } + _ => reader.consume()?, + } + } + + nodes.sort_by(|_, b, _, d| b.partial_cmp(d).unwrap()); // This _shouldn't_ panic because our `partial_cmp` method covers all cases + Ok(FunctionBlockDiagram { nodes }) + } +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::{ + model::fbd::FunctionBlockDiagram, + reader::PeekableReader, + serializer::{ + XBlock, XConnection, XConnectionPointIn, XConnectionPointOut, XExpression, XFbd, XInOutVariables, + XInVariable, XInputVariables, XOutVariable, XOutputVariables, XPosition, XRelPosition, XVariable, + }, + xml_parser::Parseable, + }; + + #[test] + fn add_block() { + let content = XFbd::new() + .with_block( + XBlock::init("1", "ADD", "0") + .with_input_variables( + XInputVariables::new() + .with_variable(XVariable::init("a", false).with_connection_in_initialized("1")) + .with_variable(XVariable::init("b", false).with_connection_in_initialized("2")), + ) + .with_inout_variables(XInOutVariables::new().close()) + .with_output_variables( + XOutputVariables::new() + .with_variable(XVariable::init("c", false).with_connection_out_initialized()), + ), + ) + .with_in_variable( + XInVariable::init("2", false) + .with_position(XPosition::new().close()) + .with_connection_point_out( + XConnectionPointOut::new().with_rel_position(XRelPosition::init()), + ) + .with_expression(XExpression::new().with_data("a")), + ) + .with_in_variable( + XInVariable::init("3", false) + .with_position(XPosition::new().close()) + .with_connection_point_out( + XConnectionPointOut::new().with_rel_position(XRelPosition::init()), + ) + .with_expression(XExpression::new().with_data("b")), + ) + .with_out_variable( + XOutVariable::init("4", false) + .with_position(XPosition::new().close()) + .with_attribute("executionOrderId", "1") + .with_connection_point_in( + XConnectionPointIn::new() + .with_rel_position(XRelPosition::init()) + .with_connection(XConnection::new().with_attribute("refLocalId", "1")), + ) + .with_expression(XExpression::new().with_data("c")), + ) + .serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(FunctionBlockDiagram::visit(&mut reader).unwrap()); + } +} diff --git a/compiler/plc_xml/src/model/interface.rs b/compiler/plc_xml/src/model/interface.rs new file mode 100644 index 0000000000..e324314b2c --- /dev/null +++ b/compiler/plc_xml/src/model/interface.rs @@ -0,0 +1,59 @@ +use super::pou::PouType; + +#[derive(Debug)] +pub(crate) struct Interface { + //TODO: return type, variables, etc.. + // we only care about the addData field for now, as it contains our text-declaration + + // the addData field is be application-specific and can be implemented/extended as needed + add_data: Option, +} + +// XXX: this implementation is very specific to our own format. we might want to make it generic/provide an interface via traits +// so it can be easily modified/extended for other purposes +impl Interface { + pub fn new(content: &str) -> Self { + Interface { add_data: Some(Data::new_implementation(content)) } + } + + pub fn get_data_content(&self) -> Option<&str> { + let Some(ref data) = self.add_data else { + return None + }; + + Some(&data.content) + } + + // We have to append a END_... to the declaration, as it is missing in our text declaration + pub fn append_end_keyword(self, pou_type: &PouType) -> Self { + let Some(old_data) = self.add_data else { + // if we have no content, we have nothing to append to. return as is + return self + }; + + Interface { + add_data: Some(Data::new_implementation(&format!("{}\nEND_{}", old_data.content, pou_type))), + } + } +} + +/// Application specific data +#[derive(Debug)] +struct Data { + content: String, + handle: HandleUnknown, +} + +impl Data { + fn new_implementation(content: &str) -> Self { + Data { content: content.to_string(), handle: HandleUnknown::Implementation } + } +} + +/// Recommended processor-handling for unknown data elements +#[derive(Debug)] +enum HandleUnknown { + Preserve, + Discard, + Implementation, +} diff --git a/compiler/plc_xml/src/model/pou.rs b/compiler/plc_xml/src/model/pou.rs new file mode 100644 index 0000000000..ee0b745ac8 --- /dev/null +++ b/compiler/plc_xml/src/model/pou.rs @@ -0,0 +1,197 @@ +use std::{collections::HashMap, str::FromStr}; + +use quick_xml::events::Event; + +use crate::{error::Error, extensions::GetOrErr, reader::PeekableReader, xml_parser::Parseable}; + +use super::{action::Action, body::Body, interface::Interface}; + +#[derive(Debug, Default)] +pub(crate) struct Pou { + pub name: String, + pub pou_type: PouType, + pub body: Body, + pub actions: Vec, + pub interface: Option, +} + +impl Pou { + fn with_attributes(self, attributes: HashMap) -> Result { + Ok(Pou { + name: attributes.get_or_err("name")?, + pou_type: attributes.get_or_err("pouType").map(|it| it.parse())??, + body: self.body, + actions: self.actions, + interface: self.interface, + }) + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub(crate) enum PouType { + #[default] + Program, + Function, + FunctionBlock, +} + +impl std::fmt::Display for PouType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PouType::Program => write!(f, "PROGRAM"), + PouType::Function => write!(f, "FUNCTION"), + PouType::FunctionBlock => write!(f, "FUNCTION_BLOCK"), + } + } +} + +impl TryFrom<&str> for PouType { + type Error = Error; + + fn try_from(value: &str) -> Result { + match value { + "program" => Ok(PouType::Program), + "function" => Ok(PouType::Function), + "functionBlock" => Ok(PouType::FunctionBlock), + _ => Err(Error::UnexpectedElement(value.to_string())), + } + } +} + +impl Parseable for Pou { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + let mut pou = Pou::default().with_attributes(reader.attributes()?)?; + loop { + match reader.peek()? { + Event::Start(tag) => match tag.name().as_ref() { + b"interface" => { + // XXX: this is very specific to our own xml schema, but does not adhere to the plc open standard + reader.consume_until_start(b"content")?; + match reader.next()? { + Event::Start(tag) => { + pou.interface = Some(Interface::new(&reader.read_text(tag.name())?)) + } + _ => reader.consume()?, + } + } + b"body" => { + pou.body = Body::visit(reader)?; + if let Some(interface) = pou.interface { + pou.interface = Some(interface.append_end_keyword(&pou.pou_type)); + } + + // TODO: change in order to parse INTERFACE, ACTION etc.. + reader.consume_until(vec![b"pou"])?; + return Ok(pou); + } + + _ => reader.consume()?, + }, + + Event::End(tag) if tag.name().as_ref() == b"pou" => return Ok(pou), + + _ => reader.consume()?, + } + } + } +} + +impl FromStr for PouType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "program" => Ok(PouType::Program), + "function" => Ok(PouType::Function), + "functionBlock" => Ok(PouType::FunctionBlock), + _ => Err(Error::UnexpectedElement(s.to_string())), + } + } +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::{ + model::{pou::Pou, project::Project}, + reader::PeekableReader, + serializer::{ + XAddData, XBody, XContent, XData, XFbd, XInterface, XLocalVars, XPou, XTextDeclaration, + }, + xml_parser::Parseable, + }; + + #[test] + fn empty() { + let content = XPou::new() + .with_attribute("xmlns", "http://www.plcopen.org/xml/tc6_0201") + .with_attribute("name", "foo") + .with_attribute("pouType", "program") + .with_interface( + XInterface::new().with_local_vars(XLocalVars::new().close()).with_add_data( + XAddData::new().with_data_data( + XData::new() + .with_attribute("name", "www.bachmann.at/plc/plcopenxml") + .with_attribute("handleUnknown", "implementation") + .with_text_declaration(XTextDeclaration::new().with_content( + XContent::new().with_data( + r#" +PROGRAM foo +VAR + +END_VAR + "#, + ), + )), + ), + ), + ) + .with_body(XBody::new().with_fbd(XFbd::new().close())) + .serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Project::pou_entry(&mut reader)); + } + + #[test] + fn poutype_program() { + let content = + XPou::new().with_attribute("name", "foo").with_attribute("pouType", "program").serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Pou::visit(&mut reader)); + } + + #[test] + fn poutype_function() { + let content = + XPou::new().with_attribute("name", "foo").with_attribute("pouType", "function").serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Pou::visit(&mut reader)); + } + + #[test] + fn poutype_function_block() { + let content = + XPou::new().with_attribute("name", "foo").with_attribute("pouType", "functionBlock").serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(Pou::visit(&mut reader)); + } + + #[test] + fn poutype_unknown() { + let content = + XPou::new().with_attribute("name", "foo").with_attribute("pouType", "asdasd").serialize(); + + let mut reader = PeekableReader::new(&content); + assert_eq!( + Pou::visit(&mut reader).unwrap_err().to_string(), + "Found an unexpected element 'asdasd'".to_string() + ); + } +} diff --git a/compiler/plc_xml/src/model/project.rs b/compiler/plc_xml/src/model/project.rs new file mode 100644 index 0000000000..04a5d5fb27 --- /dev/null +++ b/compiler/plc_xml/src/model/project.rs @@ -0,0 +1,32 @@ +use crate::xml_parser::Parseable; + +use super::pou::Pou; + +/// The Project root as specified in the official XSD +#[derive(Debug)] +pub(crate) struct Project { + pub pous: Vec, + /* + attributes, + dataTypes, + fileHeader, + contentHeader, + instances, + addData, + documentation + */ +} + +impl Parseable for Project { + type Item = Self; + + fn visit(_reader: &mut crate::reader::PeekableReader) -> Result { + unimplemented!() + } +} + +impl Project { + pub fn pou_entry(reader: &mut crate::reader::PeekableReader) -> Result { + Ok(Project { pous: vec![Pou::visit(reader)?] }) + } +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__block__tests__add_block.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__block__tests__add_block.snap new file mode 100644 index 0000000000..9c51c9b4f6 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__block__tests__add_block.snap @@ -0,0 +1,46 @@ +--- +source: compiler/plc_cfc/src/model/block.rs +expression: "Block::visit(&mut reader).unwrap()" +--- +Block { + local_id: 1, + global_id: None, + type_name: "ADD", + instance_name: None, + execution_order_id: Some( + 0, + ), + variables: [ + BlockVariable { + kind: Input, + formal_parameter: "a", + negated: false, + ref_local_id: Some( + 1, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Input, + formal_parameter: "b", + negated: false, + ref_local_id: Some( + 2, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Output, + formal_parameter: "c", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__body__tests__body.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__body__tests__body.snap new file mode 100644 index 0000000000..ac6c26b97b --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__body__tests__body.snap @@ -0,0 +1,9 @@ +--- +source: compiler/plc_cfc/src/model/body.rs +expression: "Body::visit(&mut reader).unwrap()" +--- +Body { + function_block_diagram: FunctionBlockDiagram { + nodes: {}, + }, +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__block_output_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__block_output_variable.snap new file mode 100644 index 0000000000..067cc965de --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__block_output_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler/plc_cfc/src/model/variables.rs +expression: "BlockVariable::visit(&mut reader)" +--- +Ok( + [ + BlockVariable { + kind: Output, + formal_parameter: "", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__input_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__input_variable.snap new file mode 100644 index 0000000000..eef43b2822 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__input_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler/plc_cfc/src/model/variables.rs +expression: "BlockVariable::visit(&mut reader)" +--- +Ok( + [ + BlockVariable { + kind: Input, + formal_parameter: "", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_in.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_in.snap new file mode 100644 index 0000000000..74dd052492 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_in.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_cfc/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "formalParameter": "", + "negated": "false", + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_out.snap b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_out.snap new file mode 100644 index 0000000000..e97b9cd665 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_cfc__model__variables__tests__variable_with_connection_point_out.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_cfc/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "negated": "false", + "formalParameter": "", + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__block__tests__add_block.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__block__tests__add_block.snap new file mode 100644 index 0000000000..b16550e7be --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__block__tests__add_block.snap @@ -0,0 +1,45 @@ +--- +source: compiler/plc_xml/src/model/block.rs +expression: "Block::visit(&mut reader).unwrap()" +--- +Block { + local_id: 1, + type_name: "ADD", + instance_name: None, + execution_order_id: Some( + 0, + ), + variables: [ + BlockVariable { + kind: Input, + formal_parameter: "a", + negated: false, + ref_local_id: Some( + 1, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Input, + formal_parameter: "b", + negated: false, + ref_local_id: Some( + 2, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Output, + formal_parameter: "c", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__empty.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__empty.snap new file mode 100644 index 0000000000..ce3f01bf63 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__empty.snap @@ -0,0 +1,7 @@ +--- +source: compiler/plc_xml/src/model/body.rs +expression: "Body::visit(&mut reader).unwrap()" +--- +Body { + function_block_diagram: None, +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__fbd_with_add_block.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__fbd_with_add_block.snap new file mode 100644 index 0000000000..a02f652cb6 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__body__tests__fbd_with_add_block.snap @@ -0,0 +1,55 @@ +--- +source: compiler/plc_xml/src/model/body.rs +expression: "Body::visit(&mut reader).unwrap()" +--- +Body { + function_block_diagram: Some( + FunctionBlockDiagram { + nodes: { + 1: Block( + Block { + local_id: 1, + type_name: "ADD", + instance_name: None, + execution_order_id: Some( + 0, + ), + variables: [ + BlockVariable { + kind: Input, + formal_parameter: "a", + negated: false, + ref_local_id: Some( + 1, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Input, + formal_parameter: "b", + negated: false, + ref_local_id: Some( + 2, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Output, + formal_parameter: "c", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], + }, + ), + }, + }, + ), +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__fbd__tests__add_block.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__fbd__tests__add_block.snap new file mode 100644 index 0000000000..eecf585852 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__fbd__tests__add_block.snap @@ -0,0 +1,85 @@ +--- +source: compiler/plc_xml/src/model/fbd.rs +expression: "FunctionBlockDiagram::visit(&mut reader).unwrap()" +--- +FunctionBlockDiagram { + nodes: { + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 2, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + 3: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 3, + negated: false, + expression: "b", + execution_order_id: None, + ref_local_id: None, + }, + ), + 1: Block( + Block { + local_id: 1, + type_name: "ADD", + instance_name: None, + execution_order_id: Some( + 0, + ), + variables: [ + BlockVariable { + kind: Input, + formal_parameter: "a", + negated: false, + ref_local_id: Some( + 1, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Input, + formal_parameter: "b", + negated: false, + ref_local_id: Some( + 2, + ), + edge: None, + storage: None, + enable: None, + }, + BlockVariable { + kind: Output, + formal_parameter: "c", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], + }, + ), + 4: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 4, + negated: false, + expression: "c", + execution_order_id: Some( + 1, + ), + ref_local_id: Some( + 1, + ), + }, + ), + }, +} diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__empty.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__empty.snap new file mode 100644 index 0000000000..4d70410248 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__empty.snap @@ -0,0 +1,28 @@ +--- +source: compiler/plc_xml/src/model/pou.rs +expression: "Project::pou_entry(&mut reader)" +--- +Ok( + Project { + pous: [ + Pou { + name: "foo", + pou_type: Program, + body: Body { + function_block_diagram: None, + }, + actions: [], + interface: Some( + Interface { + add_data: Some( + Data { + content: "\nPROGRAM foo\nVAR\n\nEND_VAR\n \nEND_PROGRAM", + handle: Implementation, + }, + ), + }, + ), + }, + ], + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function.snap new file mode 100644 index 0000000000..84a68ba24f --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function.snap @@ -0,0 +1,15 @@ +--- +source: compiler/plc_xml/src/model/pou.rs +expression: "Pou::visit(&mut reader)" +--- +Ok( + Pou { + name: "foo", + pou_type: Function, + body: Body { + function_block_diagram: None, + }, + actions: [], + interface: None, + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function_block.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function_block.snap new file mode 100644 index 0000000000..e12178303f --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_function_block.snap @@ -0,0 +1,15 @@ +--- +source: compiler/plc_xml/src/model/pou.rs +expression: "Pou::visit(&mut reader)" +--- +Ok( + Pou { + name: "foo", + pou_type: FunctionBlock, + body: Body { + function_block_diagram: None, + }, + actions: [], + interface: None, + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_program.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_program.snap new file mode 100644 index 0000000000..0b627fdd00 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__pou__tests__poutype_program.snap @@ -0,0 +1,15 @@ +--- +source: compiler/plc_xml/src/model/pou.rs +expression: "Pou::visit(&mut reader)" +--- +Ok( + Pou { + name: "foo", + pou_type: Program, + body: Body { + function_block_diagram: None, + }, + actions: [], + interface: None, + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_inout_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_inout_variable.snap new file mode 100644 index 0000000000..25a8ff019b --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_inout_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: "BlockVariable::visit(&mut reader)" +--- +Ok( + [ + BlockVariable { + kind: InOut, + formal_parameter: "", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_input_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_input_variable.snap new file mode 100644 index 0000000000..6684d93f29 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_input_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: "BlockVariable::visit(&mut reader)" +--- +Ok( + [ + BlockVariable { + kind: Input, + formal_parameter: "", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_output_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_output_variable.snap new file mode 100644 index 0000000000..fa63b1e734 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__block_output_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: "BlockVariable::visit(&mut reader)" +--- +Ok( + [ + BlockVariable { + kind: Output, + formal_parameter: "", + negated: false, + ref_local_id: None, + edge: None, + storage: None, + enable: None, + }, + ], +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_in_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_in_variable.snap new file mode 100644 index 0000000000..3c2e975ffa --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_in_variable.snap @@ -0,0 +1,14 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: "FunctionBlockVariable::visit(&mut reader)" +--- +Ok( + FunctionBlockVariable { + kind: Input, + local_id: 0, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_out_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_out_variable.snap new file mode 100644 index 0000000000..aac3ff770c --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__fbd_out_variable.snap @@ -0,0 +1,14 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: "FunctionBlockVariable::visit(&mut reader)" +--- +Ok( + FunctionBlockVariable { + kind: Output, + local_id: 0, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__negated_variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__negated_variable.snap new file mode 100644 index 0000000000..25fdcc655f --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__negated_variable.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "formalParameter": "", + "negated": "true", + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable.snap new file mode 100644 index 0000000000..7a3f6d8357 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "negated": "false", + "formalParameter": "", + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_in.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_in.snap new file mode 100644 index 0000000000..7a3f6d8357 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_in.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "negated": "false", + "formalParameter": "", + }, +) diff --git a/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_out.snap b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_out.snap new file mode 100644 index 0000000000..80d6d70600 --- /dev/null +++ b/compiler/plc_xml/src/model/snapshots/plc_xml__model__variables__tests__variable_with_connection_point_out.snap @@ -0,0 +1,10 @@ +--- +source: compiler/plc_xml/src/model/variables.rs +expression: visit_variable(&mut reader) +--- +Ok( + { + "formalParameter": "", + "negated": "false", + }, +) diff --git a/compiler/plc_xml/src/model/variables.rs b/compiler/plc_xml/src/model/variables.rs new file mode 100644 index 0000000000..916a267395 --- /dev/null +++ b/compiler/plc_xml/src/model/variables.rs @@ -0,0 +1,276 @@ +use quick_xml::events::Event; + +use crate::extensions::GetOrErr; +use crate::xml_parser::Parseable; +use crate::{error::Error, extensions::TryToString, reader::PeekableReader}; +use std::{collections::HashMap, str::FromStr}; + +#[derive(Debug, PartialEq)] +pub(crate) struct BlockVariable { + pub kind: VariableKind, + pub formal_parameter: String, + pub negated: bool, + pub ref_local_id: Option, + pub edge: Option, + pub storage: Option, + pub enable: Option, +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Edge { + Falling, + Rising, +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Storage { + Set, + Reset, +} + +impl BlockVariable { + pub fn new(hm: HashMap, kind: VariableKind) -> Result { + Ok(Self { + kind, + formal_parameter: hm.get_or_err("formalParameter")?, + negated: hm.get_or_err("negated").map(|it| it == "true")?, + ref_local_id: hm.get("refLocalId").map(|it| it.parse()).transpose()?, + edge: hm.get("edge").map(|it| it.parse()).transpose()?, + storage: hm.get("storage").map(|it| it.parse()).transpose()?, + enable: hm.get("enable").map(|it| it == "true"), + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum VariableKind { + Input, + Output, + InOut, + Temp, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct FunctionBlockVariable { + pub kind: VariableKind, + pub local_id: usize, + pub negated: bool, + pub expression: String, + pub execution_order_id: Option, + pub ref_local_id: Option, +} + +impl FunctionBlockVariable { + pub fn new(hm: HashMap, kind: VariableKind) -> Result { + Ok(Self { + kind, + local_id: hm.get_or_err("localId").map(|it| it.parse())??, + negated: hm.get_or_err("negated").map(|it| it == "true")?, + expression: hm.get_or_err("expression")?, + execution_order_id: hm.get("executionOrderId").map(|it| it.parse()).transpose()?, + ref_local_id: hm.get("refLocalId").map(|it| it.parse()).transpose()?, + }) + } +} + +impl TryFrom<&[u8]> for VariableKind { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + match value { + b"inputVariables" | b"inVariable" => Ok(VariableKind::Input), + b"outputVariables" | b"outVariable" => Ok(VariableKind::Output), + b"inOutVariables" | b"inOutVariable" => Ok(VariableKind::InOut), + _ => { + let value = std::str::from_utf8(value).map_err(Error::Encoding)?; + Err(Error::UnexpectedElement(value.to_string())) + } + } + } +} + +impl FromStr for Edge { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "falling" => Ok(Edge::Falling), + "rising" => Ok(Edge::Rising), + _ => Err(Error::UnexpectedElement(s.to_string())), + } + } +} + +impl FromStr for Storage { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "set" => Ok(Storage::Set), + "reset" => Ok(Storage::Reset), + _ => Err(Error::UnexpectedElement(s.to_string())), + } + } +} + +impl Parseable for FunctionBlockVariable { + type Item = Self; + + fn visit(reader: &mut PeekableReader) -> Result { + let next = reader.peek()?; + let kind = match &next { + Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() { + b"inVariable" => VariableKind::Input, + b"outVariable" => VariableKind::Output, + b"inOutVariable" => VariableKind::InOut, + _ => unreachable!(), + }, + + _ => unreachable!(), + }; + + let mut attributes = reader.attributes()?; + loop { + match reader.peek()? { + Event::Start(tag) | Event::Empty(tag) if tag.name().as_ref() == b"connection" => { + attributes.extend(reader.attributes()?); + } + + Event::Text(tag) => { + attributes.insert("expression".into(), tag.as_ref().try_to_string()?); + reader.consume()?; + } + + Event::End(tag) => match tag.name().as_ref() { + b"inVariable" | b"outVariable" => { + reader.consume()?; + break; + } + _ => reader.consume()?, + }, + + _ => reader.consume()?, + } + } + + FunctionBlockVariable::new(attributes, kind) + } +} + +impl Parseable for BlockVariable { + type Item = Vec; + + fn visit(reader: &mut PeekableReader) -> Result { + let kind = match reader.next()? { + Event::Start(tag) | Event::Empty(tag) => VariableKind::try_from(tag.name().as_ref())?, + _ => unreachable!(), + }; + + let mut res = vec![]; + + loop { + match reader.peek()? { + Event::Start(tag) if tag.name().as_ref() == b"variable" => { + let attributes = visit_variable(reader)?; + res.push(BlockVariable::new(attributes, kind)?); + } + + Event::End(tag) + if matches!( + tag.name().as_ref(), + b"inputVariables" | b"outputVariables" | b"inOutVariables" + ) => + { + reader.consume()?; + return Ok(res); + } + + Event::Eof => { + return Err(Error::UnexpectedEndOfFile(vec![ + b"inputVariables", + b"outputVariables", + b"inOutVariables", + ])) + } + _ => reader.consume()?, + }; + } + } +} + +fn visit_variable(reader: &mut PeekableReader) -> Result, Error> { + let mut attributes = HashMap::new(); + loop { + match reader.peek()? { + Event::Start(tag) | Event::Empty(tag) => match tag.name().as_ref() { + b"variable" | b"connection" => attributes.extend(reader.attributes()?), + _ => reader.consume()?, + }, + + Event::End(tag) if tag.name().as_ref() == b"variable" => { + reader.consume()?; + break; + } + _ => reader.consume()?, + } + } + + Ok(attributes) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::{ + model::variables::{BlockVariable, FunctionBlockVariable}, + reader::PeekableReader, + serializer::{ + XExpression, XInOutVariables, XInVariable, XInputVariables, XOutVariable, XOutputVariables, + XVariable, + }, + xml_parser::Parseable, + }; + + #[test] + fn block_input_variable() { + let content = XInputVariables::new().with_variable(XVariable::init("", false)).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(BlockVariable::visit(&mut reader)); + } + + #[test] + fn block_output_variable() { + let content = XOutputVariables::new().with_variable(XVariable::init("", false)).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(BlockVariable::visit(&mut reader)); + } + + #[test] + fn block_inout_variable() { + let content = XInOutVariables::new().with_variable(XVariable::init("", false)).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(BlockVariable::visit(&mut reader)); + } + + #[test] + fn fbd_in_variable() { + let content = + XInVariable::init("0", false).with_expression(XExpression::new().with_data("a")).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(FunctionBlockVariable::visit(&mut reader)); + } + + #[test] + fn fbd_out_variable() { + let content = + XOutVariable::init("0", false).with_expression(XExpression::new().with_data("a")).serialize(); + + let mut reader = PeekableReader::new(&content); + assert_debug_snapshot!(FunctionBlockVariable::visit(&mut reader)); + } +} diff --git a/compiler/plc_xml/src/reader.rs b/compiler/plc_xml/src/reader.rs new file mode 100644 index 0000000000..ad7f296da7 --- /dev/null +++ b/compiler/plc_xml/src/reader.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; + +use quick_xml::{events::Event, name::QName, Reader}; + +use crate::{error::Error, extensions::TryToString}; + +pub(crate) struct PeekableReader<'xml> { + reader: Reader<&'xml [u8]>, + peeked: Option>, +} + +impl<'xml> PeekableReader<'xml> { + pub fn new(content: &'xml str) -> Self { + PeekableReader { + reader: { + let mut reader = Reader::from_str(content); + reader.trim_text(true); + reader + }, + peeked: None, + } + } + + pub fn next(&mut self) -> Result, Error> { + if let Some(event) = self.peeked.take() { + return Ok(event); + } + + self.reader.read_event().map_err(Error::ReadEvent) + } + + pub fn peek(&mut self) -> Result<&Event<'xml>, Error> { + if self.peeked.is_none() { + self.peeked = Some(self.reader.read_event().map_err(Error::ReadEvent)?); + } + + match self.peeked.as_ref() { + Some(val) => Ok(val), + None => unreachable!(), + } + } + + // Advances the reader until it sees one of the defined token and stops after consuming it + pub fn consume_until(&mut self, tokens: Vec<&'static [u8]>) -> Result<(), Error> { + loop { + match self.next()? { + Event::End(tag) if tokens.contains(&tag.name().as_ref()) => break, + Event::Eof => return Err(Error::UnexpectedEndOfFile(tokens)), + _ => continue, + } + } + + Ok(()) + } + + // Advances the reader until it sees the defined token and stops without consuming it + pub fn consume_until_start(&mut self, token: &'static [u8]) -> Result<(), Error> { + loop { + match self.peek()? { + Event::Start(tag) if token == tag.name().as_ref() => break, + Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![token])), + _ => self.consume()?, + } + } + + Ok(()) + } + + /// Advances the reader consuming the event without returning it. + pub fn consume(&mut self) -> Result<(), Error> { + self.next()?; + Ok(()) + } + + /// Advances the reader, consuming the event returning its attributes. + pub fn attributes(&mut self) -> Result, Error> { + let tag = match self.next()? { + Event::Start(tag) | Event::Empty(tag) => tag, + _ => todo!(), + }; + + let mut hm = HashMap::new(); + for it in tag.attributes().flatten() { + hm.insert(it.key.try_to_string()?, it.value.try_to_string()?); + } + + Ok(hm) + } + + pub fn read_text(&mut self, name: QName) -> Result { + Ok(self.reader.read_text(name).map_err(Error::ReadEvent)?.to_string()) + } +} + +#[test] +fn peek() { + const CONTENT: &str = r#" + + + + + + + + + + + + + + + + + + + + + + + + local_a + + + + "#; + + let mut temp = PeekableReader::new(CONTENT); + assert_eq!(temp.peek().unwrap(), &Event::Start(quick_xml::events::BytesStart::new("body"))); + assert_eq!(temp.next().unwrap(), Event::Start(quick_xml::events::BytesStart::new("body"))); +} diff --git a/compiler/plc_xml/src/serializer.rs b/compiler/plc_xml/src/serializer.rs new file mode 100644 index 0000000000..f4e4e19ab6 --- /dev/null +++ b/compiler/plc_xml/src/serializer.rs @@ -0,0 +1,379 @@ +type Attributes = Vec<(&'static str, &'static str)>; + +/// Number of spaces to use when indenting XML +const INDENT_SPACES: usize = 4; + +#[derive(Debug, Default)] +enum Content { + Node(Vec), + Data(&'static str), + + #[default] + Empty, +} + +impl<'b> Content { + fn push(&mut self, node: Node) { + let mut nodes = match self.take() { + Content::Node(nodes) => nodes, + Content::Empty => vec![], + _ => unreachable!("cannot push onto data field"), + }; + + nodes.push(node); + + self.replace(Content::Node(nodes)); + } + + fn replace(&'b mut self, other: Content) -> Content { + std::mem::replace(self, other) + } + + fn take(&'b mut self) -> Content { + std::mem::take(self) + } + + fn iter(self) -> impl Iterator { + match self { + Content::Node(nodes) => nodes, + _ => vec![], + } + .into_iter() + } +} + +#[derive(Debug)] +struct Node { + name: &'static str, + attributes: Attributes, + closed: bool, + content: Content, +} + +impl Node { + fn attributes(&self) -> String { + let mut fmt = String::new(); + for attr in &self.attributes { + fmt = format!(r#"{fmt}{key}="{value}" "#, key = attr.0, value = attr.1) + } + + fmt + } + + // TODO: ✨ Beautify ✨ this + fn serialize(self, level: usize) -> String { + let (indent, name, attributes) = (" ".repeat(level * INDENT_SPACES), self.name, self.attributes()); + let mut fmt = String::new(); + match self.content { + Content::Data(data) => fmt = format!("{indent}<{name} {attributes}>{data}\n",), + _ => { + if self.closed { + fmt = format!( + "{indent}<{name} {attributes}/>\n", + indent = " ".repeat(level * INDENT_SPACES), + name = self.name, + attributes = self.attributes() + ); + } + + if !self.closed { + fmt = format!( + "{indent}<{name} {attributes}>\n", + indent = " ".repeat(level * INDENT_SPACES), + name = self.name, + attributes = self.attributes() + ); + } + + for node in self.content.iter() { + fmt = format!("{fmt}{}", node.serialize(level + 1)); + } + + if !self.closed { + fmt = format!( + "{fmt}{indent}\n", + indent = " ".repeat(level * INDENT_SPACES), + name = &self.name + ); + } + } + }; + + #[cfg(feature = "debug")] + println!("{fmt}"); + + fmt + } +} + +pub(crate) fn with_header(data: &str) -> String { + let header = r#""#; + format!("{header}\n{data}") +} + +// For `declare_type_and_extend_if_needed! { (Pou, "pou", (Body, with_body)) }` the macro will expand to +// +// pub(crate) struct Pou(Node); +// +// impl Pou { +// pub fn new() -> Self { +// Self(Node { name: "pou", attributes: vec![], closed: false, nodes: vec![] }) +// } +// +// ... the remaining non-optional functions in the impl block ... +// +// ... the optional extend method +// pub(crate) fn with_body(arg: Body) -> Self { +// self.get_inner_ref_mut().nodes.push(arg.get_inner()); +// self +// } +// } +// TODO: revisit visiblity of functions +macro_rules! declare_type_and_extend_if_needed { + ($(($name:ident, $name_xml:expr, $(($arg:ty, $fn_name:ident)),*),) +) => { + $( + // will be implemented for every $name + #[derive(Debug)] + pub(crate) struct $name(Node); + impl $name { + pub fn new() -> Self { + Self(Node { name: $name_xml, attributes: vec![], closed: false, content: Content::Empty }) + } + + pub fn with_attribute(mut self, key: &'static str, value: &'static str) -> Self { + self.0.attributes.push((key, value)); + self + } + + pub fn with_data(mut self, data: &'static str) -> Self { + self.0.content = Content::Data(data); + self + } + + pub fn close(mut self) -> Self { + self.0.closed = true; + self + } + + pub fn serialize(self) -> String { + self.0.serialize(0) + } + + fn get_inner(self) -> Node { + self.0 + } + + fn get_inner_ref_mut(&mut self) -> &mut Node { + &mut self.0 + } + + // this part is optional. + $( + pub(crate) fn $fn_name(mut self, value: $arg) -> Self { + self.get_inner_ref_mut().content.push(value.get_inner()); + self + })* + })* + } +} + +declare_type_and_extend_if_needed! { + ( + XPou, "pou", + (XBody, with_body), + (XInterface, with_interface) + ), + ( + XBlock, "block", + (XInOutVariables, with_inout_variables), + (XInputVariables, with_input_variables), + (XOutputVariables, with_output_variables) + ), + ( + + XBody, "body", + (XFbd, with_fbd) + ), + ( + XConnectionPointIn, "connectionPointIn", + (XConnection, with_connection), + (XRelPosition, with_rel_position) + ), + ( + XConnectionPointOut, "connectionPointOut", + (XConnection, with_connection), + (XRelPosition, with_rel_position) + ), + ( + XFbd, "FBD", + (XBlock, with_block), + (XInVariable, with_in_variable), + (XOutVariable, with_out_variable), + (XContinuation, with_continuation), + (XConnector, with_connector) + ), + ( + XVariable, "variable", + (XConnectionPointIn, with_connection_in), + (XConnectionPointOut, with_connection_out) + ), + ( + XInVariable, "inVariable", + (XConnectionPointOut, with_connection_point_out), + (XPosition, with_position), + (XExpression, with_expression) + ), + ( + XOutVariable, "outVariable", + (XPosition, with_position), + (XConnectionPointIn, with_connection_point_in), + (XExpression, with_expression) + ), + ( + XInOutVariables, "inOutVariables", + (XVariable, with_variable) + ), + ( + XInputVariables, "inputVariables", + (XVariable, with_variable) + ), + ( + XOutputVariables, "outputVariables", + (XVariable, with_variable) + ), + ( + XInterface, "interface", + (XLocalVars, with_local_vars), + (XAddData, with_add_data) + ), + (XLocalVars, "localVars",), + ( + XAddData, "addData", + (XData, with_data_data) + ), + ( + XData, "data", + (XTextDeclaration, with_text_declaration) + ), + ( + XTextDeclaration, "textDeclaration", + (XContent, with_content) + ), + (XContent, "content",), + + // these are not being extended: + (XPosition, "position",), + (XRelPosition, "relPosition",), + (XConnection, "connection",), + (XExpression, "expression",), + + ( + XContinuation, "continuation", + (XPosition, with_position), + (XConnectionPointOut, with_connection_point_out) + ), + ( + XConnector, "connector", + (XPosition, with_position), + (XConnectionPointIn, with_connection_point_in) + ), +} + +#[cfg(test)] +mod tests { + + use crate::serializer::{XConnection, XConnectionPointIn, XConnectionPointOut, XRelPosition}; + + use super::{ + XAddData, XBlock, XContent, XData, XInVariable, XInterface, XOutVariable, XPou, XTextDeclaration, + XVariable, + }; + + // convenience methods to reduce amount of boiler-plate-code + impl XVariable { + pub(crate) fn init(name: &'static str, negated: bool) -> Self { + XVariable::new() + .with_attribute("formalParameter", name) + .with_attribute("negated", if negated { "true" } else { "false" }) + } + + pub(crate) fn with_connection_in_initialized(self, ref_lid: &'static str) -> Self { + self.with_connection_in( + XConnectionPointIn::new() + .with_rel_position(XRelPosition::init().close()) + .with_connection(XConnection::new().with_attribute("refLocalId", ref_lid).close()), + ) + } + + pub(crate) fn with_connection_out_initialized(self) -> Self { + self.with_connection_out( + XConnectionPointOut::new().with_rel_position(XRelPosition::init().close()), + ) + } + } + + impl XRelPosition { + pub(crate) fn init() -> Self { + XRelPosition::new().with_attribute("x", "0").with_attribute("y", "0").close() + } + } + + impl XConnectionPointIn { + pub(crate) fn with_ref(ref_local_id: &'static str) -> Self { + XConnectionPointIn::new() + .with_rel_position(XRelPosition::init().close()) + .with_connection(XConnection::new().with_attribute("refLocalId", ref_local_id).close()) + } + } + + impl XConnectionPointOut { + pub(crate) fn with_rel_pos() -> Self { + XConnectionPointOut::new().with_rel_position(XRelPosition::init().close()) + } + } + + impl XInVariable { + pub(crate) fn init(local_id: &'static str, negated: bool) -> Self { + XInVariable::new() + .with_attribute("localId", local_id) + .with_attribute("negated", if negated { "true" } else { "false" }) + } + } + + impl XOutVariable { + pub(crate) fn init(local_id: &'static str, negated: bool) -> Self { + XOutVariable::new() + .with_attribute("localId", local_id) + .with_attribute("negated", if negated { "true" } else { "false" }) + } + } + + impl XBlock { + pub(crate) fn init(local_id: &'static str, type_name: &'static str, exec_id: &'static str) -> Self { + XBlock::new() + .with_attribute("localId", local_id) + .with_attribute("typeName", type_name) + .with_attribute("executionOrderId", exec_id) + } + } + + impl XPou { + pub(crate) fn init(name: &'static str, pou_type: &'static str, declaration: &'static str) -> Self { + XPou::new() + .with_attribute("xmlns", "http://www.plcopen.org/xml/tc6_0201") + .with_attribute("name", name) + .with_attribute("pouType", pou_type) + .with_interface(XInterface::init_with_text_declaration(declaration)) + } + } + + impl XInterface { + pub(crate) fn init_with_text_declaration(declaration: &'static str) -> Self { + XInterface::new().with_add_data(XAddData::new().with_data_data( + XData::new().with_text_declaration( + XTextDeclaration::new().with_content(XContent::new().with_data(declaration)), + ), + )) + } + } +} diff --git a/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__model_is_sorted_by_execution_order.snap b/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__model_is_sorted_by_execution_order.snap new file mode 100644 index 0000000000..cd1e9a211d --- /dev/null +++ b/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__model_is_sorted_by_execution_order.snap @@ -0,0 +1,68 @@ +--- +source: compiler/plc_cfc/src/cfc_parser.rs +expression: "deserializer::visit(src).unwrap()" +--- +Pou { + name: "thistimereallyeasy", + pou_type: Program, + body: Body { + function_block_diagram: FunctionBlockDiagram { + nodes: { + 1: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 1, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 2, + negated: false, + expression: "b", + execution_order_id: Some( + 2, + ), + ref_local_id: Some( + 1, + ), + }, + ), + 3: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 3, + negated: false, + expression: "c", + execution_order_id: Some( + 0, + ), + ref_local_id: Some( + 1, + ), + }, + ), + 4: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 4, + negated: false, + expression: "d", + execution_order_id: Some( + 1, + ), + ref_local_id: Some( + 1, + ), + }, + ), + }, + }, + global_id: None, + }, + declaration: "\n PROGRAM thistimereallyeasy\n VAR\n a, b, c, d : DINT;\n END_VAR\n \nEND_PROGRAM", +} diff --git a/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__variable_assignment.snap b/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__variable_assignment.snap new file mode 100644 index 0000000000..0ff7762095 --- /dev/null +++ b/compiler/plc_xml/src/snapshots/plc_cfc__cfc_parser__tests__variable_assignment.snap @@ -0,0 +1,40 @@ +--- +source: compiler/plc_cfc/src/cfc_parser.rs +expression: pou +--- +Pou { + name: "thistimereallyeasy", + pou_type: Program, + body: Body { + function_block_diagram: FunctionBlockDiagram { + nodes: { + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 2, + negated: false, + expression: "b", + execution_order_id: Some( + 0, + ), + ref_local_id: Some( + 1, + ), + }, + ), + 1: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 1, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + }, + }, + global_id: None, + }, + declaration: "\n PROGRAM thistimereallyeasy\n VAR\n a, b : DINT;\n END_VAR\n \nEND_PROGRAM", +} diff --git a/compiler/plc_xml/src/xml_parser.rs b/compiler/plc_xml/src/xml_parser.rs new file mode 100644 index 0000000000..9ab26c53b6 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser.rs @@ -0,0 +1,169 @@ +use ast::{ + ast::{ + AstId, AstStatement, CompilationUnit, Implementation, LinkageType, PouType as AstPouType, + SourceRange, SourceRangeFactory, + }, + provider::IdProvider, +}; +use plc::{lexer, parser::expressions_parser::parse_expression}; +use plc_diagnostics::{diagnostician::Diagnostician, diagnostics::Diagnostic}; + +use quick_xml::events::Event; + +use crate::{ + error::Error, + model::{pou::PouType, project::Project}, + reader::PeekableReader, +}; + +mod action; +mod block; +mod fbd; +mod pou; +mod tests; +mod variables; + +pub(crate) trait Parseable { + type Item; + fn visit(reader: &mut PeekableReader) -> Result; +} + +pub(crate) fn visit(content: &str) -> Result { + let mut reader = PeekableReader::new(content); + loop { + match reader.peek()? { + Event::Start(tag) if tag.name().as_ref() == b"pou" => return Project::pou_entry(&mut reader), + Event::Start(tag) if tag.name().as_ref() == b"project" => return Project::visit(&mut reader), + Event::Eof => return Err(Error::UnexpectedEndOfFile(vec![b"pou"])), + _ => reader.consume()?, + } + } +} + +pub fn parse_file( + source: &str, + location: &'static str, + linkage: LinkageType, + id_provider: IdProvider, + diagnostician: &mut Diagnostician, +) -> CompilationUnit { + let (unit, errors) = parse(source, location, linkage, id_provider); + //Register the source file with the diagnostician + diagnostician.register_file(location.to_string(), source.to_string()); + diagnostician.handle(errors); + unit +} + +fn parse( + source: &str, + location: &'static str, + linkage: LinkageType, + id_provider: IdProvider, +) -> (CompilationUnit, Vec) { + // transform the xml file to a data model. + // XXX: consecutive call-statements are nested in a single ast-statement. this will be broken up with temporary variables in the future + let Ok(project) = visit(source) else { + todo!("cfc errors need to be transformed into diagnostics") + }; + + // create a new parse session + let parser = ParseSession::new(&project, location, id_provider, linkage); + + // try to parse a declaration data field + let Some((unit, diagnostics)) = parser.try_parse_declaration() else { + unimplemented!("XML schemas without text declarations are not yet supported") + }; + + // transform the data model into rusty AST statements and add them to the compilation unit + (unit.with_implementations(parser.parse_model()), diagnostics) +} + +pub(crate) struct ParseSession<'parse> { + project: &'parse Project, + id_provider: IdProvider, + linkage: LinkageType, + file_name: &'static str, + range_factory: SourceRangeFactory, +} + +impl<'parse> ParseSession<'parse> { + fn new( + project: &'parse Project, + file_name: &'static str, + id_provider: IdProvider, + linkage: LinkageType, + ) -> Self { + ParseSession { + project, + id_provider, + linkage, + file_name, + range_factory: SourceRangeFactory::for_file(file_name), + } + } + + /// parse the compilation unit from the addData field + fn try_parse_declaration(&self) -> Option<(CompilationUnit, Vec)> { + let Some(content) = self.project.pous + .first() + .and_then(|it| + it.interface + .as_ref() + .and_then(|it| + it.get_data_content() + ) + ) else { + return None + }; + + //TODO: if our ST parser returns a diagnostic here, we might not have a text declaration and need to rely on the XML to provide us with + // the necessary data. for now, we will assume to always have a text declaration + Some(plc::parser::parse( + lexer::lex_with_ids( + content, + self.id_provider.clone(), + SourceRangeFactory::for_file(self.file_name), + ), + self.linkage, + self.file_name, + )) + } + + fn parse_expression(&self, expr: &str) -> AstStatement { + // TODO: diagnostics not handled + parse_expression(&mut lexer::lex_with_ids( + html_escape::decode_html_entities_to_string(expr, &mut String::new()), + self.id_provider.clone(), + SourceRangeFactory::for_file(self.file_name), + )) + } + + fn parse_model(&self) -> Vec { + let mut implementations = vec![]; + for pou in &self.project.pous { + // transform body + implementations.push(pou.build_implementation(self)); + // transform actions + pou.actions.iter().for_each(|action| implementations.push(action.build_implementation(self))); + } + implementations + } + + fn next_id(&self) -> AstId { + self.id_provider.clone().next_id() + } + + fn create_range(&self, range: core::ops::Range) -> SourceRange { + self.range_factory.create_range(range) + } +} + +impl From for AstPouType { + fn from(value: PouType) -> Self { + match value { + PouType::Program => AstPouType::Program, + PouType::Function => AstPouType::Function, + PouType::FunctionBlock => AstPouType::FunctionBlock, + } + } +} diff --git a/compiler/plc_xml/src/xml_parser/action.rs b/compiler/plc_xml/src/xml_parser/action.rs new file mode 100644 index 0000000000..f91bdd7560 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/action.rs @@ -0,0 +1,28 @@ +use ast::ast::{AstStatement, Implementation, PouType as AstPouType, SourceRange}; + +use crate::model::action::Action; + +use super::ParseSession; + +impl Action { + pub(crate) fn transform(&self, _session: &ParseSession) -> Vec { + todo!() + } + + pub(crate) fn build_implementation(&self, session: &ParseSession) -> Implementation { + let statements = self.transform(session); + + Implementation { + name: self.name.to_owned(), + type_name: self.type_name.to_owned(), + linkage: session.linkage, + pou_type: AstPouType::Action, + statements, + location: SourceRange::undefined(), + name_location: SourceRange::undefined(), + overriding: false, + generic: false, + access: None, + } + } +} diff --git a/compiler/plc_xml/src/xml_parser/block.rs b/compiler/plc_xml/src/xml_parser/block.rs new file mode 100644 index 0000000000..3c544fb5f0 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/block.rs @@ -0,0 +1,38 @@ +use ast::ast::{AstStatement, SourceRange}; + +use crate::model::{block::Block, fbd::NodeIndex}; + +use super::ParseSession; + +impl Block { + pub(crate) fn transform(&self, session: &ParseSession, index: &NodeIndex) -> AstStatement { + let operator = Box::new(AstStatement::Reference { + name: self.type_name.clone(), + location: SourceRange::undefined(), + id: session.next_id(), + }); + + let parameters = if !self.variables.is_empty() { + Box::new(Some(AstStatement::ExpressionList { + expressions: self + .variables + .iter() + .filter_map(|var| { + // try to transform the element this block variable points to + var.transform(session, index) + }) + .collect(), + id: session.next_id(), + })) + } else { + Box::new(None) + }; + + AstStatement::CallStatement { + operator, + parameters, + location: SourceRange::undefined(), + id: session.next_id(), + } + } +} diff --git a/compiler/plc_xml/src/xml_parser/fbd.rs b/compiler/plc_xml/src/xml_parser/fbd.rs new file mode 100644 index 0000000000..7cfbf47f20 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/fbd.rs @@ -0,0 +1,77 @@ +use ast::ast::AstStatement; +use indexmap::IndexMap; + +use crate::model::fbd::{FunctionBlockDiagram, Node, NodeId}; + +use super::ParseSession; + +impl FunctionBlockDiagram { + /// Transforms the body of a function block diagram to their AST-equivalent, in order of execution. + /// Only statements that are necessary for execution logic will be selected. + pub(crate) fn transform(&self, session: &ParseSession) -> Vec { + let mut ast_association = IndexMap::new(); + // transform each node to an ast-statement. since we might see and transform a node multiple times, we use an + // ast-association map to keep track of the latest statement for each id + self.nodes.iter().for_each(|(id, _)| { + let (insert, remove_id) = self.transform_node(*id, session, &ast_association); + + if let Some(id) = remove_id { + ast_association.remove(&id); + }; + + ast_association.insert(*id, insert); + }); + + // filter the map for each statement belonging to a node with an execution id or a temp-var, discard the rest -> these have no impact + ast_association + .into_iter() + .filter(|(key, _)| self.nodes.get(key).is_some_and(|node| node.get_exec_id().is_some())) + .map(|(_, value)| value) + .collect() + } + + fn transform_node( + &self, + id: NodeId, + session: &ParseSession, + ast_association: &IndexMap, + ) -> (AstStatement, Option) { + let Some(current_node) = self.nodes.get(&id) else { + unreachable!() + }; + + match current_node { + Node::Block(block) => (block.transform(session, &self.nodes), None), + Node::FunctionBlockVariable(var) => { + let lhs = var.transform(session); + + // if we are not being assigned to, we can return here + let Some(ref_id) = var.ref_local_id else { + return (lhs, None); + }; + + let (rhs, remove_id) = ast_association + .get(&ref_id) + .map(|stmt| { + if matches!(stmt, AstStatement::CallStatement { .. }) { + (stmt.clone(), Some(ref_id)) + } else { + self.transform_node(ref_id, session, ast_association) + } + }) + .expect("Expected AST statement, found None"); + + ( + AstStatement::Assignment { + left: Box::new(lhs), + right: Box::new(rhs), + id: session.next_id(), + }, + remove_id, + ) + } + Node::Control(_) => todo!(), + Node::Connector(_) => todo!(), + } + } +} diff --git a/compiler/plc_xml/src/xml_parser/pou.rs b/compiler/plc_xml/src/xml_parser/pou.rs new file mode 100644 index 0000000000..e582f0e961 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/pou.rs @@ -0,0 +1,40 @@ +use ast::ast::{AstStatement, Implementation, SourceRange}; + +use crate::model::pou::Pou; + +use super::ParseSession; + +impl Pou { + fn transform(&self, session: &ParseSession) -> Vec { + let Some(fbd) = &self.body.function_block_diagram else { + // empty body + return vec![] + }; + + if cfg!(feature = "debug") { + let statements = fbd.transform(session); + println!("{statements:#?}"); + + return statements; + } + + fbd.transform(session) + } + + pub fn build_implementation(&self, session: &ParseSession) -> Implementation { + let statements = self.transform(session); + + Implementation { + name: self.name.to_owned(), + type_name: self.name.to_owned(), + linkage: session.linkage, + pou_type: self.pou_type.into(), + statements, + location: SourceRange::undefined(), + name_location: SourceRange::undefined(), + overriding: false, + generic: false, + access: None, + } + } +} diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__function_returns.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__function_returns.snap new file mode 100644 index 0000000000..bdf7d1e970 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__function_returns.snap @@ -0,0 +1,55 @@ +--- +source: compiler/plc_xml/src/xml_parser/tests.rs +expression: "deserializer::visit(&content).unwrap()" +--- +Project { + pous: [ + Pou { + name: "FuncyReturn", + pou_type: Function, + body: Body { + function_block_diagram: Some( + FunctionBlockDiagram { + nodes: { + 1: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 1, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 2, + negated: false, + expression: "FuncyReturn", + execution_order_id: Some( + 0, + ), + ref_local_id: Some( + 1, + ), + }, + ), + }, + }, + ), + }, + actions: [], + interface: Some( + Interface { + add_data: Some( + Data { + content: "FUNCTION FuncyReturn : DINT\n VAR_INPUT\n a : DINT;\n END_VAR\nEND_FUNCTION", + handle: Implementation, + }, + ), + }, + ), + }, + ], +} diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__model_is_sorted_by_execution_order.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__model_is_sorted_by_execution_order.snap new file mode 100644 index 0000000000..97f669fb12 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__model_is_sorted_by_execution_order.snap @@ -0,0 +1,83 @@ +--- +source: compiler/plc_xml/src/xml_parser/tests.rs +expression: "deserializer::visit(src).unwrap()" +--- +Project { + pous: [ + Pou { + name: "thistimereallyeasy", + pou_type: Program, + body: Body { + function_block_diagram: Some( + FunctionBlockDiagram { + nodes: { + 1: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 1, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + 3: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 3, + negated: false, + expression: "c", + execution_order_id: Some( + 0, + ), + ref_local_id: Some( + 1, + ), + }, + ), + 4: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 4, + negated: false, + expression: "d", + execution_order_id: Some( + 1, + ), + ref_local_id: Some( + 1, + ), + }, + ), + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 2, + negated: false, + expression: "b", + execution_order_id: Some( + 2, + ), + ref_local_id: Some( + 1, + ), + }, + ), + }, + }, + ), + }, + actions: [], + interface: Some( + Interface { + add_data: Some( + Data { + content: "\n PROGRAM thistimereallyeasy\n VAR\n a, b, c, d : DINT;\n END_VAR\n \nEND_PROGRAM", + handle: Implementation, + }, + ), + }, + ), + }, + ], +} diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__simple_return.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__simple_return.snap new file mode 100644 index 0000000000..b6f1245f95 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__simple_return.snap @@ -0,0 +1,138 @@ +--- +source: compiler/plc_xml/src/xml_parser/tests.rs +expression: parse(&content) +--- +( + CompilationUnit { + global_vars: [], + units: [ + POU { + name: "increment_until", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "value", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Input( + ByVal, + ), + }, + VariableBlock { + variables: [ + Variable { + name: "i", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + initializer: Some( + LiteralInteger { + value: 0, + }, + ), + }, + ], + variable_block_type: Local, + }, + ], + pou_type: Function, + return_type: Some( + DataTypeReference { + referenced_type: "INT", + }, + ), + }, + ], + implementations: [ + Implementation { + name: "increment_until", + type_name: "increment_until", + linkage: Internal, + pou_type: Function, + statements: [ + ReturnStatement (conditional; BinaryExpression { operator: Equal, left: Reference { name: "i" }, right: Reference { name: "value" } }, + CallStatement { + operator: Reference { + name: "ADD", + }, + parameters: Some( + ExpressionList { + expressions: [ + Reference { + name: "i", + }, + LiteralInteger { + value: 1, + }, + ], + }, + ), + }, + Assignment { + left: Reference { + name: "i", + }, + right: CallStatement { + operator: Reference { + name: "ADD", + }, + parameters: Some( + ExpressionList { + expressions: [ + Reference { + name: "i", + }, + LiteralInteger { + value: 1, + }, + ], + }, + ), + }, + }, + ], + location: SourceRange { + range: 0..0, + }, + name_location: SourceRange { + range: 0..0, + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.cfc", + new_lines: NewLines { + line_breaks: [ + 31, + 41, + 58, + 66, + 67, + 71, + 87, + 95, + ], + }, + }, + [ + SyntaxError { + message: "Unexpected token: expected KeywordEndFunction but found ''", + range: [ + SourceRange { + range: 94..94, + file: Some( + "test.cfc", + ), + }, + ], + err_no: syntax__unexpected_token, + }, + ], +) diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__variable_assignment.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__variable_assignment.snap new file mode 100644 index 0000000000..2d492d9295 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__tests__tests__variable_assignment.snap @@ -0,0 +1,55 @@ +--- +source: compiler/plc_xml/src/xml_parser/tests.rs +expression: pou +--- +Project { + pous: [ + Pou { + name: "thistimereallyeasy", + pou_type: Program, + body: Body { + function_block_diagram: Some( + FunctionBlockDiagram { + nodes: { + 1: FunctionBlockVariable( + FunctionBlockVariable { + kind: Input, + local_id: 1, + negated: false, + expression: "a", + execution_order_id: None, + ref_local_id: None, + }, + ), + 2: FunctionBlockVariable( + FunctionBlockVariable { + kind: Output, + local_id: 2, + negated: false, + expression: "b", + execution_order_id: Some( + 0, + ), + ref_local_id: Some( + 1, + ), + }, + ), + }, + }, + ), + }, + actions: [], + interface: Some( + Interface { + add_data: Some( + Data { + content: "\n PROGRAM thistimereallyeasy\n VAR\n a, b : DINT;\n END_VAR\n \nEND_PROGRAM", + handle: Implementation, + }, + ), + }, + ), + }, + ], +} diff --git a/compiler/plc_xml/src/xml_parser/tests.rs b/compiler/plc_xml/src/xml_parser/tests.rs new file mode 100644 index 0000000000..b27c2ba3b9 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/tests.rs @@ -0,0 +1,165 @@ +#[cfg(test)] +mod tests { + use ast::{ + ast::{CompilationUnit, LinkageType}, + provider::IdProvider, + }; + use insta::assert_debug_snapshot; + use plc_diagnostics::diagnostics::Diagnostic; + + use crate::{ + serializer::{ + with_header, XBody, XConnection, XConnectionPointIn, XExpression, XFbd, XInVariable, + XOutVariable, XPou, XRelPosition, + }, + xml_parser::{self, tests::ASSIGNMENT_A_B}, + }; + + fn parse(content: &str) -> (CompilationUnit, Vec) { + xml_parser::parse(content, "test.cfc", LinkageType::Internal, IdProvider::default()) + } + + #[test] + fn variable_assignment() { + let pou = xml_parser::visit(ASSIGNMENT_A_B).unwrap(); + assert_debug_snapshot!(pou); + } + + #[test] + fn model_is_sorted_by_execution_order() { + let src = r#" + + + + + + + + + PROGRAM thistimereallyeasy + VAR + a, b, c, d : DINT; + END_VAR + + + + + + + + + + + + + a + + + + + + + + b + + + + + + + + c + + + + + + + + d + + + + + "#; + + assert_debug_snapshot!(xml_parser::visit(src).unwrap()); + } + + #[test] + fn function_returns() { + let content = with_header( + &XPou::init( + "FuncyReturn", + "function", + "FUNCTION FuncyReturn : DINT + VAR_INPUT + a : DINT; + END_VAR", + ) + .with_body( + XBody::new().with_fbd( + XFbd::new() + .with_in_variable( + XInVariable::init("1", false).with_expression(XExpression::new().with_data("a")), + ) + .with_out_variable( + XOutVariable::init("2", false) + .with_attribute("executionOrderId", "0") + .with_expression(XExpression::new().with_data("FuncyReturn")) + .with_connection_point_in( + XConnectionPointIn::new() + .with_rel_position(XRelPosition::init().close()) + .with_connection( + XConnection::new().with_attribute("refLocalId", "1").close(), + ), + ), + ), + ), + ) + .serialize(), + ); + + assert_debug_snapshot!(xml_parser::visit(&content).unwrap()); + } +} + +const ASSIGNMENT_A_B: &str = r#" + + + + + + + + + PROGRAM thistimereallyeasy + VAR + a, b : DINT; + END_VAR + + + + + + + + + + + + + + b + + + + + + + a + + + + +"#; diff --git a/compiler/plc_xml/src/xml_parser/variables.rs b/compiler/plc_xml/src/xml_parser/variables.rs new file mode 100644 index 0000000000..b2a91bb1c5 --- /dev/null +++ b/compiler/plc_xml/src/xml_parser/variables.rs @@ -0,0 +1,45 @@ +use ast::ast::{AstStatement, Operator}; + +use crate::model::{ + fbd::{Node, NodeIndex}, + variables::{BlockVariable, FunctionBlockVariable}, +}; + +use super::ParseSession; + +impl BlockVariable { + pub(crate) fn transform(&self, session: &ParseSession, index: &NodeIndex) -> Option { + let Some(ref_id) = &self.ref_local_id else { + // param not provided/passed + return None; + }; + + // XXX: data-recursion? + match index.get(ref_id) { + Some(Node::Block(block)) => Some(block.transform(session, index)), + Some(Node::FunctionBlockVariable(var)) => Some(var.transform(session)), + Some(Node::Control(_)) => todo!(), + Some(Node::Connector(_)) => todo!(), + None => unreachable!(), + } + } +} + +// variables, parameters -> more readable names? +impl FunctionBlockVariable { + pub(crate) fn transform(&self, session: &ParseSession) -> AstStatement { + if self.negated { + let ident = session.parse_expression(&self.expression); + let location = ident.get_location(); + + AstStatement::UnaryExpression { + operator: Operator::Not, + value: Box::new(ident), + location, + id: session.next_id(), + } + } else { + session.parse_expression(&self.expression) + } + } +} diff --git a/src/lexer.rs b/src/lexer.rs index 71aae37329..d9075d3919 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,14 +1,11 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use core::ops::Range; -use logos::Filter; -use logos::Lexer; -use logos::Logos; +use logos::{Filter, Lexer, Logos}; +use plc_ast::ast::{AstId, DirectAccessType, HardwareAccessType, SourceRange, SourceRangeFactory}; use plc_ast::provider::IdProvider; use plc_diagnostics::diagnostics::Diagnostic; pub use tokens::Token; -use plc_ast::ast::{AstId, DirectAccessType, HardwareAccessType, SourceRange, SourceRangeFactory}; - #[cfg(test)] mod tests; mod tokens; diff --git a/src/parser.rs b/src/parser.rs index b0d1cffb47..631b829680 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -23,7 +23,7 @@ use self::{ }; mod control_parser; -mod expressions_parser; +pub mod expressions_parser; #[cfg(test)] pub mod tests; diff --git a/src/validation.rs b/src/validation.rs index 15b25031fb..1af3bd0866 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -1,6 +1,6 @@ use plc_ast::ast::{AstStatement, CompilationUnit}; +use plc_derive::Validators; use plc_diagnostics::diagnostics::Diagnostic; -use rusty_derive::Validators; use crate::{ index::{ diff --git a/tests/integration/cfc.rs b/tests/integration/cfc.rs new file mode 100644 index 0000000000..bd0b448eb0 --- /dev/null +++ b/tests/integration/cfc.rs @@ -0,0 +1,82 @@ +use driver::runner::compile_and_run; + +use crate::get_test_file; + +#[test] +fn variables_assigned() { + // GIVEN a ST program with hardcoded values and CFC program with two variables but without a body + let st_file = get_test_file("cfc/assigning.st"); + let cfc_file = get_test_file("cfc/assigning.cfc"); + + // WHEN assigning these values to the CFC-POU variables and adding them together + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + + // THEN we get the correct result + assert_eq!(res, 300); +} + +#[test] +fn simple_assignment() { + // GIVEN a CFC program which assigns one variable to another + let st_file = get_test_file("cfc/variable_assignment.st"); + let cfc_file = get_test_file("cfc/variable_assignment.cfc"); + // WHEN assigning values to them and then calling the program + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN the second variable will have the value of the first variable + assert_eq!(res, 10); +} + +#[test] +fn select_call_in_function_block_with_input_variables() { + // GIVEN a CFC program which selects a variable based on a predicate + let st_file = get_test_file("cfc/select.st"); + let cfc_file = get_test_file("cfc/select.cfc"); + // WHEN assigning values to them and then calling the program + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN the correct value is selected + assert_eq!(res, 1); +} + +#[test] +fn custom_function_call_in_function_block() { + // GIVEN a CFC program which calls a subroutine + let st_file = get_test_file("cfc/my_add.st"); + let cfc_file = get_test_file("cfc/my_add.cfc"); + // WHEN calling the + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN the second variable will have the value of the first variable + assert_eq!(res, 4); +} + +#[test] +fn chained_calls() { + // GIVEN a CFC program which assigns a variable + let st_file = get_test_file("cfc/chained_calls.st"); + let cfc_file = get_test_file("cfc/chained_calls.cfc"); + // WHEN assigning values to them and then calling the program + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN the second variable will have the value of the first variable + assert_eq!(res, 10); +} + +#[test] +fn chained_calls_galore() { + // GIVEN a CFC program which assigns a variable + let st_file = get_test_file("cfc/chained_calls_galore.st"); + let cfc_file = get_test_file("cfc/chained_calls_galore.cfc"); + // WHEN assigning values to them and then calling the program + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN the second variable will have the value of the first variable + assert_eq!(res, 88); +} + +#[test] +fn function_returns() { + // GIVEN a CFC function which doubles a value + let st_file = get_test_file("cfc/function_returns.st"); + let cfc_file = get_test_file("cfc/function_returns.cfc"); + // WHEN passing a value into the function + let res: i32 = compile_and_run(vec![st_file, cfc_file], &mut {}); + // THEN it will return the correct value + assert_eq!(res, 222); +} diff --git a/tests/integration/data/cfc/assigning.cfc b/tests/integration/data/cfc/assigning.cfc new file mode 100644 index 0000000000..d868cf0b05 --- /dev/null +++ b/tests/integration/data/cfc/assigning.cfc @@ -0,0 +1,21 @@ + + + + + + + + +FUNCTION_BLOCK pass_through +VAR + a, b: DINT; +END_VAR + + + + + + + + + diff --git a/tests/integration/data/cfc/assigning.st b/tests/integration/data/cfc/assigning.st new file mode 100644 index 0000000000..c8ee1cd6e2 --- /dev/null +++ b/tests/integration/data/cfc/assigning.st @@ -0,0 +1,10 @@ +FUNCTION main : DINT +VAR + x : DINT := 200; + y : DINT := 100; + pt : pass_through; +END_VAR + pt.a := x; + pt.b := y; + main := pt.a + pt.b +END_PROGRAM \ No newline at end of file diff --git a/tests/integration/data/cfc/chained_calls.cfc b/tests/integration/data/cfc/chained_calls.cfc new file mode 100644 index 0000000000..7999894425 --- /dev/null +++ b/tests/integration/data/cfc/chained_calls.cfc @@ -0,0 +1,127 @@ + + + + + + + + +FUNCTION_BLOCK myAdder +VAR + x, y: DINT; +END_VAR + +VAR_OUTPUT + +END_VAR + +VAR + +END_VAR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + + + + y + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + diff --git a/tests/integration/data/cfc/chained_calls.st b/tests/integration/data/cfc/chained_calls.st new file mode 100644 index 0000000000..e6b5d26835 --- /dev/null +++ b/tests/integration/data/cfc/chained_calls.st @@ -0,0 +1,23 @@ +FUNCTION main : DINT +VAR + x : DINT := 2; + y : DINT := 1; + adder : myAdder; +END_VAR + adder.x := x; + adder.y := y; + + // temp1 := x + y + 1; + // temp2 := temp1 + y + 1; + // x := temp1 + temp2 = 2*temp1 + y + 1 = 2x + 2y + 2 + y + 1 + // x := 2x + 3y + 3 + adder(); + main := adder.x; +END_PROGRAM + +FUNCTION myAdd : DINT +VAR_INPUT + a, b : DINT; +END_VAR + myAdd := a + b; +END_FUNCTION \ No newline at end of file diff --git a/tests/integration/data/cfc/chained_calls_galore.cfc b/tests/integration/data/cfc/chained_calls_galore.cfc new file mode 100644 index 0000000000..1b974c3d83 --- /dev/null +++ b/tests/integration/data/cfc/chained_calls_galore.cfc @@ -0,0 +1,301 @@ + + + + + + + + +PROGRAM ridiculous_chaining +VAR_INPUT + x, y : DINT; +END_VAR +VAR + z : DINT; +END_VAR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + + + + y + + + + + + + + z + + + + +
x, y = 2
+
+
+ + + +
4
+
+
+ + + +
8
+
+
+ + + +
8
+
+
+ + + +
16
+
+
+ + + +
32
+
+
+ + + +
48
+
+
+ + + +
64
+
+
+ + + +
112
+
+
+
+ +
\ No newline at end of file diff --git a/tests/integration/data/cfc/chained_calls_galore.st b/tests/integration/data/cfc/chained_calls_galore.st new file mode 100644 index 0000000000..e6ad0aacd1 --- /dev/null +++ b/tests/integration/data/cfc/chained_calls_galore.st @@ -0,0 +1,17 @@ +FUNCTION main : DINT +VAR + x : DINT := 2; + y : DINT := 2; + chainer : ridiculous_chaining; +END_VAR + chainer(x, y); + + main := chainer.z; +END_PROGRAM + +FUNCTION myAdd : DINT +VAR_INPUT + a, b : DINT; +END_VAR + myAdd := a + b; +END_FUNCTION \ No newline at end of file diff --git a/tests/integration/data/cfc/function_returns.cfc b/tests/integration/data/cfc/function_returns.cfc new file mode 100644 index 0000000000..c1e5eac1b2 --- /dev/null +++ b/tests/integration/data/cfc/function_returns.cfc @@ -0,0 +1,41 @@ + + + + + + + + +FUNCTION FuncyReturn : DINT +VAR_INPUT + a : DINT; +END_VAR + +VAR + +END_VAR + + + + + + + + + + + + + a * 2 + + + + + + + + FuncyReturn + + + + \ No newline at end of file diff --git a/tests/integration/data/cfc/function_returns.st b/tests/integration/data/cfc/function_returns.st new file mode 100644 index 0000000000..b88418f09a --- /dev/null +++ b/tests/integration/data/cfc/function_returns.st @@ -0,0 +1,3 @@ +FUNCTION main : DINT + main := FuncyReturn(111); +END_FUNCTION \ No newline at end of file diff --git a/tests/integration/data/cfc/my_add.cfc b/tests/integration/data/cfc/my_add.cfc new file mode 100644 index 0000000000..f9fcb55ca2 --- /dev/null +++ b/tests/integration/data/cfc/my_add.cfc @@ -0,0 +1,77 @@ + + + + + + + + +FUNCTION_BLOCK myAdder +VAR + x, y: DINT; +END_VAR + +VAR_OUTPUT + +END_VAR + +VAR + +END_VAR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + + + + + + + y + 1 + + + + + + + + x + + + + diff --git a/tests/integration/data/cfc/my_add.st b/tests/integration/data/cfc/my_add.st new file mode 100644 index 0000000000..ebd7360332 --- /dev/null +++ b/tests/integration/data/cfc/my_add.st @@ -0,0 +1,20 @@ +FUNCTION main : DINT +VAR + x : DINT := 2; + y : DINT := 1; + adder : myAdder; +END_VAR + adder.x := x; + adder.y := y; + + // x := x + y + 1 + adder(); + main := adder.x; +END_PROGRAM + +FUNCTION myAdd : DINT +VAR_INPUT + a, b : DINT; +END_VAR + myAdd := a + b; +END_FUNCTION \ No newline at end of file diff --git a/tests/integration/data/cfc/select.cfc b/tests/integration/data/cfc/select.cfc new file mode 100644 index 0000000000..bbb6ce3f9e --- /dev/null +++ b/tests/integration/data/cfc/select.cfc @@ -0,0 +1,86 @@ + + + + + + + + +FUNCTION_BLOCK select + +VAR_INPUT + a, b : DINT; +END_VAR +VAR + selected: DINT; +END_VAR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + a > b + + + + + + + a + + + + + + + b + + + + + + + + selected + + + + diff --git a/tests/integration/data/cfc/select.st b/tests/integration/data/cfc/select.st new file mode 100644 index 0000000000..8df01a4db6 --- /dev/null +++ b/tests/integration/data/cfc/select.st @@ -0,0 +1,11 @@ +FUNCTION main : DINT +VAR + x : DINT := 2; + y : DINT := 1; + selector : select; +END_VAR + // x > y ? y : x + selector(x, y); + // expecting y + main := selector.selected; +END_PROGRAM \ No newline at end of file diff --git a/tests/integration/data/cfc/variable_assignment.cfc b/tests/integration/data/cfc/variable_assignment.cfc new file mode 100644 index 0000000000..9d0a394e68 --- /dev/null +++ b/tests/integration/data/cfc/variable_assignment.cfc @@ -0,0 +1,37 @@ + + + + + + + + +FUNCTION_BLOCK assignment +VAR + a, b: DINT; +END_VAR + + + + + + + + + + + + + a + + + + + + + + b + + + + \ No newline at end of file diff --git a/tests/integration/data/cfc/variable_assignment.st b/tests/integration/data/cfc/variable_assignment.st new file mode 100644 index 0000000000..99a9d9175a --- /dev/null +++ b/tests/integration/data/cfc/variable_assignment.st @@ -0,0 +1,12 @@ +FUNCTION main : DINT +VAR + x : DINT := 10; + y : DINT := 33333; + assign : assignment; +END_VAR + assign.a := x; + assign.b := y; + assign(); + // b := a + main := assign.b; +END_FUNCTION \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs index ad34787b4e..22a2b58f58 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -37,6 +37,7 @@ mod correctness { } mod integration { mod build_description_tests; + mod cfc; mod command_line_compile; mod external_files; mod linking;